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

Commit

Permalink
Add sessionStorage and localStorage functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
caseyWebb committed Feb 1, 2017
1 parent 45366c7 commit 57e308a
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 60 deletions.
10 changes: 9 additions & 1 deletion README.md
Expand Up @@ -49,7 +49,15 @@ const query = new Query({
foo: {
default: 'foo',
initial: 'bar',
coerce: (v) => v === 'baz' ? 'qux' : v

// force value
coerce: (v) => v === 'baz' ? 'qux' : v,

// use sessionStorage
session: false,

// use localStorage
local: false
},

bar: 'bar'
Expand Down
5 changes: 3 additions & 2 deletions karma.conf.js
Expand Up @@ -52,7 +52,7 @@ module.exports = function(config) {
config.set({
basePath: '',

frameworks: ['tap'],
frameworks: ['tap', 'sinon'],

files: ['test.js'],

Expand Down Expand Up @@ -93,7 +93,8 @@ module.exports = function(config) {
},

module: {
preLoaders
preLoaders,
noparse: [/sinon/]
},

isparta: {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -38,10 +38,12 @@
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"karma-tap": "^3.1.1",
"karma-webpack": "^1.8.0",
"knockout": "^3.4.1",
"minimist": "^1.2.0",
"sinon": "^1.17.7",
"tape": "^4.6.3",
"webpack": "^1.13.3"
}
Expand Down
143 changes: 104 additions & 39 deletions src/index.js
Expand Up @@ -6,72 +6,116 @@ const links = {}

let _parse, _stringify

function getCoercions(config) {
const coercions = {}
Object.entries(config).forEach(([k, v = {}]) => coercions[k] = v.coerce)
return coercions
}

function getDefaults(config) {
const defaults = {}
Object.entries(config).forEach(([k, v = {}]) =>
defaults[k] = v.default || v.initial || v.coerce
? v.default
: v)
return defaults
const ret = {}
Object.entries(config).forEach(([k, v]) => ret[k] = v.default)
return ret
}

function getInitialValues(config) {
const inits = {}
Object.entries(config).forEach(([k, v = {}]) =>
inits[k] = v.default || v.initial || v.coerce
? v.initial || v.default
: v)
return inits
const ret = {}
Object.entries(config).forEach(([name, { initial }]) => {
if (initial) {
ret[name] = initial
}
})
return ret
}

function getSessionValues(config, group) {
return getBrowserStorageValues(config, group, sessionStorage)
}

function getLocalValues(config, group) {
return getBrowserStorageValues(config, group, localStorage)
}

function getBrowserStorageValues(config, group, store) {
const ret = {}
Object.keys(config).forEach((name) => {
const browserStorageKey = getBrowserStorageKey(name, group)
const fromSession = store.getItem(browserStorageKey)
if (fromSession) {
ret[name] = fromSession
}
})
return ret
}

function getBrowserStorageKey(name, group) {
let key = '_KO_QUERYSTRING_'
if (group) {
key += group + '_'
}
key += name
return key
}

function expandConfig(c) {
const ret = {}
Object.entries(c).forEach(([k, v]) => {
ret[k] = v.default || v.initial || v.coerce || v.session || v.local
? v
: { default: v }
})
return ret
}

class Query {
constructor(config = {}, group) {
this._group = group
this._config = config
this._defaults = getDefaults(config)
config = expandConfig(config)

if (isUndefined(query[this._group])) {
query[this._group] = {}
links[this._group] = 1
if (isUndefined(query[group])) {
query[group] = {}
links[group] = 1
} else {
links[this._group]++
links[group]++
}

const coercions = getCoercions(config)
const initialValues = getInitialValues(config)
const fromQS = Query.fromQS(this._group)
const init = Object.assign({}, initialValues, fromQS)
this._group = group
this._defaults = getDefaults(config)

const initials = Object.assign(
{},
this._defaults,
getInitialValues(config),
getSessionValues(config, group),
getLocalValues(config, group),
Query.fromQS(group))

const { proxy, revoke } = Proxy.revocable(this, {
get: (_, name) => {
if (name[0] === '_' || !isUndefined(this[name])) {
return this[name]
}
if (isUndefined(query[this._group][name])) {
query[this._group][name] = Query.createQuerySetterObservable(group, name, this._defaults[name], init[name], coercions[name])
Object.assign(query[this._group][name], {
isDefault: ko.pureComputed(() => query[this._group][name]() === this._defaults[name]),
if (isUndefined(query[group][name])) {
const _config = config[name] || {}
query[group][name] = Query.createQuerySetterObservable({
group,
name,
default: this._defaults[name],
initial: initials[name],
coerce: _config.coerce,
session: _config.session,
local: _config.local
})
Object.assign(query[group][name], {
isDefault: ko.pureComputed(() => query[group][name]() === this._defaults[name]),
clear: () => {
query[this._group][name](this._defaults[name])
query[group][name](this._defaults[name])
}
})
if (this._forceRecompute) {
ko.tasks.schedule(() => this._forceRecompute(!this._forceRecompute()))
}
}
return query[this._group][name]
return query[group][name]
}
})

this.revoke = revoke

Object.keys(init).forEach((k) => proxy[k])
Object.keys(initials).forEach((k) => proxy[k])

return proxy
}
Expand Down Expand Up @@ -200,22 +244,43 @@ class Query {
return this._queuedUpdate
}

static createQuerySetterObservable(group, name, defaultVal, initVal, coerce) {
const obs = ko.observable(initVal)
static createQuerySetterObservable({
group,
name,
default: _default,
initial,
coerce,
session,
local
}) {
const obs = ko.observable(initial)
const key = getBrowserStorageKey(name, group)

if (session) {
sessionStorage.setItem(key, initial)
} else if (local) {
localStorage.setItem(key, initial)
}

return ko.pureComputed({
read() {
return obs()
},
write(v) {
if (isUndefined(v)) {
v = defaultVal
v = _default
}
if (coerce) {
v = coerce(v)
}
obs(v)
Query.queueQueryStringWrite()

if (session) {
sessionStorage.setItem(key, v)
} else if (local) {
localStorage.setItem(key, v)
}
}
})
}
Expand Down
60 changes: 58 additions & 2 deletions test.js
@@ -1,3 +1,5 @@
/* global sinon */

import ko from 'knockout'
import test from 'tape'
import $ from 'jquery'
Expand Down Expand Up @@ -96,6 +98,60 @@ ko.components.register('test', {
t.end()
})

test('session', (t) => {
const sessionStorageSpy = sinon.spy(window.sessionStorage, 'setItem')

const query1 = new Query({
foo: {
default: 'foo',
session: true
}
})

t.true(sessionStorageSpy.called, 'saves value in sessionStorage')

query1.dispose()

const query2 = new Query({
foo: {
session: true
}
})

t.equals(query2.foo(), 'foo', 'initializes from sessionStorage')

query2.dispose()

t.end()
})

test('local', (t) => {
const localStorageSpy = sinon.spy(window.localStorage, 'setItem')

const query1 = new Query({
foo: {
default: 'foo',
local: true
}
})

t.true(localStorageSpy.called, 'saves value in localStorage')

query1.dispose()

const query2 = new Query({
foo: {
local: true
}
})

t.equals(query2.foo(), 'foo', 'initializes from localStorage')

query2.dispose()

t.end()
})

test('advanced', (t) => {
history.replaceState(null, null, location.pathname + '?{"foo": "notfoo"}')

Expand Down Expand Up @@ -185,11 +241,11 @@ ko.components.register('test', {

const query = new Query()

t.deepEquals({ foo: 'foo' }, query.toJS(), 'returns unwrapped query object')
t.deepEquals(query.toJS(), { foo: 'foo' }, 'returns unwrapped query object')

query.foo(undefined)

t.deepEquals({}, query.toJS(), 'omits undefined values')
t.deepEquals(query.toJS(), {}, 'omits undefined values')

query.dispose()
t.end()
Expand Down

0 comments on commit 57e308a

Please sign in to comment.