Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cherrytree-for-knockout

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cherrytree-for-knockout - npm Package Compare versions

Comparing version 0.0.2 to 0.1.0

.travis.yml

5

bower.json

@@ -31,3 +31,6 @@ {

"tests"
]
],
"devDependencies": {
"chai-dom": "~1.0.0"
}
}

181

cherrytree-for-knockout.js

@@ -10,3 +10,3 @@ (function(factory) {

var middleware = factory(ko)
ko.bindingHandlers.routeComponent.middleware = middleware
ko.bindingHandlers.routeView.middleware = middleware
}

@@ -17,17 +17,54 @@ })(function(ko) {

ko.components.register('route-blank', {
viewModel: { instance: {} },
template: '<div></div>'
template: '<div></div>',
synchronous: true
})
ko.components.register('route-loading', {
viewModel: { instance: {} },
template: '<div class="route-loading"></div>'
template: '<div class="route-loading"></div>',
synchronous: true
})
ko.bindingHandlers.routeComponent = {
init: function() {
function clone(obj) {
return Object.keys(obj).reduce(function(clone, key) {
clone[key] = obj[key]
return clone
}, {})
}
var origCreatChildContext = ko.bindingContext.prototype.createChildContext
// a bit of a hack, but since the component binding instantiates the component,
// likely async too, and no way to walk down binding contexts.
// we are extending the component context
ko.bindingContext.prototype.createChildContext = function(dataItemOrAccessor, dataItemAlias, extendCallback) {
return origCreatChildContext.call(this, dataItemOrAccessor, dataItemAlias, function(ctx) {
var retval = typeof extendCallback === 'function' && extendCallback(ctx)
if (ctx && ctx.$parentContext && ctx.$parentContext._routeCtx) {
delete ctx.$parentContext._routeCtx
delete ctx._routeCtx
ctx.$routeComponent = dataItemOrAccessor
}
return retval
})
}
ko.bindingHandlers.routeView = {
init: function(_, valueAccessor, __, ___, bindingContext) {
var router = valueAccessor()
if (router && typeof router.map === 'function' && typeof router.use === 'function') {
if (!bindingContext.$root.router) {
bindingContext.$root.router = router
}
if (!bindingContext.$root.activeRoutes) {
bindingContext.$root.activeRoutes = activeRoutes
}
}
return { controlsDescendantBindings: true }
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var depth = 0, contextIter = bindingContext.$parentContext
update: function(element, valueAccessor, ab, vm, bindingContext) {
var depth = 0, contextIter = bindingContext.$parentContext,
routeComponent = ko.observable({ name: 'route-blank' }),
prevRoute, routeClass
while (contextIter) {

@@ -40,36 +77,105 @@ if ('$route' in contextIter) {

var route = activeRoutes()[depth] || { name: 'route-blank' }
ko.computed(function() {
var route = activeRoutes()[depth]
if (route == prevRoute) return
if (!route) {
routeComponent({ name: 'route-blank' })
return
}
bindingContext.$route = route
bindingContext.$route = route
bindingContext._routeCtx = true
return ko.bindingHandlers.component.init(element, function() {
if (route.resolutions) {
return route.resolutions() ?
{ name: route.name, params: route.resolutions() } :
{ name: 'route-loading' }
var res = route.resolutions()
if (res) {
var params = clone(res)
params.$route = clone(route)
delete params.$route.resolutions
prevRoute = route
routeComponent({ name: ko.bindingHandlers.routeView.prefix + route.name, params: params })
} else {
return { name: route.name }
routeComponent({ name: 'route-loading' })
}
}, null, { disposeWhenNodeIsRemoved: element }).extend({ rateLimit: 5 })
ko.computed(function() {
var newClass = routeComponent().name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()
if (newClass === routeClass) return
if (routeClass) {
element.classList.remove(routeClass)
}
routeClass = newClass
element.classList.add(routeClass)
}, null, { disposeWhenNodeIsRemoved: element })
return ko.bindingHandlers.component.init(element, routeComponent, ab, vm, bindingContext)
}
}
ko.bindingHandlers.routeView.prefix = 'route:'
ko.bindingHandlers.routeHref = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var router = bindingContext.$root.router
if (!router) {
throw new Error('No router found on the root binding context. Make sure to initialize the toplevel routeView with your router as the option.')
}
return ko.bindingHandlers.attr.update(element, function() {
var opts = ko.unwrap(valueAccessor()), name, params
if (typeof opts === 'string') {
name = opts
} else if (opts) {
name = ko.unwrap(opts.name)
params = ko.unwrap(opts.params)
}
return {
href: opts && router.generate(
name || bindingContext.$route.name,
params || bindingContext.$route.params)
}
}, allBindings, viewModel, bindingContext)
}
}
ko.bindingHandlers.routeComponent.prefix = 'route:'
function routeEqual(comp, route) {
if (!comp || !route || comp.name !== route.name) return false
return Object.keys(route.params).every(function(param) {
return comp.params[param] === route.params[param]
})
}
return function knockoutCherrytreeMiddleware(transition) {
var resolutions = {}, routeResolvers = []
activeRoutes(transition.routes.filter(function(route) {
return route.options && route.options.template
}).map(function(route) {
var routeData = {
name: ko.bindingHandlers.routeComponent.prefix + route.ancestors.concat([route.name]).join('.'),
params: transition.params,
query: transition.query,
resolutions: route.options.resolve && ko.observable()
var resolutions = {}, routeResolvers = [], startIdx = 0,
filteredRoutes = transition.routes.filter(function(route) {
return route.options && !!(route.options.template || route.options.resolve)
})
while (routeEqual(activeRoutes()[startIdx], filteredRoutes[startIdx])) {
Object.assign(resolutions, activeRoutes()[startIdx].resolutions())
startIdx++
}
var newRoutes = filteredRoutes.slice(startIdx).map(function(route) {
var routeData
if (route.options.template) {
routeData = {
name: route.name,
params: transition.params,
query: transition.query,
resolutions: ko.observable(),
transitionTo: transition.redirectTo
}
var compName = ko.bindingHandlers.routeView.prefix + routeData.name
if (!ko.components.isRegistered(compName)) {
ko.components.register(compName, route.options)
}
}
if (!ko.components.isRegistered(routeData.name)) {
ko.components.register(routeData.name, route.options)
}
if (route.options.resolve) {
var resolvers = Object.keys(route.options.resolve)
var resolve = route.options.resolve
if (resolve || routeResolvers.length) {
var resolvers = Object.keys(resolve || {})

@@ -80,13 +186,18 @@ routeResolvers.push(function() {

})).then(function(moreResolutions) {
routeData.resolutions(moreResolutions.reduce(function(all, r, idx) {
moreResolutions.reduce(function(all, r, idx) {
all[resolvers[idx]] = r
return all
}, resolutions))
}, resolutions)
routeData && routeData.resolutions(resolutions)
return routeData
})
})
} else if (routeData) {
routeData.resolutions(resolutions)
}
return routeData
}))
}).filter(function(i) { return !!i })
activeRoutes.splice.apply(activeRoutes, [startIdx, activeRoutes().length - startIdx].concat(newRoutes))
return routeResolvers.reduce(function(promise, then) {

@@ -93,0 +204,0 @@ return promise ? promise.then(then) : then()

{
"name": "cherrytree-for-knockout",
"version": "0.0.2",
"version": "0.1.0",
"description": "Use knockout components with CherryTree hiearchial routing",
"main": "cherrytree-for-knockout.js",
"scripts": {
"test": "mocha-phantomjs test/tests.html"
"test": "mocha-phantomjs tests/tests.html && (node -e \"require('grunt').tasks(['test']);\" || true)"
},

@@ -28,17 +28,20 @@ "repository": {

"homepage": "https://github.com/nathanboktae/cherrytree-for-knockout#readme",
"peerDependencies": {
"dependencies": {
"knockout": "^3.3.0"
},
"devDependencies": {
"babel-core": "^5.8.23",
"chai": "^3.2.0",
"chai-jquery": "^2.0.0",
"cherrytree": "github:QubitProducts/cherrytree",
"jquery": "^2.1.4",
"knockout": "^3.3.0",
"chai-dom": "^1.2.1",
"cherrytree": "^2.0.0-rc3",
"grunt": "^0.4.5",
"grunt-connect": "^0.2.0",
"grunt-contrib-connect": "^0.11.2",
"grunt-saucelabs": "^8.6.1",
"mocha": "^2.2.5",
"mocha-phantomjs": "^3.6.0",
"phantomjs": "^1.9.7-15",
"sinon": "^1.15.4",
"phantomjs": "1.9.7-15",
"sinon-browser-only": "^1.12.1",
"sinon-chai": "^2.8.0"
}
}

@@ -5,2 +5,68 @@ ## CherryTree for Knockout

Inspired heavily by angular-ui-router. In early alpha development - see the tests for functionality.
[![Build Status](https://secure.travis-ci.org/nathanboktae/cherrytree-for-knockout.png?branch=master)](https://travis-ci.org/nathanboktae/cherrytree-for-knockout)
[![SauceLabs Test Status](https://saucelabs.com/browser-matrix/Cherrytree-ko.svg)](https://saucelabs.com/u/Cherrytree-ko)
### Overview
As you design your webapp, you will begin to identify workflows and pages in a heirachial fashion. Given the familiar forum domain, you will have a list of forums, then a list of thread in a specific form, then posts in that forum. You may also have an account page which has a private messages section. Each section will have it's own view and logic, and may need data loaded before it can be reached.
cherrytree-for-knockout helps you with all that legwork. You associate components with routes that will load and display where you want in the page (you define that, and anything outside the component for the route, like a breadcrumb bar, account dropdown that is on every page, etc is fully in your control). You can even specify data you need (any function that returns a promise) that will be provided to your component before initializes.
cherrytree-for-knockout is extremely lightweight in the microlib spirit at < 200 lines of code. It has one job and does it well.
### Example
Specify your components when you map your routes like so:
```javascript
var login = {
viewModel: function() {
this.username = ko.observable()
// ....
},
template: '<form class="login"><input name="username" data-bind="value: username></input> .... </form>'
}
var forums = { /* ... */ }
router.map(function(route) {
route('login', login)
route('forums.index', forums)
route('forums', forums, function() {
route('forums.view', forum)
route('threads', forum, function() {
route('thread', thread)
})
})
})
```
Notice that you do not have to explicitly register the component via `ko.components.register` - cherrytree-for-knockout will do that for you.
Now for the HTML:
```
<body>
<header>
<ul data-bind="foreach: route.state && route.state.routes">
<li data-bind="routeHref: name, text: name"></li>
</ul>
<a class="signout" data-bind="click: signout"></a>
</header>
<main data-bind="routeView: router"></main>
<script>
ko.applyBindings({
router: router,
signout: function() { /* ... */ }
})
</script>
</body>
```
Notice the `routeView` binding. This is where a component for a route will be rendered. In the top level `routeView` binding, you must provide the router instance. This will be available on the root view model as `router`. For nested `routeView`s, the parameter is currently ignored so `true` or `{}` will suffice.
Above `main` there is a header which creates bindings based on the current route state. cherrytree-for-knockout will back the `state` property behind an observable, so when the current route changes, depedencies will update, so we can have a simple breadcrumb in this example. `routeHref` is a binding handler that will set the `href` for the route you specify via `router.generate`
Below that is a signout button with a click handler, showing that cherrytree-for-knockout plugs into your existing app how you wish, and ultimately your are still in control of your application's layout and workflow.
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