diff --git a/README.md b/README.md
index 5378459..2fb7481 100644
--- a/README.md
+++ b/README.md
@@ -23,11 +23,36 @@ This FlowRouter version attempts to provide the same functionality found in the
# Installation
-Use `meteor add ahref:flow-router-breadcrumb` to add the package to your meteor app
+Use `meteor add sojourneer:flow-router-breadcrumb` to add the package to your meteor app
+
+# Concept
+Two parameters need to be specified on the flow routes that will have breadcrumbs.
+The 'parent' attribute forms a chain of routes that is traversed from the current route backwards along the parent links to construct the breadcrumb trail.
+Each breadcrumb is the result of evaluating the 'title' parameter in the routes along the chain.
+
+The 'title' parameter can be a string, optionally containing route path parameter references.
+Alternatively, the 'title' parameter can be a function. In either case, a data context is provided by the package.
+The context is an object containing path and query parameters, which are taken from FlowRoute.current() for the rightmost crumb, or from the crumb to the immediate right.
+
+The 'parent' parameter can be a string, in which it is taken as the name of the parent route, or a function.
+If it is a function, it will be called with the data context described above, and must return an object containing
+* name: name of the parent route
+* params: the path params
+* queryParams: the queryParams
+
+The params and queryParams allow modifying the data context supplied to the (parent route) crumb to the left; e.g. changing the name of a parameter.
+
+Finally, the crumbs making up the breadcrumb trail are cached. When the active route changes, the trail is examined left-to-right to see if any of the routes or data context has changed.
+The changed crumb and the crumbs to its right are all re-evaluated.
# Usage
* You need to add two parameters to your flow routes which are `parent` and `title`
+ * 'parent': string or function giving the parent route and optionally modifying the data context, as described above.
+ * 'title': a string or function, as described above.
+* The following optional parameters can be specifed on the flow routes
+ * 'breadcrumbCSS' adds the specified classes to the breadcrumb
+ * 'caps' flow route parameter,if present, causes the title to be capitalized
## 1. Example Flow Router with multiple levels
@@ -60,17 +85,27 @@ FlowRouter.route('/dashboard/analytics/books', {
### In this example the Breadcrumb would look for the url `/post/hello-world` like: `Home / Blogpost Hello-World`
```
+// Level 1
FlowRouter.route('/', {
name: 'home',
template: 'home',
title: 'Home'
});
+// Level 2
FlowRouter.route('/post/:_name', {
name: 'post',
parent: 'home', // this should be the name variable of the parent route
title: 'Blogpost :_name' // the variable :_name will be automatically replaced with the value from the url
});
+
+// Level 3. Because we are using different path variables than Level 2, we construct a new object in parent() with the required names.
+FlowRouter.route('/post/:_postname/comment/:_name', {
+ name: 'post',
+ parent: function() { return {name: 'posts', params: {_name: this.params._postname}, queryParams: {}}; },
+ title: function() { return 'Comment ' + this.params._name.substr(0,4); },
+ breadcrumbCSS: 'comment'
+});
```
## 3. Example use of the de-slugify feature
@@ -95,11 +130,18 @@ Then all the '-' characters in the title will be changed into ' ' and the title
```
-
- {{#each Breadcrumb}}
- - {{title}}
- {{/each}}
-
+ {{#if BreadcrumbNonEmpty}}
+
+ {{#each Breadcrumb}}
+ {{#if active}}
+ - {{title}}
+ {{else}}
+ - {{title}}
+ {{/if}}
+ {{/each}}
+
+ {{/if}}
+ {{getReactive}}
```
@@ -112,3 +154,7 @@ if (Meteor.is_client) {
}
}
```
+
+## Internal dependencies / credits
+Forked from [ahref:flow-router-breadcrumb](https://atmospherejs.com/ahref/flow-router-breadcrumb)
+
diff --git a/lib/breadcrumb.html b/lib/breadcrumb.html
index 3ed6509..f3b1a10 100644
--- a/lib/breadcrumb.html
+++ b/lib/breadcrumb.html
@@ -1,8 +1,14 @@
-
- {{#each Breadcrumb}}
- - {{title}}
- {{/each}}
-
+ {{#if BreadcrumbNonEmpty}}
+
+ {{#each Breadcrumb}}
+ {{#if active}}
+ - {{title}}
+ {{else}}
+ - {{title}}
+ {{/if}}
+ {{/each}}
+
+ {{/if}}
{{getReactive}}
\ No newline at end of file
diff --git a/lib/breadcrumb.js b/lib/breadcrumb.js
index e2c867f..68f9dd5 100644
--- a/lib/breadcrumb.js
+++ b/lib/breadcrumb.js
@@ -1,3 +1,8 @@
+/*
+This basic idea is at each movement up the parent chain, the child constructs the parent's parameters from its own (which are in turn from it's child, or from the current route)
+
+ */
+
String.prototype.capitalize = function() {
return this.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); });
};
@@ -8,13 +13,26 @@ var getRouteByName = function(name) {
//TODO: Request a public variable way of getting this map?
return FlowRouter._routesMap[name];
};
-var enrichRouteObject = function(routeName, isCurrent) {
+
+var getParams = function(path) {
+ return _.map(path.match(/[:][^/]+/g), function(fp) { return fp.substr(1); });
+}
+
+_.mixin({
+ isEqualPicked: function(a, b, attrs) {
+ for(k in attrs) {
+ if(a[k] != b[k]) return false;
+ }
+ return true;
+ }
+})
+
+var enrichRouteObject = function(route, params, queryParams) {
// replace all parameters in the title
- var routeOptions = getRouteByName(routeName) && getRouteByName(routeName).options;
- var title = (routeOptions && routeOptions.hasOwnProperty('title')) ? routeOptions.title : FlowRouter.options.title;
+ var routeOptions = route.options;
+ var title = (routeOptions && routeOptions.hasOwnProperty('title')) ? routeOptions.title : null; //FlowRouter.options.title;
if ('function' === typeof title)
- title = _.bind(title, FlowRouter.current())();
- var params = FlowRouter.current().params;
+ title = _.bind(title, {route: route, params: params, queryParams})();
if (title) {
for (var i in params) {
title = title && title.replace(
@@ -22,68 +40,104 @@ var enrichRouteObject = function(routeName, isCurrent) {
}
if (routeOptions.slug)
title = title.split(routeOptions.slug).join(' ');
- if (!getRouteByName(routeName).options.noCaps)
+
+ if (routeOptions.caps)
title = title && title.capitalize();
} else {
title = null;
}
- if(isCurrent) {
- cssClasses = 'active';
- } else {
- cssClasses = '';
- }
-
- if (title) return {
- 'routeName': routeName,
- 'params': params,
- 'title': title,
- 'cssClasses': cssClasses,
- 'url': FlowRouter.path(routeName,FlowRouter.current().params,FlowRouter.current().queryParams),
- 'route': getRouteByName(routeName)
- }
+ //if (title)
+ return {
+ 'routeName': route.name,
+ 'params': params,
+ 'queryParams': queryParams,
+ 'title': title || null,
+ 'cssClasses': routeOptions.breadcrumbCSS || '',
+ 'url': FlowRouter.path(route.name, params, queryParams),
+ 'route': route
+ };
}
-var getAllParents = function() {
- if(FlowRouter.current().route) {
- var current = FlowRouter.current().route.name;
- var parent = getRouteByName(FlowRouter.current().route.name).options.parent;
- if ('function' === typeof parent)
- parent = _.bind(parent, FlowRouter.current())()
+var repairStack = function() {
+ currentStack = []; //[0] is root
+ var route = FlowRouter.current().route;
- if(parent) {
- return getParentParent([enrichRouteObject(current,true),enrichRouteObject(parent)]);
+ currentStack.push({route: route, routeName: route.name, params: FlowRouter.current().params, queryParams: FlowRouter.current().queryParams});
+ if(Breadcrumb.debug) console.log("Breadcrumb.currentStack",currentStack[0]);
+
+ while((parent = route.options.parent) != null) {
+ var prev = currentStack[0 /*currentStack.length-1*/];
+
+ if('string' == typeof(parent)) {
+ var route = getRouteByName(parent);
+ currentStack.unshift({
+ routeName: parent,
+ route: route,
+ params: prev.params,
+ queryParams: prev.queryParams
+ });
} else {
- return [enrichRouteObject(current)];
+ // the parameters for the parent are specified in the child's options.parent
+ var parent = _.bind(parent, prev)();
+ var route = getRouteByName(parent.name);
+ currentStack.unshift({
+ routeName: parent.name,
+ route: route,
+ params: parent.params,
+ queryParams: parent.queryParams
+ });
}
- } else {
- // no routes have been specified
- return [];
+ if(Breadcrumb.debug) console.log("Breadcrumb.currentStack",currentStack[0]);
+ } // while
+
+ function subset(childSet, parentSet ) {
+ for(k in parentSet) {
+ if(! _.isEqual(parentSet[k], childSet[k]))
+ return false
+ }
+ return true;
}
-}
+ // Do the comparison with the stored stack
+ if(Breadcrumb._stack.length > currentStack.length)
+ Breadcrumb._stack.length = currentStack.length;
+
+ var index;
+ // Keeping going while the route and params/queryparams are the same
+ for(index = 0; index < currentStack.length && index < Breadcrumb._stack.length; ++index) {
+ var curr = currentStack[index]; var prev = Breadcrumb._stack[index];
-// parents must be always an array
-var getParentParent = function(parents) {
- var lastParent = parents[parents.length-1];
- if(newParent = (lastParent && getRouteByName(lastParent.routeName).options.parent)) {
- if ('function' === typeof newParent) {
- newParent = _.bind(newParent, FlowRouter.current())()
+ if(curr.routeName != prev.routeName || !_.isEqualPicked(curr.params, prev.params, getParams(curr.route.path)) || ! _.isEqual(curr.queryParams, prev.queryParams)) {
+ if(Breadcrumb.debug) console.log("Breadcrumb.Mismatch", curr, prev);
+ break;
}
- parents.push(enrichRouteObject(newParent))
- return getParentParent(parents);
- } else {
- return parents;
}
+ // Rebuild everything after the last match
+ for(; index < currentStack.length; ++index) {
+ var currSpec = currentStack[index];
+ if(Breadcrumb.debug) console.log("Breadcrumb.Evaluate", currSpec);
+ Breadcrumb._stack[index] = enrichRouteObject(currSpec.route, currSpec.params, currSpec.queryParams);
+ }
+
+ for(i = 0; i < Breadcrumb._stack.length; ++i)
+ Breadcrumb._stack[i].active = (i == Breadcrumb._stack.length-1);
+
+ return Breadcrumb._stack;
}
Breadcrumb = {
+ debug:false,
+ _stack: [],
getAll: function() {
FlowRouter.watchPathChange();
- return _.compact(getAllParents()).reverse();
- },
+ return repairStack();
+ }
};
UI.registerHelper('Breadcrumb', function(template) {
- return Breadcrumb.getAll();
+ return _.reject(Breadcrumb.getAll(), function(entry) { return entry.title == null; });
+});
+UI.registerHelper('BreadcrumbNonEmpty', function(template) {
+ return _.some(Breadcrumb.getAll(), function(entry) { return entry.title != null; });
});
diff --git a/package.js b/package.js
index 65bfbf4..401b20d 100644
--- a/package.js
+++ b/package.js
@@ -1,8 +1,8 @@
Package.describe({
- name: 'ahref:flow-router-breadcrumb',
- summary: 'This package will provide a easy way to add a breadcrumb to FlowRouter with enough flexibility.',
- version: '1.1.0',
- git: 'https://github.com/rfox90/meteor-breadcrumb-plugin/'
+ name: 'sojourneer:flow-router-breadcrumb',
+ summary: 'Provide a easy and flexible way to add breadcrumb trail support for FlowRouter.',
+ version: '1.2.0',
+ git: 'https://github.com/Sojourneer/meteor-breadcrumb-plugin.git'
});
function configurePackage(api) {