Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store component #248

Merged
merged 17 commits into from Oct 3, 2018
@@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [0.32.0] - 2018-10-2
### Added
- Added Store component [#248](https://github.com/plotly/dash-core-components/pull/248)


## [0.31.0] - 2018-09-21
### Changed
- Updated NPM scripts:
@@ -0,0 +1,67 @@
# AUTO GENERATED FILE - DO NOT EDIT

from dash.development.base_component import Component, _explicitize_args


class Store(Component):
"""A Store component.
Easily keep data on the client side with this component.
The data is not inserted in the DOM.
Data can be in memory, localStorage or sessionStorage.
The data will be kept with the id as key.
Keyword arguments:
- id (string; required): The key of the storage.
- storage_type (a value equal to: 'local', 'session', 'memory'; optional): The type of the web storage.
memory: only kept in memory, reset on page refresh.
local: window.localStorage, data is kept after the browser quit.
session: window.sessionStorage, data is cleared once the browser quit.
- data (dict | list | number | string; optional): The stored data for the id.
- clear_data (boolean; optional): Set to true to remove the data contained in `data_key`.
- modified_timestamp (number; optional): The last time the storage was modified.
Available events: """
@_explicitize_args
def __init__(self, id=Component.REQUIRED, storage_type=Component.UNDEFINED, data=Component.UNDEFINED, clear_data=Component.UNDEFINED, modified_timestamp=Component.UNDEFINED, **kwargs):
self._prop_names = ['id', 'storage_type', 'data', 'clear_data', 'modified_timestamp']
self._type = 'Store'
self._namespace = 'dash_core_components'
self._valid_wildcard_attributes = []
self.available_events = []
self.available_properties = ['id', 'storage_type', 'data', 'clear_data', 'modified_timestamp']
self.available_wildcard_properties = []

_explicit_args = kwargs.pop('_explicit_args')
_locals = locals()
_locals.update(kwargs) # For wildcard attrs
args = {k: _locals[k] for k in _explicit_args if k != 'children'}

for k in ['id']:
if k not in args:
raise TypeError(
'Required argument `' + k + '` was not specified.')
super(Store, self).__init__(**args)

def __repr__(self):
if(any(getattr(self, c, None) is not None
for c in self._prop_names
if c is not self._prop_names[0])
or any(getattr(self, c, None) is not None
for c in self.__dict__.keys()
if any(c.startswith(wc_attr)
for wc_attr in self._valid_wildcard_attributes))):
props_string = ', '.join([c+'='+repr(getattr(self, c, None))
for c in self._prop_names
if getattr(self, c, None) is not None])
wilds_string = ', '.join([c+'='+repr(getattr(self, c, None))
for c in self.__dict__.keys()
if any([c.startswith(wc_attr)
for wc_attr in
self._valid_wildcard_attributes])])
return ('Store(' + props_string +
(', ' + wilds_string if wilds_string != '' else '') + ')')
else:
return (
'Store(' +
repr(getattr(self, self._prop_names[0], None)) + ')')
@@ -13,6 +13,7 @@
from .RadioItems import RadioItems
from .RangeSlider import RangeSlider
from .Slider import Slider
from .Store import Store
from .SyntaxHighlighter import SyntaxHighlighter
from .Tab import Tab
from .Tabs import Tabs
@@ -36,6 +37,7 @@
"RadioItems",
"RangeSlider",
"Slider",
"Store",
"SyntaxHighlighter",
"Tab",
"Tabs",

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -1,6 +1,6 @@
{
"name": "dash-core-components",
"version": "0.31.0",
"version": "0.32.0",
"description": "Core component suite for Dash",
"repository": {
"type": "git",
@@ -9,7 +9,7 @@
"main": "src/index.js",
"scripts": {
"generate-python-classes": "python -c \"import dash; dash.development.component_loader.generate_classes('dash_core_components', 'dash_core_components/metadata.json');\"",
"prepublish": "npm run build:js && npm run build:py",
"prepublish": "npm run build:js && npm run build:js-dev && npm run build:py",
"publish-all": "node scripts/publish.js",
"start": "webpack-serve ./webpack.serve.config.js --open",
"lint": "eslint src",
@@ -1 +1 @@
__version__ = '0.31.0'
__version__ = '0.32.0'
@@ -1,6 +1,6 @@
{
"name": "dash-core-components",
"version": "0.31.0",
"version": "0.32.0",
"description": "Core component suite for Dash",
"repository": {
"type": "git",
@@ -9,7 +9,7 @@
"main": "src/index.js",
"scripts": {
"generate-python-classes": "python -c \"import dash; dash.development.component_loader.generate_classes('dash_core_components', 'dash_core_components/metadata.json');\"",
"prepublish": "npm run build:js && npm run build:py",
"prepublish": "npm run build:js && npm run build:js-dev && npm run build:py",
"publish-all": "node scripts/publish.js",
"start": "webpack-serve ./webpack.serve.config.js --open",
"lint": "eslint src",
@@ -0,0 +1,230 @@
import R from 'ramda';
import React from 'react';
import PropTypes from 'prop-types';

function dataCheck(data, old) {
// Assuming data and old are of the same type.
if (R.isNil(old) || R.isNil(data)) {
return true;
}
const type = R.type(data);
if (type === 'Array') {
if (data.length !== old.length) {
return true;
}
for (let i = 0; i < data.length; i++) {
if (data[i] !== old[i]) {
return true;
}
}
} else if (R.contains(type, ['String', 'Number'])) {
return old !== data;
} else if (type === 'Object') {
return R.any(([k, v]) => old[k] !== v)(Object.entries(data));
}
return false;
}

class MemStore {
constructor() {
this._data = {};
this._modified = -1;
}

getItem(key) {
return this._data[key];
}

setItem(key, value) {
this._data[key] = value;
this.setModified(key);
}

removeItem(key) {
delete this._data[key];
this.setModified(key);
}

// noinspection JSUnusedLocalSymbols
setModified(_) {
this._modified = Date.now();
}

// noinspection JSUnusedLocalSymbols
getModified(_) {
return this._modified;
}
}

class WebStore {
constructor(storage) {
this._storage = storage;
}

getItem(key) {
return JSON.parse(this._storage.getItem(key));
}

setItem(key, value) {
this._storage.setItem(key, JSON.stringify(value));
this.setModified(key);
}

removeItem(key) {
this._storage.removeItem(key);
this._storage.removeItem(`${key}-timestamp`);
}

setModified(key) {
this._storage.setItem(`${key}-timestamp`, Date.now());
}

getModified(key) {
return (
Number.parseInt(this._storage.getItem(`${key}-timestamp`), 10) || -1
);
}
}

const _localStore = new WebStore(window.localStorage);
const _sessionStore = new WebStore(window.sessionStorage);

/**
* Easily keep data on the client side with this component.
* The data is not inserted in the DOM.
* Data can be in memory, localStorage or sessionStorage.
* The data will be kept with the id as key.
*/
export default class Store extends React.Component {
constructor(props) {
super(props);

if (props.storage_type === 'local') {
this._backstore = _localStore;
} else if (props.storage_type === 'session') {
this._backstore = _sessionStore;
} else if (props.storage_type === 'memory') {
this._backstore = new MemStore();
}

this.onStorageChange = this.onStorageChange.bind(this);
}

onStorageChange(e) {
const {id, setProps} = this.props;
if (e.key === id && setProps && e.newValue !== e.oldValue) {
setProps({
data: JSON.parse(e.newValue),
modified_timestamp: this._backstore.getModified(id),
});
}
}

componentWillMount() {
const {setProps, id, data, storage_type} = this.props;
if (storage_type !== 'memory') {
window.addEventListener('storage', this.onStorageChange);
}

const old = this._backstore.getItem(id);
if (R.isNil(old) && data) {
// Initial data mount
this._backstore.setItem(id, data);
if (setProps) {
setProps({
modified_timestamp: this._backstore.getModified(id),
});
}
return;
}

if (setProps && dataCheck(old, data)) {
setProps({
data: old,
modified_timestamp: this._backstore.getModified(id),
});
}
}

componentWillUnmount() {
if (this.props.storage_type !== 'memory') {
window.removeEventListener('storage', this.onStorageChange);
}
}

componentDidUpdate() {
const {data, id, clear_data, setProps} = this.props;
if (clear_data) {
this._backstore.removeItem(id);
if (setProps) {
setProps({
clear_data: false,
data: null,
modified_timestamp: this._backstore.getModified(id),
});
}
} else if (data) {
const old = this._backstore.getItem(id);
// Only set the data if it's not the same data.
if (dataCheck(data, old)) {
this._backstore.setItem(id, data);
if (setProps) {
setProps({
modified_timestamp: this._backstore.getModified(id),
});
}
}
}
}

render() {
return null;
}
}

Store.defaultProps = {
storage_type: 'memory',
clear_data: false,
modified_timestamp: -1,
};

Store.propTypes = {
/**
* The key of the storage.
*/
id: PropTypes.string.isRequired,

/**
* The type of the web storage.
*
* memory: only kept in memory, reset on page refresh.
* local: window.localStorage, data is kept after the browser quit.
* session: window.sessionStorage, data is cleared once the browser quit.
*/
storage_type: PropTypes.oneOf(['local', 'session', 'memory']),

/**
* The stored data for the id.
*/
data: PropTypes.oneOfType([
PropTypes.object,
PropTypes.array,
PropTypes.number,
PropTypes.string,
]),

/**
* Set to true to remove the data contained in `data_key`.
*/
clear_data: PropTypes.bool,

/**
* The last time the storage was modified.
*/
modified_timestamp: PropTypes.number,

/**
* Dash-assigned callback that gets fired when the value changes.
*/
setProps: PropTypes.func,
};
@@ -1,6 +1,6 @@
/* eslint-disable import/prefer-default-export */
import ConfirmDialog from './components/ConfirmDialog.react';
import ConfirmDialogProvider from './components/ConfirmDialogProvider.react'
import ConfirmDialogProvider from './components/ConfirmDialogProvider.react';
import Dropdown from './components/Dropdown.react';
import Input from './components/Input.react';
import Graph from './components/Graph.react';
@@ -19,6 +19,7 @@ import DatePickerRange from './components/DatePickerRange.react';
import Upload from './components/Upload.react';
import Tabs from './components/Tabs.react';
import Tab from './components/Tab.react';
import Store from './components/Store.react';

export {
Checklist,
@@ -40,5 +41,6 @@ export {
Textarea,
DatePickerSingle,
DatePickerRange,
Upload
Upload,
Store,
};
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.