Skip to content
This repository has been archived by the owner on Dec 19, 2017. It is now read-only.

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
caseyWebb committed Jun 14, 2016
1 parent 4cf3175 commit a699749
Show file tree
Hide file tree
Showing 17 changed files with 553 additions and 608 deletions.
108 changes: 64 additions & 44 deletions dist/ko-component-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,23 +437,25 @@ return /******/ (function(modules) { // webpackBootstrap

var el = this.config.el.getElementsByClassName('component-wrapper')[0];
delete toCtx.query;
if (fromCtx.route.component === toCtx.route.component) {
(0, _utils.merge)(this, toCtx);
} else {
(0, _utils.extend)(this, toCtx);
}
if (query) {
this.query.update(query, pathname);
}
this.isNavigating(false);
_knockout2.default.tasks.runEarly();
resolve(true);

if (animate) {
_knockout2.default.tasks.schedule(function () {
return _this2.config.inTransition(el, fromCtx, toCtx);
});
}
toCtx.route.runPipeline(toCtx).then(function () {
if (fromCtx.route.component === toCtx.route.component) {
(0, _utils.merge)(_this2, toCtx);
} else {
(0, _utils.extend)(_this2, toCtx);
}
if (query) {
_this2.query.update(query, pathname);
}
_this2.isNavigating(false);
_knockout2.default.tasks.runEarly();
resolve(true);

if (animate) {
_knockout2.default.tasks.schedule(function () {
return _this2.config.inTransition(el, fromCtx, toCtx);
});
}
});
}
});
});
Expand All @@ -474,31 +476,7 @@ return /******/ (function(modules) { // webpackBootstrap
ctx = ctx.$child;
}

return run(callbacks);

function run(callbacks) {
return new Promise(function (resolve) {
if (callbacks.length === 0) {
return resolve(true);
}
var cb = callbacks.shift();
var recursiveResolve = function recursiveResolve() {
var shouldUpdate = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
return shouldUpdate ? run(callbacks).then(resolve) : resolve(false);
};

if (cb.length === 1) {
cb(recursiveResolve);
} else {
var v = cb();
if ((0, _utils.isUndefined)(v) || typeof v.then !== 'function') {
recursiveResolve(v);
} else {
v.then(recursiveResolve);
}
}
});
}
return (0, _utils.cascade)(callbacks);
}
}, {
key: 'getRouteForUrl',
Expand Down Expand Up @@ -1363,6 +1341,7 @@ return /******/ (function(modules) { // webpackBootstrap

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

exports.cascade = cascade;
exports.decodeURLEncodedURIComponent = decodeURLEncodedURIComponent;
exports.deepEquals = deepEquals;
exports.extend = extend;
Expand All @@ -1377,6 +1356,34 @@ return /******/ (function(modules) { // webpackBootstrap

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function cascade(callbacks) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}

return new Promise(function (resolve) {
if (callbacks.length === 0) {
return resolve(true);
}
var cb = callbacks.shift();
var recursiveResolve = function recursiveResolve() {
var shouldUpdate = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
return shouldUpdate ? cascade.apply(undefined, [callbacks].concat(args)).then(resolve) : resolve(false);
};

if (cb.length === args.length + 1) {
cb.apply(undefined, args.concat([recursiveResolve]));
} else {
var v = cb.apply(undefined, args);
if (isUndefined(v) || typeof v.then !== 'function') {
recursiveResolve(v);
} else {
v.then(recursiveResolve);
}
}
});
}

function decodeURLEncodedURIComponent(val) {
if (typeof val !== 'string') {
return val;
Expand Down Expand Up @@ -1658,7 +1665,7 @@ return /******/ (function(modules) { // webpackBootstrap
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Route = function () {
function Route(path, component) {
function Route(path, pipeline) {
_classCallCheck(this, Route);

if (path[path.length - 1] === '!') {
Expand All @@ -1667,7 +1674,15 @@ return /******/ (function(modules) { // webpackBootstrap
path = path.replace(/\(?\*\)?/, '(.*)');
}

this.component = component;
if (typeof pipeline === 'string') {
this.component = pipeline;
this.pipeline = [];
} else if (typeof pipeline[pipeline.length - 1] === 'string') {
this.component = pipeline.pop();
this.pipeline = pipeline;
} else {
this.pipeline = pipeline;
}

this._keys = [];
this._regexp = (0, _pathToRegexp2.default)(path, this._keys);
Expand Down Expand Up @@ -1727,6 +1742,11 @@ return /******/ (function(modules) { // webpackBootstrap

return [path, params, hash, pathname, querystring, childPath];
}
}, {
key: 'runPipeline',
value: function runPipeline(ctx) {
return (0, _utils.cascade)(this.pipeline, ctx);
}
}]);

return Route;
Expand Down
2 changes: 1 addition & 1 deletion dist/ko-component-router.min.js

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions docs/bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Bindings

The router is supplied with several bindings to make your life a bit easier and your apps
a bit more powerful. The three bindings correlate to the `path`, `state`, and `query` arguments
of `ctx.update`. These bindings use that function behind the scenes, so it should come as no
surprise that they support the same bubbling behavior, however you can [lock the context](#locking-to-a-context)
if necessary.

## Usage

```html
<div data-bind="path: '/user', state: { user: user }, query: { search: 'foo' }"></div>
```

#### Locking to a Context

In certain cases, the default route bubbling behavior may not be quite right,
and in those cases you need to nudge the router in the right direction. You can accomplish
that with `..`, `./`, `//` following idiomatic path modifiers.

`..` forces the router to bubble to the parent router. These can be chained, i.e. `../../my-route`.

`./` forces the router to evaluate the route at the current context. This can be used to force a
404 route in a child router.

`//` forces top-down traversal rather than bottom-up. For all intents and purposes this can be regarded
as a "absolute path" prefix with respect to the base path.

#### Styling

For convenience, elements with path bindings that match the current route have the `active-path` class
added.

#### Special Notes

##### Use Anchors

While the bindings rely on the `click` binding so you can functionally attach them to any element you choose,
if you wish to preserve ctrl+click, context menu (open in new tab/window, etc.), and other native browser UX
you must still use anchor tags. The path binding will set an `href` property for these behaviors to fall back on.

##### Passing Data

It may not seem immediately intuitive, but the state binding can be quite handy for passing data around in apps
without a full blown model/collection layer. Imagine the following situation.

- A `user-list` component with a list of users
- A `user-show` component that lists individual user information

If this data lives on a server it makes sense you would want to avoid having to refetch data.
Assuming the `user-list` viewmodel loads an array of `user` objects with the same schema the
`user-show` component uses to render, one way to do this could be to add the following binding
the anchor tags in the `user-list` component...

```html
<!-- ko foreach: { data: users, as: 'user' } -->
<a data-bind="path: '/users/' + user.id, state: { user: user }, text: user.name"></a>
<!-- /ko -->
```

And the following in the `user-show` viewmodel...

```javascript
class ViewModel {
constructor(ctx) {
this.user = ctx.state().user
}
}
```

##### Unit Testing

The bindings expect to find a `$router` object containing the router on the binding context and will throw an
error if it is not found.

To remedy this you can stub the router with the files in the `lib` directory — these are the source
files babel-ified and exported for commonjs, i.e. webpack/browserify/etc.

Consult the following example...

```javascript
import ko from 'knockout'
import RouterContext from 'ko-component-router/lib/context'
// const RouterContext = require('ko-component-router/lib/context').default in a commonjs environment
import 'ko-component-router/lib/binding'
import { renderComponent } from 'ko-component-tester'
import parentRouterRoutes from '../path/to/routes'
import routes from './path/to/routes'

// this assumes you are using webpack or a similar tool
// to write your templates in HTML. Adjust accordingly.
import template from './path/to/template.html'

const parentRouter = new RouterContext({}, { routes: parentRouterRoutes, base: '/root' })
const $router = new RouterContext({ $parentContext: { $router: parentRouter } }, { routes })

describe('<app-nav/>', () => {
const $el = renderComponent({ template }, { params }, { $router })

it('has home link', () => expect($el.find('a[href=/root/instructor]')).to.exist)
})
```

Alternatively you could delete `path`, `state`, and `query` from `ko.bindingHandlers` before rendering.

_Related:_
- [ko-component-tester#renderComponent](https://github.com/Profiscience/ko-component-tester#rendercomponentcomponent-params-bindingcontext)
96 changes: 96 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Config

These are the options available to pass to the `<ko-component-router></ko-component-router>` component

## routes

`routes` is an object where the keys are [express style](https://github.com/pillarjs/path-to-regexp) routes and
values are the array `[...callbacks, 'component-name']` or just `component-name` for shorthand. `...callbacks` are
functions that are called with the router context and an optional `done` callback and will be called sequentially
before setting the router's view to `'component-name'`. If callbacks aren't your style, you can also use promises.

```javascript
const routes = {
// explicit path
'/about': 'about',

// one required param (\`name\`)
// one optional param (\`operation\`)
'/user/:name/:operation?': [
getUser,
'user'
],

// wildcard segment
'/*': '404',

// named wildcard segment
'/file/:file(*)': 'file'
}

function getUser(ctx /*, done */) {
return new Promise((resolve) => {
$.get(`/API/Users/${ctx.params.name}`).then((u) => {
ctx.state.user = u
resolve()
})
})
}
```

The callbacks can also be used for dynamic routing by setting `ctx.route.component`

```javascript
const routes = {
'/user/:name/:operation?': [getComponent]
}

function getComponent(ctx) {
if (ctx.params.operation === 'edit') {
ctx.route.component = 'user-edit'
} else {
ctx.route.component = 'user-show'
}
}
```

## base

The base path your app is running under, if applicable.
e.g., your app is running from a \`/blog\` directory

__This option is only applicable to the top-level router__

## hashbang

Whether or not to use HTML4 hashbang routing. Defaults to false.

When using with legacy browsers that do not support the <code>history</code>
API, you should include the <a href="https://github.com/devote/HTML5-History-API">HTML5-History-API polyfill</a>
with the following one-liner.

```html
<!--[if lte IE 9]><script src="https://cdnjs.cloudflare.com/ajax/libs/html5-history-api/4.0.2/history.iegte8.min.js?type=!/&basepath=<basepath>"></script><![endif]-->
```

__This option is only applicable to the top-level router__

## persistQuery

Whether or not to preserve the querystring when navigating between pages. Defaults to false.

Note, when the router is unmounted the queries will be disposed.

## persistState

Whether or not to preserve the querystring when navigating between pages. defaults to false.

Note, when the router is unmounted the states will be disposed.

## inTransition: (el, fromCtx, toCtx) => {}

Defines a function to run immediately after mounting a component

## outTransition: (el, fromCtx, toCtx[, done]) => {}

Defines a function to run immediately before unmounting a component

0 comments on commit a699749

Please sign in to comment.