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.1.0 to 0.2.2

110

cherrytree-for-knockout.js

@@ -5,6 +5,4 @@ (function(factory) {

} else if (typeof exports === 'object' && typeof module === 'object') {
/*global module*/
module.exports = factory
} else {
/*global ko*/
var middleware = factory(ko)

@@ -14,3 +12,3 @@ ko.bindingHandlers.routeView.middleware = middleware

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

@@ -27,7 +25,7 @@ ko.components.register('route-blank', {

function clone(obj) {
return Object.keys(obj).reduce(function(clone, key) {
clone[key] = obj[key]
return clone
}, {})
function extend(target, obj) {
return Object.keys(obj).reduce(function(t, key) {
t[key] = obj[key]
return t
}, target)
}

@@ -54,9 +52,13 @@

init: function(_, valueAccessor, __, ___, bindingContext) {
var router = valueAccessor()
if (router && typeof router.map === 'function' && typeof router.use === 'function') {
var r = valueAccessor()
if (r && typeof r.map === 'function' && typeof r.use === 'function') {
router = r
if (!bindingContext.$root.router) {
bindingContext.$root.router = router
bindingContext.$root.router = r
}
if (!bindingContext.$root.activeRoutes) {
bindingContext.$root.activeRoutes = activeRoutes
bindingContext.$leafRoute = function() {
return activeRoutes()[activeRoutes().length - 1]
}
}

@@ -92,5 +94,10 @@ }

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

@@ -121,3 +128,2 @@ routeComponent({ name: ko.bindingHandlers.routeView.prefix + route.name, params: params })

update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var router = bindingContext.$root.router
if (!router) {

@@ -128,3 +134,3 @@ 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
var opts = ko.unwrap(valueAccessor()), name, params, query
if (typeof opts === 'string') {

@@ -135,2 +141,9 @@ name = opts

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

@@ -141,3 +154,4 @@

name || bindingContext.$route.name,
params || bindingContext.$route.params)
params || bindingContext.$route.params,
query)
}

@@ -148,2 +162,42 @@ }, allBindings, viewModel, bindingContext)

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 : {}))
if (transitioning) return
if (transitioning !== false) {
transitioning = false
return
}
var url = router.location.getURL(),
stringified = router.options.qs.stringify(query)
router.location.setURL(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)
}
})
}
}
function routeEqual(comp, route) {

@@ -162,6 +216,7 @@ if (!comp || !route || comp.name !== route.name) return false

})
transitioning = true // router.state.activeTransition isn't set to this one yet
while (routeEqual(activeRoutes()[startIdx], filteredRoutes[startIdx])) {
Object.assign(resolutions, activeRoutes()[startIdx].resolutions())
updateQueryParams(activeRoutes()[startIdx], transition.query)
startIdx++

@@ -180,2 +235,3 @@ }

}
var compName = ko.bindingHandlers.routeView.prefix + routeData.name

@@ -185,2 +241,21 @@ if (!ko.components.isRegistered(compName)) {

}
var query = route.options.query
if (query) {
routeData.queryParams = Object.keys(query).reduce(function(q, 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
} else {
if (queryVal) {
q[key] = ko.observableArray(Array.isArray(queryVal) ? queryVal : [queryVal])
} else {
q[key] = ko.observableArray(defaultVal)
}
q[key].default = defaultVal.splice()
}
return q
}, {})
}
}

@@ -211,2 +286,3 @@

activeRoutes.splice.apply(activeRoutes, [startIdx, activeRoutes().length - startIdx].concat(newRoutes))
transitioning = false

@@ -213,0 +289,0 @@ return routeResolvers.reduce(function(promise, then) {

3

package.json
{
"name": "cherrytree-for-knockout",
"version": "0.1.0",
"version": "0.2.2",
"description": "Use knockout components with CherryTree hiearchial routing",

@@ -43,2 +43,3 @@ "main": "cherrytree-for-knockout.js",

"phantomjs": "1.9.7-15",
"qs": "^6.0.0",
"sinon-browser-only": "^1.12.1",

@@ -45,0 +46,0 @@ "sinon-chai": "^2.8.0"

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

},
template: '<form class="login"><input name="username" data-bind="value: username></input> .... </form>'
template: '<form class="login"><input name="username" data-bind="value: username"></input> .... </form>'
}

@@ -49,3 +49,3 @@

```
```html
<body>

@@ -74,1 +74,22 @@ <header>

### Two-way binding of Query Parameters
Keeping all your view state in the query parameter allows users to always refresh the page and get back right where they are at, and share links to other people to see exactly what they are seeing. cherrytree-for-knockout will let you bind to query string parameters easily to support this by giving you an observable that reflects the query string, including defaults.
```javascript
var inbox = {
path: 'inbox',
query: {
sort: 'desc'
},
viewModel: function(params) {
this.sort = params.sort
this.toggleSort = () => params.sort(params.sort() === 'asc' ? 'desc' : 'asc')
}
template: '<div class="inbox">\
<a class="sort" data-bind="click: toggleSort, text: sort"></a>\
</div>'
}
```
When `a.sort` is clicked, the URL becomes `/inbox?sort=desc`. When clicked again, it becomes `/inbox` as sort gets set back to it's default.
describe('CherryTree for Knockout', function() {
var router, location, testEl, forums, forum, thread, login, hrefTest, goToRoute
var router, location, testEl, forums, forum, thread, login, inboxParams, goToRoute
beforeEach(function() {
router = cherrytree({ location: 'memory' })
router = cherrytree({ location: 'memory', qs: Qs })
router.use(ko.bindingHandlers.routeView.middleware)

@@ -43,13 +43,2 @@

hrefTest = {
synchronous: true,
path: 'href-test/:someparam',
viewModel: function() {
return { goToRoute: goToRoute }
},
template: '<nav class="href-test">\
<a data-bind="routeHref: goToRoute"></a>\
</nav>'
}
router.map(function(route) {

@@ -62,3 +51,30 @@ route('login', login)

})
route('router-href-test', hrefTest)
route('router-href-test', {
synchronous: true,
path: 'href-test/:someparam',
query: {
foo: undefined,
},
viewModel: function() {
return { goToRoute: goToRoute }
},
template: '<nav class="href-test">\
<a data-bind="routeHref: goToRoute"></a>\
</nav>'
})
route('inbox', {
synchronous: true,
query: {
sort: 'desc',
search: undefined,
tags: []
},
viewModel: function(params) {
return inboxParams = params
},
template: '<div class="inbox">\
<input data-bind="textInput: search" />\
<a class="sort" data-bind="click: function() { sort(sort() === \'asc\' ? \'desc\' : \'asc\') }, text: sort"></a>\
</div>'
})
})

@@ -101,3 +117,3 @@

}
}
}
setTimeout(attempt, 2)

@@ -202,2 +218,3 @@

should.not.exist(activeRoutes()[0])
return pollUntilPassing(function() {

@@ -217,2 +234,20 @@ activeRoutes().map(function(r) { return r.name }).should.deep.equal(['forums', 'threads'])

it('should expose $leafRoute() on the bindingContext for easy access to the deepest route', function() {
location.setURL('/forums/1')
ko.contextFor(testEl).$leafRoute.should.be.instanceof(Function)
should.not.exist(ko.contextFor(testEl).$leafRoute())
return pollUntilPassing(function() {
ko.contextFor(testEl).$leafRoute().name.should.equal('threads')
ko.contextFor(testEl.querySelector('section.forums h1')).$leafRoute().name.should.equal('threads')
}).then(function() {
location.setURL('/forums/1/threads/2')
return pollUntilPassing(function() {
ko.contextFor(testEl).$leafRoute().name.should.equal('thread')
ko.contextFor(testEl.querySelector('section.forums h1')).$leafRoute().name.should.equal('thread')
ko.contextFor(testEl.querySelector('section.thread p')).$leafRoute().name.should.equal('thread')
})
})
})
it('should expose the route component as $routeComponent', function() {

@@ -267,2 +302,17 @@ ko.components.register('some-component', {

})
it('should not rerender anything when only the query string changes', function() {
location.setURL('/forums/1')
return pollUntilPassing(function() {
testEl.querySelector('section.forums section.forum').should.be.ok
}).then(function() {
testEl.querySelector('section.forums section.forum').foo = 'bar'
location.setURL('/forums/1?hello=world')
return new Promise(function(res) {
setTimeout(function() { res() }, 100)
})
}).then(function() {
testEl.querySelector('section.forums section.forum').should.have.property('foo')
})
})
})

@@ -274,3 +324,3 @@

location.setURL('/href-test/foobar')
location.setURL('/href-test/foobar?foo=bar')
return pollUntilPassing(function() {

@@ -317,2 +367,32 @@ testEl.querySelector('.href-test').should.be.ok

})
it('should include the query bounded observables if true', function() {
goToRoute({
name: 'thread',
params: {
forumId: 2,
threadId: 3
},
query: true
})
testEl.querySelector('.href-test a').should.have.attr('href', '/forums/2/threads/3?foo=bar')
location.setURL('/href-test/foobar?foo=1')
return pollUntilPassing(function() {
testEl.querySelector('.href-test a').should.have.attr('href', '/forums/2/threads/3?foo=1')
})
})
it('should accept an explicit object, overwriting query values', function() {
goToRoute({
params: {
someparam: 'baz'
},
query: {
foo: 1,
somequery: 'a'
}
})
testEl.querySelector('.href-test a').should.have.attr('href', '/href-test/baz?foo=1&somequery=a')
})
})

@@ -569,2 +649,103 @@

})
describe('query', function() {
it('should provide an object with observables behind properties defaulting to the specified values on params', function() {
location.setURL('/inbox')
return pollUntilPassing(function() { inboxParams.sort }).then(function() {
ko.isWritableObservable(inboxParams.sort).should.be.true
ko.isWritableObservable(inboxParams.search).should.be.true
ko.isWritableObservable(inboxParams.tags).should.be.true
inboxParams.tags.push.should.be.instanceof(Function)
inboxParams.sort().should.equal('desc')
chai.expect(inboxParams.search()).to.equal(undefined)
inboxParams.tags().should.be.empty
})
})
it('should set values from the query string as those property\'s initial value', function() {
location.setURL('/inbox?sort=asc&search=%20Hi&tags[]=suggestion')
return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
testEl.querySelector('.inbox a.sort').should.have.text('asc')
testEl.querySelector('.inbox input').value.should.equal(' Hi')
inboxParams.tags().should.deep.equal(['suggestion'])
})
})
it('should replace the current location history when an observable changes, preserving other querystring values', function() {
location.setURL('/inbox?foo=bar&search=bob')
return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
var sort = testEl.querySelector('.inbox a.sort')
if (typeof sort.click === 'function') {
sort.click()
} else {
inboxParams.sort('asc')
}
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=bar&search=bob&sort=asc')
testEl.querySelector('.inbox a.sort').should.have.text('asc')
})
}).then(function() {
inboxParams.search('Jane')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=bar&search=Jane&sort=asc')
testEl.querySelector('.inbox input').value.should.equal('Jane')
})
})
})
it('should remove the query string if it becomes the default', function() {
location.setURL('/inbox?sort=asc')
return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
inboxParams.sort('desc')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox')
})
}).then(function() {
inboxParams.tags.push('unread')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?tags%5B0%5D=unread')
})
})
})
it('should expand array items out in the query string properly when an observableArray updates', function() {
location.setURL('/inbox?foo=bar&search=bob')
return pollUntilPassing(function() { testEl.querySelector('.inbox a.sort').click }).then(function() {
inboxParams.tags(['unread'])
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=bar&search=bob&tags%5B0%5D=unread')
})
}).then(function() {
inboxParams.tags.push('priority')
return pollUntilPassing(function() {
location.getURL().should.equal('/inbox?foo=bar&search=bob&tags%5B0%5D=unread&tags%5B1%5D=priority')
})
})
})
it('should update the observables if the query string changes at the same location', 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')
testEl.querySelector('.inbox input').value.should.equal('bob')
location.setURL('/inbox?foo=baz&search=Jane&tags=unread&tags=priority')
return pollUntilPassing(function() {
testEl.querySelector('.inbox input').value.should.equal('Jane')
testEl.querySelector('.inbox a.sort').should.have.text('desc')
inboxParams.tags().should.deep.equal(['unread', 'priority'])
location.getURL().should.equal('/inbox?foo=baz&search=Jane&tags=unread&tags=priority')
})
}).then(function() {
location.setURL('/inbox')
return pollUntilPassing(function() {
testEl.querySelector('.inbox input').value.should.equal('')
testEl.querySelector('.inbox a.sort').should.have.text('desc')
inboxParams.tags().should.be.empty
location.getURL().should.equal('/inbox')
})
})
})
})
})

Sorry, the diff of this file is not supported yet

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