gristlabs_grist-core/app/client/lib/koSession.js

91 lines
3.0 KiB
JavaScript
Raw Normal View History

/**
* koSession offers observables whose values are tied to the browser session or history:
*
* sessionValue(key) - an observable preserved across history entries and reloads.
*
* Note: we could also support "browserValue", shared across all tabs and across browser restarts
* (same as sessionValue but using window.localStorage), but it seems more appropriate to store
* such values on the server.
*/
/* global window, $ */
var _ = require('underscore');
var ko = require('knockout');
/**
* Maps a string key to an observable. The space of keys is shared for all kinds of observables,
* and they differ only in where they store their state. Each observable gets several extra
* properties:
* @property {String} ksKey The key used for storage. It should be unique across koSession values.
* @property {Object} ksDefault The default value if the storage doesn't have one.
* @property {Function} ksFetch The method to fetch the value from storage.
* @property {Function} ksSave The method to save the value to storage.
*/
var _sessionValues = {};
function createObservable(key, defaultValue, methods) {
var obs = _sessionValues[key];
if (!obs) {
_sessionValues[key] = obs = ko.observable();
obs.ksKey = key;
obs.ksDefaultValue = defaultValue;
obs.ksFetch = methods.fetch;
obs.ksSave = methods.save;
obs.dispose = methods.dispose;
// We initialize the observable before setting rateLimit, to ensure that the initialization
// doesn't end up triggering subscribers that are about to be added (which seems to be a bit
// of a problem with rateLimit extender, and possibly deferred). This workaround relies on the
// fact that the extender modifies its target without creating a new one.
obs(obs.ksFetch());
obs.extend({deferred: true});
obs.subscribe(function(newValue) {
if (newValue !== this.ksFetch()) {
console.log("koSession: %s changed %s -> %s", this.ksKey, this.ksFetch(), newValue);
this.ksSave(newValue);
}
}, obs);
}
return obs;
}
/**
* Returns an observable whose value sticks across reloads and navigation, but is different for
* different browser tabs. E.g. it may be used to reflect whether a side pane is open.
* The `key` isn't visible to the user, so pick any unique string name.
*/
function sessionValue(key, optDefault) {
return createObservable(key, optDefault, sessionValueMethods);
}
exports.sessionValue = sessionValue;
var sessionValueMethods = {
'fetch': function() {
var value = window.sessionStorage.getItem(this.ksKey);
if (!value) {
return this.ksDefaultValue;
}
try {
return JSON.parse(value);
} catch (e) {
return this.ksDefaultValue;
}
},
'save': function(value) {
window.sessionStorage.setItem(this.ksKey, JSON.stringify(value));
},
'dispose': function(value) {
window.sessionStorage.removeItem(this.ksKey);
}
};
function onApplyState() {
_.each(_sessionValues, function(obs, key) {
obs(obs.ksFetch());
});
}
$(window).on('applyState', onApplyState);