block by shawnbot bb167c3c33b36c52b162

bb167c3c33b36c52b162

Full Screen

Stateful Checkboxes

This quick little hack creates a global function named checkboxes() that uses [localStorage] to remember the state of checkboxes on the host page. Usage:

var boxes = checkboxes({
    // the checkbox selector (always prefixed with 'input[type=checkbox]')
    selector:   '.stateful',
    // the storage engine (only 'localStorage' is supported here)
    storage:    'localStorage',
    // the prefix for all storage keys
    namespace:  'stateful.checkboxes:' + location.pathname,
    // elements matching this selector will clear the checkboxes when clicked
    clear:      'button.stateful-clear'
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Stateful Checkboxes</title>
    <script src="checkboxes.js"></script>
    <style>
      html {
        padding: 1em
      }

      body {
        font-family: 'Helvetica Neue', helvetica, arial, sans-serif;
      }

      h1 {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <h1>Stateful Checkboxes</h1>
    <ol>
      <li><input type="checkbox"> add some checkboxes</li>
      <li><input type="checkbox"> make them stateful</li>
      <li><input type="checkbox"> profit?</li>
    </ol>
    <p><input type="checkbox" checked> this should be checked by default</p>
    <p><button id="clear">clear</button></p>
  </body>
  <script>
    checkboxes({
      selector: '',     // all checkboxes
      clear: '#clear'   // clear button
    });
  </script>
</html>

checkboxes.js

(function(exports) {

  var defaults = {
    selector: '.stateful',
    storage: 'localStorage',
    namespace: 'stateful.checkboxes:' + location.pathname,
    clear: 'button.stateful-clear'
  };

  exports.checkboxes = function(options) {
    options = extend({}, defaults, options);

    var selector = 'input[type=checkbox]' + (options.selector || ''),
        storage = getEngine(options.storage),
        boxes = selectAll(selector),
        prefix = options.namespace;

    boxes.forEach(function(box, i) {
      var key = getKey(box, i),
          val = storage.get(key);
      box.__key__ = key;
      box.__checked__ = box.checked;
      if (val !== null) {
        box.checked = val === 'true';
      }
      box.addEventListener('change', function() {
        storage.set(key, box.checked);
      });
    });

    if (options.clear) {
      selectAll(options.clear).forEach(function(button) {
        button.addEventListener('click', clear);
      });
    }

    function getKey(box, i) {
      return prefix + (box.id || box.name || '@' + i);
    }

    function getData() {
      var data = {};
      forEach(boxes, function(box, i) {
        data[box.__key__] = box.checked;
      });
      return data;
    }

    function clear() {
      forEach(boxes, function(box, i) {
        box.checked = box.__checked__;
        storage.rem(box.__key__);
      });
    }

    return {
      data: getData,
      clear: clear
    };
  };

  function selectAll(selector) {
    return [].slice.call(document.querySelectorAll(selector));
  }

  function getEngine(name) {
    switch (name) {
      case 'cookie':
        throw 'cookie engine not implemented';

      case 'localStorage':
      case null:
        var ls = window.localStorage;
        return {
          get: ls.getItem.bind(ls),
          set: ls.setItem.bind(ls),
          rem: ls.removeItem.bind(ls)
        };
    }
    throw 'no such storage engine: ' + name;
  }

  function forEach(list, fn, context) {
    return Array.prototype.forEach.call(list, fn, context || this);
  }

  function extend(obj) {
    [].slice.call(arguments, 1).forEach(function(arg) {
      if (!arg) return;
      for (var key in arg) {
        obj[key] = arg[key];
      }
    });
    return obj;
  }

})(this);