## The Code I've got things operating as desired so far and am hoping fresh eyes can point out any mistakes or bits that can be improved, a [live demo][github__live_demo] is hosted and updated on GitHub Pages, and the [source code][github__source] is just a `fork` or `clone` away.... ### `BrowserStorage.js` ```JavaScript class BrowserStorage { /** * @this BrowserStorage */ constructor() { this.supports_local_storage = this.supportsLocalStorage(); this.supports_cookies = this.supportsCookies(); this.storage_available = (this.supports_cookies || this.supports_local_storage) ? true : false; } /** * Note, use `this.supports_local_storage` instead within tests. * @returns {boolean} */ supportsLocalStorage() { try { localStorage.setItem('test_key', true); } catch (e) { return false; } finally { localStorage.removeItem('test_key'); } return true; } /** * Note, use `this.supports_cookies` instead within tests. * @returns {boolean} * @this BrowserStorage */ supportsCookies() { if (navigator.cookieEnabled) return true; try { document.cookie = 'testcookie'; } catch (e) { return false; } finally { if (document.cookie.indexOf('testcookie') != -1) { this.remove('testcookie'); return true; } } return false; } /** * Copy of `constructor` method that should not through a type error * @returns {none} * @this BrowserStorage */ constructorRefresh() { this.supports_local_storage = this.supportsLocalStorage(); this.supports_cookies = this.supportsCookies(); this.storage_available = (this.supports_cookies || this.supports_local_storage) ? true : false; } /** * Gets specified value from browser storage and URI component encodes while returning * @returns {?boolean|?number|?string} * @param {string|number} key - Name of key to look up value for. * @this BrowserStorage */ get(key) { let value = null; if (this.supports_local_storage) { value = localStorage.getItem(key); } else if (this.supports_cookies) { let cookie_data = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)'); value = cookie_data ? cookie_data[2] : null; } return encodeURIComponent(value); } /** * Removes select value by key from browser storage; note for cookies, _full wipe_ will occur on next page load * @returns {boolean} * @this BrowserStorage */ remove(key) { if (this.supports_local_storage) { localStorage.removeItem(key) return true; } else if (this.supports_cookies) { // Note, unsetting and expiring in the past is how // to remove one cookie upon the next page load. this.set(key, '', -7); return true; } return false; } /** * Stores client settings within browser, _serverless_ in the truest scene * @param {string|number} key - _variable name_ to store value under * @param {boolean|number|string} value - stored either under localStorage or as a cookie * @param {number} days_to_live - how long a browser is suggested to keep cookies * @returns {boolean} * @this BrowserStorage */ set(key, value, days_to_live = false) { if (this.supports_local_storage) { localStorage.setItem(key, value); return true; } else if (this.supports_cookies) { let expires = ''; if (days_to_live) { let date = new Date(); if (days_to_live == 0) { date.setTime(date.getTime()); } else { date.setTime(date.getTime() + (days_to_live * 24 * 60 * 60 * 1000)); } expires = '; expires=' + date.toGMTString(); } document.cookie = name + '=' + value + expires + '; path=/'; return true; } return false; } /** * Clears **all** client settings from either localStorage or cookies * @returns {boolean} * @this BrowserStorage */ clear() { if (this.supports_local_storage) { localStorage.clear(); return true; } else if (this.supports_cookies) { let cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { let cookie = cookies[i]; let key = encodeURIComponent(cookie.split('=')[0].replace(/(^\s+|\s+$)/g, '')); if (typeof(key) == 'string' || typeof(key) == 'number') { this.remove(key); } else { return false; } } return true; } return false; } } ``` ### `main.css` ```css /** * 1. Center element * 2. Re-justify text */ body { text-align: center; /* 1 */ } .container__main_content { max-width: 80%; max-width: 80vw; display: inline-block; /* 1 */ text-align: left; /* 2 */ } /** * Privacy notice footer stuff */ .container__privacy_notice { position: fixed; bottom: 5px; max-width: 80%; max-width: 80vw; background-color: lightgrey; text-align: left; /* 2 */ } .privacy_notice__label { background-color: black; color: white; font-size: 1.5rem; padding: 5px 10px; float: right; } .privacy_notice__label:hover { color: lightgrey; } /* State of label when checkbox is unchecked */ .privacy_notice__label::after { content: attr(value); } /** * Checkbox hacks */ .hidden__input { display: none; visibility: hidden; opacity: 0; filter: alpha(opacity=0); } /* State updates for when checkbox is checked */ .privacy_notice__checkbox:checked ~ .container__privacy_notice { box-shadow: unset; max-width: 1.5rem; max-height: 1.5rem; } .privacy_notice__checkbox:checked ~ .container__privacy_notice * { visibility: hidden; opacity: 0; filter: alpha(opacity=0); } .privacy_notice__checkbox:checked ~ .container__privacy_notice .privacy_notice__label::after { content: attr(checked); } /* State of label when checkbox is checked */ .privacy_notice__checkbox:checked ~ .container__privacy_notice::after { content: '\1F56D'; position: absolute; bottom: 5px; left: 5px; } /* Combo of checked and hover state updates */ .privacy_notice__checkbox:checked ~ .container__privacy_notice:hover { max-width: 80%; max-width: 80vw; max-height: unset; } .privacy_notice__checkbox:checked ~ .container__privacy_notice:hover::after { display: none; visibility: hidden; opacity: 0; filter: alpha(opacity=0); } .privacy_notice__checkbox:checked ~ .container__privacy_notice:hover * { visibility: visible; opacity: 1; filter: alpha(opacity=100); } ``` ### `index.html` ```html <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <title>BrowserStorage Example</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <script src="assets/javascript-modules/browser-storage/BrowserStorage.js" type="text/javascript"></script> <link type="text/css" href="assets/css/main.css" rel="stylesheet"/> <script type="text/javascript"> const storage = new BrowserStorage(); if (storage.storage_available != true) throw new Error('We may never have any memory of this...'); function restore_settings() { let privacy_notice_read = storage.get('checkbox__privacy_notice'); if (privacy_notice_read != 'true' && privacy_notice_read != true) return false; document.getElementById('trigger__privacy_notice').checked = true; return true; } function store_settings() { let privacy_notice_read = storage.get('checkbox__privacy_notice'); if (privacy_notice_read != 'true' && privacy_notice_read != true) return false; if (document.getElementById('trigger__privacy_notice').checked != true) return false; storage.set('checkbox__privacy_notice', true, 365); return true; } function toggle_privacy_notice(input_element) { if (input_element.checked == true) { storage.set('checkbox__privacy_notice', input_element.checked, 365); return true; } storage.remove('checkbox__privacy_notice'); storage.clear(); return false; } </script> </head> <body onload="restore_settings();" onunload="store_settings();"> <div class="container__main_content"> <p> This is a static site that provides the illusion of remembering a client. Test by accepting the <i>privacy notice</i> and then refreshing or closing and re-opening a tab pointing to the current URL. </p> <p> <kbd>Ctrl</kbd> <kbd>R</kbd> or <kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>R</kbd> will refresh most desktop browsers. </p> <p> Then reject the privacy notice by clicking on the Acceptable button again to revert the behavior of this site. </p> <p> Use <kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>I</kbd> to open debugger on a desktop browser, then navigate to the Storage tab/pane to observe JavaScript doing things for the above mentioned actions. </p> </div> <input type="checkbox" name="checkbox__privacy_notice" class="hidden__input privacy_notice__checkbox" id="trigger__privacy_notice" onchange="toggle_privacy_notice(this);"> <div class="container__privacy_notice"> <p> This site uses JavaScript, and localStorage or cookies of your browser, to enhance your experience here, however, this site currently does <strong>not</strong> store any data about your activity severer-side. </p> <p> Review the <a href="https://github.com/javascript-utilities/browser-storage#privacy">Privacy</a> and <a href="https://github.com/javascript-utilities/browser-storage#security">Security</a> notices regarding the <a href="https://github.com/javascript-utilities/browser-storage/blob/master/BrowserStorage.js"> BrowserStorage.js </a>, for more details on how client-side storage and scripting are utilized. </p> <label for="trigger__privacy_notice" class="menu__trigger__label privacy_notice__label" title="Click to toggle." value="false" checked="true" >Acceptable: </label> </div> </body> </html> ``` > Note, I did try to use the web code option when writing this question but that caused various errors. ## Questions My goal is to have near 100% coverage for browsers that have JavaScript and either `localStorage` or `document.cookie` storage options; - in this _day-n-age_ is there any point in using cookies for a local storage fallback? - is there any point in using the old syntax of getting OO behavior from JavaScript or will using the `class` and `function` keywords be sufficient? > if there are any good arguments for not using `class`, please include some feature detection examples for loading an alternate version. - I keep running across `npm` but that seems to be more focused on Node related JavaScript, should I consider issuing _`npm init .`_ or would that _pollute_ their package management system? > If projects like this are a good candidate for `npm`, then some tips on Operating System agnostic ways of issuing `Git` commands would be excellent. - Currently I'm using `encodeURIComponent` when returning browser stored values, however, that leads to unpredictable behavior with boolean and numerical values (sometimes it's a string, sometimes not), so is there a _better_ way of retrieving values? > At this time it almost feels like I'll have to do some pre-checking prior to returning in order to get some consistency in behavior. - Where have I messed up and/or how can code execution be improved without sacrificing readability? - One thing I find bothersome about using _`checkbox hacks`_ is accessibility, are there any _good_ ways of getting <kbd>Tab</kbd> and <kbd>Space</kbd>/<kbd>Enter</kbd> support? ___ Thanks, and I hope that even if the above is me being a _bad influence_ that we can all learn why together. [github__live_demo]: https://javascript-utilities.github.io/browser-storage/index.html [github__source]: https://github.com/javascript-utilities/browser-storage