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.2.2 to 0.3.2

130

cherrytree-for-knockout.js

@@ -11,3 +11,5 @@ (function(factory) {

})(function(ko) {
var activeRoutes = ko.observableArray(), transitioning, router
var transitioning, router,
activeRoutes = ko.observableArray(),
activeComponents = ko.observableArray()

@@ -41,4 +43,10 @@ ko.components.register('route-blank', {

delete ctx.$parentContext._routeCtx
ctx.$route = ctx._routeCtx
delete ctx._routeCtx
ctx.$routeComponent = dataItemOrAccessor
var idx = activeRoutes.peek().indexOf(ctx.$route)
if (idx > -1) {
activeComponents.splice(idx, 1, dataItemOrAccessor)
}
}

@@ -58,3 +66,3 @@ return retval

if (!bindingContext.$root.activeRoutes) {
bindingContext.$root.activeRoutes = activeRoutes
bindingContext.$root.activeRoutes = knockoutCherrytreeMiddleware.activeRoutes
bindingContext.$leafRoute = function() {

@@ -69,10 +77,8 @@ return activeRoutes()[activeRoutes().length - 1]

update: function(element, valueAccessor, ab, vm, bindingContext) {
var depth = 0, contextIter = bindingContext.$parentContext,
var depth = 0, contextIter = bindingContext,
routeComponent = ko.observable({ name: 'route-blank' }),
prevRoute, routeClass
while (contextIter) {
if ('$route' in contextIter) {
depth++
}
while (contextIter.$parentContext && contextIter.$routeComponent !== contextIter.$parentContext.$routeComponent) {
depth++
contextIter = contextIter.$parentContext

@@ -89,4 +95,3 @@ }

bindingContext.$route = route
bindingContext._routeCtx = true
bindingContext._routeCtx = route

@@ -98,7 +103,4 @@ var res = route.resolutions()

delete params.$route.resolutions
extend(params, route.queryParams)
if (route.queryParams) {
extend(params, route.queryParams)
}
prevRoute = route

@@ -127,2 +129,14 @@ routeComponent({ name: ko.bindingHandlers.routeView.prefix + route.name, params: params })

function mapQuery(queryParams, query) {
return Object.keys(queryParams).reduce(function(q, k) {
var val = queryParams[k]()
if (val !== queryParams[k].default) {
q[k] = val
} else {
delete q[k]
}
return q
}, query || {})
}
ko.bindingHandlers.routeHref = {

@@ -143,6 +157,3 @@ update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {

if (query === true) {
query = Object.keys(bindingContext.$route.queryParams).reduce(function(q, k) {
q[k] = ko.unwrap(bindingContext.$route.queryParams[k])
return q
}, {})
query = mapQuery(bindingContext.$route.queryParams)
}

@@ -162,17 +173,8 @@ }

ko.computed(function bindToQueryString() {
var routes = activeRoutes(),
query = routes.reduce(function(q, route) {
if (route.queryParams) {
Object.keys(route.queryParams).forEach(function(key) {
var val = route.queryParams[key]()
if (val !== route.queryParams[key].default) {
q[key] = val
} else {
delete q[key]
}
})
}
return q
}, extend({}, routes.length ? routes[routes.length - 1].query : {}))
var routes = activeRoutes()
if (!routes.length) return
var lastRoute = routes[routes.length - 1]
query = mapQuery(lastRoute.queryParams, extend({}, lastRoute.query))
if (transitioning) return

@@ -186,16 +188,14 @@ if (transitioning !== false) {

stringified = router.options.qs.stringify(query)
router.location.setURL(url.split('?')[0] + (stringified ? '?' + stringified : ''))
router.location.replaceURL(url.split('?')[0] + (stringified ? '?' + stringified : ''))
})
function updateQueryParams(route, query) {
if (route.queryParams) {
Object.keys(route.queryParams).forEach(function(key) {
var observable = route.queryParams[key]
if (key in query) {
observable(query[key])
} else {
observable(Array.isArray(observable.default) ? observable.default.slice() : observable.default)
}
})
}
Object.keys(route.queryParams).forEach(function(key) {
var observable = route.queryParams[key]
if (key in query) {
observable(query[key])
} else {
observable(Array.isArray(observable.default) ? observable.default.slice() : observable.default)
}
})
}

@@ -206,2 +206,4 @@

if ('resolutions' in comp && !comp.resolutions()) return false
return Object.keys(route.params).every(function(param) {

@@ -212,4 +214,4 @@ return comp.params[param] === route.params[param]

return function knockoutCherrytreeMiddleware(transition) {
var resolutions = {}, routeResolvers = [], startIdx = 0,
function knockoutCherrytreeMiddleware(transition) {
var resolutions = {}, routeResolvers = [], queryParams = {}, startIdx = 0,
filteredRoutes = transition.routes.filter(function(route) {

@@ -233,4 +235,9 @@ return route.options && !!(route.options.template || route.options.resolve)

query: transition.query,
queryParams: queryParams,
resolutions: ko.observable(),
transitionTo: transition.redirectTo
transitionTo: function(name, params, query) {
return query === true ?
transition.redirectTo(name, params, mapQuery(routeData.queryParams)) :
transition.redirectTo.apply(transition, arguments)
}
}

@@ -245,17 +252,18 @@

if (query) {
routeData.queryParams = Object.keys(query).reduce(function(q, key) {
Object.keys(query).forEach(function(key) {
var queryVal = routeData.query[key], defaultVal = query[key]
if (!Array.isArray(defaultVal)) {
q[key] = ko.observable(queryVal !== undefined ? queryVal : defaultVal)
q[key].default = defaultVal
queryParams[key] = ko.observable(queryVal !== undefined ? queryVal : defaultVal)
queryParams[key].default = defaultVal
} else {
if (queryVal) {
q[key] = ko.observableArray(Array.isArray(queryVal) ? queryVal : [queryVal])
queryParams[key] = ko.observableArray(Array.isArray(queryVal) ? queryVal : [queryVal])
} else {
q[key] = ko.observableArray(defaultVal)
queryParams[key] = ko.observableArray(defaultVal)
}
q[key].default = defaultVal.splice()
queryParams[key].default = defaultVal.splice()
}
return q
}, {})
})
queryParams = extend({}, queryParams)
}

@@ -286,2 +294,4 @@ }

// using a peek() to avoid an extra nofication
activeComponents.peek().splice(startIdx)
activeRoutes.splice.apply(activeRoutes, [startIdx, activeRoutes().length - startIdx].concat(newRoutes))

@@ -294,2 +304,16 @@ transitioning = false

}
knockoutCherrytreeMiddleware.activeRoutes = ko.pureComputed(function() {
return activeRoutes().map(function(r, idx) {
return {
name: r.name,
params: r.params,
query: r.query,
resolutions: r.resolutions,
component: activeComponents()[idx]
}
})
})
return knockoutCherrytreeMiddleware
})
{
"name": "cherrytree-for-knockout",
"version": "0.2.2",
"version": "0.3.2",
"description": "Use knockout components with CherryTree hiearchial routing",

@@ -34,4 +34,4 @@ "main": "cherrytree-for-knockout.js",

"chai": "^3.2.0",
"chai-dom": "^1.2.1",
"cherrytree": "^2.0.0-rc3",
"chai-dom": "^1.4.0",
"cherrytree": "^2.0.0",
"grunt": "^0.4.5",

@@ -38,0 +38,0 @@ "grunt-connect": "^0.2.0",

@@ -15,3 +15,3 @@ ## CherryTree for Knockout

cherrytree-for-knockout is extremely lightweight in the microlib spirit at < 200 lines of code. It has one job and does it well.
cherrytree-for-knockout is very lightweight, focused on one single responsibility, with under 350 lines of code. It has one job and does it well.

@@ -73,2 +73,6 @@ ### Example

As you work with your route components, you'll often want to refer to it in your view, which in simple cases using `$component` will meet your needs. However, as you begin to use more components nested within eachother, using `$parents` is cumbersome and fragile, `$routeComponent` is available and exposed to work as your replacement for `$root` in a traditional one uber view-model structure.
However `$routeComponent` is not supported with asyncrounous components. In general I recommend always declaring your components as syncrounous, as `bindingContext` was designed for the syncrounous binding world, and for symettry with standard bindings that always work syncronously.
### Two-way binding of Query Parameters

@@ -75,0 +79,0 @@

@@ -19,3 +19,5 @@ describe('CherryTree for Knockout', function() {

template: '<section class="forums"><h1>Viewing all forums</h1><div data-bind="routeView: true"></div></section>',
viewModel: function() {},
viewModel: function() {
this.forumsViewModel = true
},
synchronous: true

@@ -77,3 +79,14 @@ }

<a class="sort" data-bind="click: function() { sort(sort() === \'asc\' ? \'desc\' : \'asc\') }, text: sort"></a>\
<div data-bind="routeView: true"></div>\
</div>'
}, function() {
route('unread', {
template: '<p class="unread-tags" data-bind="text: \'Tagged \' + $route.queryParams.tags().join(\', \')"></p>'
})
route('compose', {
query: {
title: undefined,
},
template: '<form><input name="title" data-bind="value: $route.queryParams.title" /></form>'
})
})

@@ -205,2 +218,3 @@ })

},
queryParams: {},
query: {

@@ -213,19 +227,56 @@ unreadOnly: 'true'

it('should expose activeRoutes() with the current active route name, query, param, and resolutions', function() {
location.setURL('/forums/1')
it('should expose activeRoutes() with the current active route details, and the instance when it resolves', function() {
var activeRoutes = ko.contextFor(testEl).$root.activeRoutes
ko.isObservable(activeRoutes).should.be.true
ko.bindingHandlers.routeView.middleware.activeRoutes.should.equal(activeRoutes)
activeRoutes().should.be.empty
should.not.exist(activeRoutes()[0])
var snapshots = []
var sub = activeRoutes.subscribe(function(items) {
snapshots.push(items.slice())
})
thread.resolve = {
foo: function() {
return Promise.resolve('bar')
}
}
location.setURL('/forums/1')
return pollUntilPassing(function() {
activeRoutes().length.should.equal(2)
snapshots.length.should.equal(3)
snapshots.map(function(s) { return s.length }).should.deep.equal([2, 2, 2])
should.not.exist(snapshots[0][0].component)
should.not.exist(snapshots[0][1].component)
snapshots[1][0].component.forumsViewModel.should.be.true
should.not.exist(snapshots[1][1].component)
snapshots[2][0].component.forumsViewModel.should.be.true
snapshots[2][1].component.title.should.equal('Viewing forum {0}')
snapshots[0][0].should.contain.keys(['params', 'query', 'resolutions'])
snapshots[0][1].params.forumId.should.equal('1')
snapshots[0].map(function(r) { return r.name }).should.deep.equal(['forums', 'threads'])
activeRoutes().map(function(r) { return r.name }).should.deep.equal(['forums', 'threads'])
activeRoutes()[1].params.forumId.should.equal('1')
activeRoutes()[1].should.contain.keys(['params', 'query'])
snapshots[0][1].params.forumId.should.equal('1')
snapshots[0][1].should.contain.keys(['params', 'query'])
}).then(function() {
location.setURL('/forums/1/threads/2')
should.not.exist(activeRoutes()[2])
return pollUntilPassing(function() {
activeRoutes().map(function(r) { return r.name }).should.deep.equal(['forums', 'threads', 'thread'])
activeRoutes()[2].params.forumId.should.equal('1')
snapshots.length.should.equal(5)
snapshots[3].map(function(r) { return r.name }).should.deep.equal(['forums', 'threads', 'thread'])
snapshots[3][2].params.forumId.should.equal('1')
should.not.exist(snapshots[3][2].component)
snapshots[3][2].resolutions().should.be.ok
snapshots[4][2].component.title.should.equal('Viewing thread {0}')
snapshots[4][2].resolutions().foo.should.equal('bar')
})
}).then(function() {
sub.dispose()
})

@@ -236,7 +287,8 @@ })

location.setURL('/forums/1')
ko.contextFor(testEl).$leafRoute.should.be.instanceof(Function)
should.not.exist(ko.contextFor(testEl).$leafRoute())
var rootContext = ko.contextFor(testEl)
rootContext.$leafRoute.should.be.instanceof(Function)
should.not.exist(rootContext.$leafRoute())
return pollUntilPassing(function() {
ko.contextFor(testEl).$leafRoute().name.should.equal('threads')
rootContext.$leafRoute().name.should.equal('threads')
ko.contextFor(testEl.querySelector('section.forums h1')).$leafRoute().name.should.equal('threads')

@@ -246,3 +298,3 @@ }).then(function() {

return pollUntilPassing(function() {
ko.contextFor(testEl).$leafRoute().name.should.equal('thread')
rootContext.$leafRoute().name.should.equal('thread')
ko.contextFor(testEl.querySelector('section.forums h1')).$leafRoute().name.should.equal('thread')

@@ -254,2 +306,23 @@ ko.contextFor(testEl.querySelector('section.thread p')).$leafRoute().name.should.equal('thread')

it('should set $route to the current route, not a sibling or parent', function() {
location.setURL('/forums/1')
var rootContext = ko.contextFor(testEl)
should.not.exist(rootContext.$route)
return pollUntilPassing(function() {
should.not.exist(rootContext.$route)
ko.contextFor(testEl.querySelector('section.forums')).$route.name.should.equal('forums')
ko.contextFor(testEl.querySelector('section.forums > div')).$route.name.should.equal('forums')
}).then(function() {
location.setURL('/forums/1/threads/2')
return pollUntilPassing(function() {
should.not.exist(rootContext.$route)
ko.contextFor(testEl.querySelector('section.forums')).$route.name.should.equal('forums')
ko.contextFor(testEl.querySelector('section.forums > div')).$route.name.should.equal('forums')
ko.contextFor(testEl.querySelector('section.thread')).$route.name.should.equal('thread')
ko.contextFor(testEl.querySelector('section.thread h4')).$route.name.should.equal('thread')
})
})
})
it('should expose the route component as $routeComponent', function() {

@@ -319,2 +392,28 @@ ko.components.register('some-component', {

})
it('should rerender any component that did not complete its resolutions', function() {
forum.resolve = {
foo: function(transition) {
return (transition.params.threadId || 0) % 2 === 1 ?
transition.redirectTo('/forums/' + transition.params.forumId + '/threads/' + (transition.params.threadId - 1))
: 'bar'
}
}
location.setURL('/forums/1/threads/10')
return pollUntilPassing(function() {
testEl.querySelector('section.thread h4').should.contain.text('thread 10')
}).then(function() {
testEl.querySelector('section.forums section.forum').foo = 'bar'
location.setURL('/forums/2/threads/9')
return pollUntilPassing(function() {
testEl.querySelector('section.thread h4').should.contain.text('thread 8')
})
}).then(function() {
var forumEl = testEl.querySelector('section.forums section.forum')
forumEl.should.not.have.property('foo')
ko.contextFor(forumEl).$route.resolutions().foo.should.equal('bar')
})
})
})

@@ -461,3 +560,3 @@

testEl.querySelectorAll('section.forums').length.should.equal(0)
}).then(function() {
}).then(function() {
forumsDeferred.resolve([{ id: 1, name: 'Home forum' }])

@@ -678,2 +777,11 @@ return pollUntilPassing(function() {

return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
// unfortunately MemoryLocation#replaceURL calls setURL so we have to disambiguate that case
origSetURL = location.setURL.bind(location)
location.replaceURL = sinon.spy(function (path, options) {
if (location.path !== path) {
origSetURL(path, options)
}
})
sinon.spy(location, 'setURL')
var sort = testEl.querySelector('.inbox a.sort')

@@ -685,5 +793,8 @@ if (typeof sort.click === 'function') {

}
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=bar&search=bob&sort=asc')
testEl.querySelector('.inbox a.sort').should.have.text('asc')
location.replaceURL.should.have.been.calledOnce
location.setURL.should.have.not.been.called
})

@@ -752,3 +863,44 @@ }).then(function() {

})
it('should inherit query string parameters from parent routes', function() {
location.setURL('/inbox/unread?tags=promotion&tags=lastweek')
return pollUntilPassing(function() {
var p = testEl.querySelector('p.unread-tags')
p.should.have.text('Tagged promotion, lastweek')
ko.contextFor(p).$route.queryParams.sort().should.equal('desc')
})
})
it('should use the same observable instances for children, but not expose child queries to parents', function() {
location.setURL('/inbox/compose?foo=bar&title=Hello')
return pollUntilPassing(function() {
testEl.querySelector('input[name="title"]').value.should.equal('Hello')
}).then(function() {
ko.contextFor(testEl.querySelector('input[name="title"]')).$route.queryParams.sort('asc')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox/compose?foo=bar&title=Hello&sort=asc')
var sortToggle = testEl.querySelector('.inbox a.sort')
sortToggle.should.have.text('asc')
ko.contextFor(sortToggle).$route.queryParams.should.not.contain.key('title')
})
})
})
it('should provide a wrapper around transitionTo to easily include the current bound querystring parameters', function() {
location.setURL('/inbox?foo=bar&search=bob')
return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
testEl.querySelector('.inbox a.sort').should.have.text('desc')
location.setURL('/inbox?foo=baz&search=Jane&tags=unread&tags=priority')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=baz&search=Jane&tags=unread&tags=priority')
})
}).then(function() {
ko.contextFor(testEl.querySelector('a.sort')).$route.transitionTo('router-href-test', { someparam: 1 }, true)
return pollUntilPassing(function() {
location.getURL().should.equal('/href-test/1?search=Jane&tags=unread&tags=priority')
})
})
})
})
})
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