components
This commit is contained in:
parent
304d578b54
commit
6fb4aabcba
344 changed files with 669 additions and 406 deletions
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<textarea style="width:200px; height: 60px;" id="area" placeholder="Write here"></textarea>
|
||||
<br>
|
||||
<button onclick="localStorage.removeItem('area');area.value=''">Clear</button>
|
||||
<script>
|
||||
area.value = localStorage.getItem('area');
|
||||
area.oninput = () => {
|
||||
localStorage.setItem('area', area.value)
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
<!doctype html>
|
||||
<textarea style="width:200px; height: 60px;" id="area"></textarea>
|
10
6-data-storage/02-localstorage/1-form-autosave/task.md
Normal file
10
6-data-storage/02-localstorage/1-form-autosave/task.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
# Autosave a form field
|
||||
|
||||
Create a `textarea` field that "autosaves" its value on every change.
|
||||
|
||||
So, if the user occasionally closes the page, and opens it again, he'll find his unfinished input at place.
|
||||
|
||||
Like this:
|
||||
|
||||
[iframe src="solution" height=120]
|
247
6-data-storage/02-localstorage/article.md
Normal file
247
6-data-storage/02-localstorage/article.md
Normal file
|
@ -0,0 +1,247 @@
|
|||
# LocalStorage, sessionStorage
|
||||
|
||||
Web storage objects `localStorage` and `sessionStorage` allow to save key/value pairs in the browser.
|
||||
|
||||
What's interesting about them is that the data survives a page refresh (for `sessionStorage`) and even a full browser restart (for `localStorage`). We'll see that very soon.
|
||||
|
||||
We already have cookies. Why additional objects?
|
||||
|
||||
- Unlike cookies, web storage objects are not sent to server with each request. Because of that, we can store much more. Most browsers allow at least 2 megabytes of data (or more) and have settings to configure that.
|
||||
- The server can't manipulate storage objects via HTTP headers, everything's done in JavaScript.
|
||||
- The storage is bound to the origin (domain/protocol/port triplet). That is, different protocols or subdomains infer different storage objects, they can't access data from each other.
|
||||
|
||||
Both storage objects provide same methods and properties:
|
||||
|
||||
- `setItem(key, value)` -- store key/value pair.
|
||||
- `getItem(key)` -- get the value by key.
|
||||
- `removeItem(key)` -- remove the key with its value.
|
||||
- `clear()` -- delete everything.
|
||||
- `key(index)` -- get the key on a given position.
|
||||
- `length` -- the number of stored items.
|
||||
|
||||
Let's see how it works.
|
||||
|
||||
## localStorage demo
|
||||
|
||||
The main features of `localStorage` are:
|
||||
|
||||
- Shared between all tabs and windows from the same origin.
|
||||
- The data does not expire. It remains after the browser restart and even OS reboot.
|
||||
|
||||
For instance, if you run this code...
|
||||
|
||||
```js run
|
||||
localStorage.setItem('test', 1);
|
||||
```
|
||||
|
||||
...And close/open the browser or just open the same page in a different window, then you can get it like this:
|
||||
|
||||
```js run
|
||||
alert( localStorage.getItem('test') ); // 1
|
||||
```
|
||||
|
||||
We only have to be on the same domain/port/protocol, the url path can be different.
|
||||
|
||||
The `localStorage` is shared, so if we set the data in one window, the change becomes visible in the other one.
|
||||
|
||||
## Object-like access
|
||||
|
||||
We can also use a plain object way of getting/setting keys, like this:
|
||||
|
||||
```js run
|
||||
// set key
|
||||
localStorage.test = 2;
|
||||
|
||||
// get key
|
||||
alert( localStorage.test ); // 2
|
||||
|
||||
// remove key
|
||||
delete localStorage.test;
|
||||
```
|
||||
|
||||
That's allowed for historical reasons, and mostly works, but generally not recommended for two reasons:
|
||||
|
||||
1. If the key is user-generated, it can be anything, like `length` or `toString`, or another built-in method of `localStorage`. In that case `getItem/setItem` work fine, while object-like access fails:
|
||||
```js run
|
||||
let key = 'length';
|
||||
localStorage[key] = 5; // Error, can't assign length
|
||||
```
|
||||
|
||||
2. There's a `storage` event, it triggers when we modify the data. That event does not happen for object-like access. We'll see that later in this chapter.
|
||||
|
||||
## Looping over keys
|
||||
|
||||
Methods provide get/set/remove functionality. But how to get all the keys?
|
||||
|
||||
Unfortunately, storage objects are not iterable.
|
||||
|
||||
One way is to use "array-like" iteration:
|
||||
|
||||
```js run
|
||||
for(let i=0; i<localStorage.length; i++) {
|
||||
let key = localStorage.key(i);
|
||||
alert(`${key}: ${localStorage.getItem(key)}`);
|
||||
}
|
||||
```
|
||||
|
||||
Another way is to use object-specific `for key in localStorage` loop.
|
||||
|
||||
That iterates over keys, but also outputs few built-in fields that we don't need:
|
||||
|
||||
```js run
|
||||
// bad try
|
||||
for(let key in localStorage) {
|
||||
alert(key); // shows getItem, setItem and other built-in stuff
|
||||
}
|
||||
```
|
||||
|
||||
...So we need either to filter fields from the prototype with `hasOwnProperty` check:
|
||||
|
||||
```js run
|
||||
for(let key in localStorage) {
|
||||
if (!localStorage.hasOwnProperty(key)) {
|
||||
continue; // skip keys like "setItem", "getItem" etc
|
||||
}
|
||||
alert(`${key}: ${localStorage.getItem(key)}`);
|
||||
}
|
||||
```
|
||||
|
||||
...Or just get the "own" keys with `Object.keys` and then loop over them if needed:
|
||||
|
||||
```js run
|
||||
let keys = Object.keys(localStorage);
|
||||
for(let key of keys) {
|
||||
alert(`${key}: ${localStorage.getItem(key)}`);
|
||||
}
|
||||
```
|
||||
|
||||
The latter works, because `Object.keys` only returns the keys that belong to the object, ignoring the prototype.
|
||||
|
||||
|
||||
## Strings only
|
||||
|
||||
Please note that both key and value must be strings.
|
||||
|
||||
If we any other type, like a number, or an object, it gets converted to string automatically:
|
||||
|
||||
```js run
|
||||
sessionStorage.user = {name: "John"};
|
||||
alert(sessionStorage.user); // [object Object]
|
||||
```
|
||||
|
||||
We can use `JSON` to store objects though:
|
||||
|
||||
```js run
|
||||
sessionStorage.user = JSON.stringify({name: "John"});
|
||||
|
||||
// sometime later
|
||||
let user = JSON.parse( sessionStorage.user );
|
||||
alert( user.name ); // John
|
||||
```
|
||||
|
||||
Also it is possible to stringify the whole storage object, e.g. for debugging purposes:
|
||||
|
||||
```js run
|
||||
// added formatting options to JSON.stringify to make the object look nicer
|
||||
alert( JSON.stringify(localStorage, null, 2) );
|
||||
```
|
||||
|
||||
|
||||
## sessionStorage
|
||||
|
||||
The `sessionStorage` object is used much less often than `localStorage`.
|
||||
|
||||
Properties and methods are the same, but it's much more limited:
|
||||
|
||||
- The `sessionStorage` exists only within the current browser tab.
|
||||
- Another tab with the same page will have a different storage.
|
||||
- But it is shared between iframes in the tab (assuming they come from the same origin).
|
||||
- The data survives page refresh, but not closing/opening the tab.
|
||||
|
||||
Let's see that in action.
|
||||
|
||||
Run this code...
|
||||
|
||||
```js run
|
||||
sessionStorage.setItem('test', 1);
|
||||
```
|
||||
|
||||
...Then refresh the page. Now you can still get the data:
|
||||
|
||||
```js run
|
||||
alert( sessionStorage.getItem('test') ); // after refresh: 1
|
||||
```
|
||||
|
||||
...But if you open the same page in another tab, and try again there, the code above returns `null`, meaning "nothing found".
|
||||
|
||||
That's exactly because `sessionStorage` is bound not only to the origin, but also to the browser tab. For that reason, `sessionStorage` is used sparingly.
|
||||
|
||||
## Storage event
|
||||
|
||||
When the data gets updated in `localStorage` or `sessionStorage`, [storage](https://www.w3.org/TR/webstorage/#the-storage-event) event triggers, with properties:
|
||||
|
||||
- `key` – the key that was changed (null if `.clear()` is called).
|
||||
- `oldValue` – the old value (`null` if the key is newly added).
|
||||
- `newValue` – the new value (`null` if the key is removed).
|
||||
- `url` – the url of the document where the update happened.
|
||||
- `storageArea` – either `localStorage` or `sessionStorage` object where the update happened.
|
||||
|
||||
The important thing is: the event triggers on all `window` objects where the storage is accessible, except the one that caused it.
|
||||
|
||||
Let's elaborate.
|
||||
|
||||
Imagine, you have two windows with the same site in each. So `localStorage` is shared between them.
|
||||
|
||||
```online
|
||||
You might want to open this page in two browser windows to test the code below.
|
||||
```
|
||||
|
||||
Now if both windows are listening for `window.onstorage`, then each one will react on updates that happened in the other one.
|
||||
|
||||
```js run
|
||||
// triggers on updates made to the same storage from other documents
|
||||
window.onstorage = event => {
|
||||
if (event.key != 'now') return;
|
||||
alert(event.key + ':' + event.newValue + " at " + event.url);
|
||||
};
|
||||
|
||||
localStorage.setItem('now', Date.now());
|
||||
```
|
||||
|
||||
Please note that the event also contains: `event.url` -- the url of the document where the data was updated.
|
||||
|
||||
Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `storageArea` references the one that was modified. We may event want to set something back in it, to "respond" to a change.
|
||||
|
||||
**That allows different windows from the same origin to exchange messages.**
|
||||
|
||||
Modern browsers also support [Broadcast channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API), the special API for same-origin inter-window communication, it's more full featured, but less supported. There are libraries that polyfill that API, based on `localStorage`, that make it available everywhere.
|
||||
|
||||
## Summary
|
||||
|
||||
Web storage objects `localStorage` and `sessionStorage` allow to store key/value in the browser.
|
||||
- Both `key` and `value` must be strings.
|
||||
- The limit is 2mb+, depends on the browser.
|
||||
- They do not expire.
|
||||
- The data is bound to the origin (domain/port/protocol).
|
||||
|
||||
| `localStorage` | `sessionStorage` |
|
||||
|----------------|------------------|
|
||||
| Shared between all tabs and windows with the same origin | Visible within a browser tab, including iframes from the same origin |
|
||||
| Survives browser restart | Dies on tab close |
|
||||
|
||||
API:
|
||||
|
||||
- `setItem(key, value)` -- store key/value pair.
|
||||
- `getItem(key)` -- get the value by key.
|
||||
- `removeItem(key)` -- remove the key with its value.
|
||||
- `clear()` -- delete everything.
|
||||
- `key(index)` -- get the key on a given position.
|
||||
- `length` -- the number of stored items.
|
||||
- Use `Object.keys` to get all keys.
|
||||
- Can use the keys as object properties, in that case `storage` event doesn't trigger.
|
||||
|
||||
Storage event:
|
||||
|
||||
- Triggers on `setItem`, `removeItem`, `clear` calls.
|
||||
- Contains all the data about the operation, the document `url` and the storage object.
|
||||
- Triggers on all `window` objects that have access to the storage except the one that generated it (within a tab for `sessionStorage`, globally for `localStorage`).
|
|
@ -0,0 +1,9 @@
|
|||
<!doctype html>
|
||||
<script>
|
||||
window.addEventListener('storage', event => {
|
||||
alert("iframe.html: onstorage");
|
||||
});
|
||||
</script>
|
||||
<button onclick="sessionStorage.setItem('now', new Date())">sessionStorage.setItem</button>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<script>
|
||||
window.addEventListener('storage', event => {
|
||||
alert("index.html: onstorage");
|
||||
});
|
||||
</script>
|
||||
<button onclick="sessionStorage.setItem('now', new Date())">sessionStorage.setItem</button>
|
||||
<iframe src="iframe.html" style="height:100px"></iframe>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue