Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                ## A CRUD JavaScript Class 

Here is a [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) JavaScript class mapping to the storage driver of your choice ([localStorage](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage) in this demo). Before going any further, a couple of things you should note:

**Each record has to be an object**. You can't store an array, a primitive or whatever; only objects. If you want to store &mdash; let's say &mdash; numbers you should wrap them into objects first; e.g. `{ value: 42 }`.

**You shouldn't try to store different entities in the same database**. The API being quite simple, it aims at solving simple issues. For a more robust JS Database, I suggest you try [Taffy](http://www.taffydb.com/).

## What is the driver?

The driver (the thing that actually does the storage) has been externalized from the main class in order to allow you to use the driver you want: `sessionStorage`, `localStorage`, `Redis`... Anything you want as long as it relies on `key/value` pairs and supports 3 methods: `setItem`, `getItem`, `removeItem`.

The one used per default is a `StorageDriver`, relying on either `localStorage` or `sessionStorage`, depending on what you pass to the constructor, but you could definitely build your own.

## How does it work?

### Instanciating a database

The `indexedKeys` property aims at speeding up the search. By setting some keys to be indexed, searching for those keys will be way faster. In any case, you can search for any key, even those which are not indexed.

    var db = new Database({
      name: 'MyDatabase',
      indexedKeys: ['job', 'age']
    })
   
### Inserting a new entry

    var obj = {
      name: 'Hugo',
      age: 22,
      job: 'dev'
    }
    
    var id = db.insert(obj)
    
### Updating an entry

If you want to update a specific entry, the easiest way is to pass its ID as the first argument. The ID is being added to the entry when inserted as the `id` property. You can change the name of this property by setting the `uniqueKey` option when instanciating the database.

    obj['mood'] = 'happy'
    db.update(id, obj)
    
To update a collection of entry based on a search, here is how you would do it:

    var dev, i, len,
        devs = this.find({ job: 'dev' })
        
    for(i = 0, len = devs.length; i < len; i++) {
      dev = devs[i]
      dev['mood'] = 'happy'
      dev.job = 'clown'
      db.update(dev.id, dev)
    }

### Retrieving entries

The `find` method requires an object to parse and search with. 

    db.find({ mood: 'happy' })
    db.find({ job: 'dev', age: 22 })
    
### Retrieving all entries

You can either call the `findAll` method which returns all existing values in the database:

    db.findAll()
    
Or you can call the `find` method with no arguments, which basically does the same thing:

    db.find()
    
### Deleting an entry

If you want to delete a specific entry, the easiest way is to pass its ID to the function. The ID is being added to the entry when inserted as the `id` property. You can change the name of this property by setting the `uniqueKey` option when instanciating the database.

    db.delete(id)

If you want to delete a collection of entries based on asearch, you can pass an object to the function. The function will first perform a find, then delete all the returned entries.

    db.delete({ job: dev })
    
## To do

* Add `limit`, `sort` and other cool operations.
* I'd like to do something like MongoDB for the update. Not great for now.
              
            
!

CSS

              
                @import "compass/css3";

body {
  padding: 1em;
}

%code {
  background: #EFEFEF;
}
  

code {
  background: #EFEFEF;
  padding: 0 .25em;
}

pre {
  background: #EFEFEF;
  padding: .5em .5em .5em 1em;
  border-left: .5em solid deepskyblue;
  text-shadow: 0 1px rgba(white, .5);
  line-height: 1.4;
  
  code {
    background: none;
    border: none;
    padding: 0;
  }
}
              
            
!

JS

              
                // Vanilla JS equivalent to $.extend
window.extend = function( obj, extObj ) {
  obj = obj || {};
  if (arguments.length > 2) {
    for (var a = 1; a < arguments.length; a++) {
      window.extend(obj, arguments[a]);
    }
  } else {
    for (var i in extObj) {
      obj[i] = extObj[i];
    }
  }
  return obj;
};

/* jshint -W024 */

(function( exports ) {
  'use strict';

  /**
   * Represents a database
   * @constructor
   * @param {Object} conf - the options to pass to the constructor
   */
  var Database = function ( conf ) {
    this.conf = exports.extend({
      name: 'database',
      indexedKeys: [],
      uniqueKey: 'id',
      driver: new Database.drivers.StorageDriver({
        name: conf.name,
        storage: exports.localStorage
      })
    }, conf || {});

    this.initialize();
  };

  /**
   * Initialization method
   * @private
   */
  Database.prototype.initialize = function () {
    this.data = this.load() || [];
    this.id = 0;
    if (this.data.length > 0) {
      this.data = this.data.map(function(e) { return parseInt(e, 10); });
      this.id = Math.max.apply(Math, this.data);
    }
  };

  /**
   * Finding entries
   * @param   {Object} obj - the object of properties/values to look for
   * @returns {Array}      - collection of items matching the search
   */
  Database.prototype.find = function ( obj ) {
    if (typeof obj === 'undefined') {
      return this.findAll();
    }

    var keys = this.getKeys(obj),
        filtered = [],
        collection;

    for (var property in keys[0]) {
      filtered.push(this.conf.driver.getItem(property + ':' + keys[0][property]) || false);
    }

    if (filtered.length === 0) {
      collection = this.data;
    } else if (filtered.length === 1) {
      collection = filtered[0];
    } else {
      collection = intersect.apply(this, filtered);
    }

    // Filtering by unindexed keys
    return this.filter(this.select(collection), keys[1]);
  };

  /**
   * Returning all entries from database
   * @returns {Array} - collection of entries
   */
  Database.prototype.findAll = function () {
    return this.select(this.data);
  };

  /**
   * Dissociate indexed from unindexed keys from an object
   * @param   {Object} obj - object to parse
   * @returns {Array}      - array of indexed keys and unindexed keys
   */
  Database.prototype.getKeys = function ( obj ) {
    var index, keys = [];
    keys[0] = {}; // indexed properties
    keys[1] = {}; // unindexed properties

    for(var property in obj) {
      index = this.conf.indexedKeys.indexOf(property) !== -1 ? 0 : 1;
      keys[index][property] = obj[property];
    }

    return keys;
  };

  /**
   * Retrieve entries from unique keys
   * @param   {Array} collection - array of unique keys
   * @returns {Array}            - array of entries
   */
  Database.prototype.select = function ( collection ) {
    var data = [];
    collection = collection.length === 1 ? [collection] : collection;

    for (var i = 0, len = collection.length; i < len; i++) {
      data.push(this.conf.driver.getItem(collection[i]));
    }

    return data;
  };

  /**
   * Filtering a collection of entries based on unindexed keys
   * @private
   * @param   {Array}  collection    - array of entries to search for
   * @param   {Object} unindexedKeys - object of unindexed keys
   * @returns {Array}                - array of entries
   */
  Database.prototype.filter = function ( collection, unindexedKeys ) {
    var okay, entry, data = [];

    for (var i = 0, len = collection.length; i < len; i++) {
      entry = collection[i];
      okay = true;

      for (var property in unindexedKeys) {
        if (entry[property] !== unindexedKeys[property]) {
          okay = false;
          break;
        }
      }

      if (okay) {
        data.push(entry);
      }
    }

    return data;
  };

  /**
   * Inserting an entry
   * @param   {Object} obj - document to insert
   * @returns {Number}     - unique key of the document
   */
  Database.prototype.insert = function ( obj ) {
    if(Object.prototype.toString.call(obj) !== '[object Object]') {
      throw 'Can\'t insert ' + obj + '. Please insert object.';
    }
    this.id++;
    if (this.data.indexOf(this.id) === -1) {
      obj[this.conf.uniqueKey] = this.id;
      this.data.push(this.id);
      this.conf.driver.setItem(this.id, obj);
      this.conf.driver.setItem('__data', this.data.join(','));
      this.buildIndex(obj);
      return this.id;
    }
  };

  /**
   * Updating an entry
   * @param   {Number} id  - unique key of the document to update
   * @param   {Object} obj - new entry
   * @returns {Object}     - object (obj)
   */
  Database.prototype.update = function ( id, obj ) {
    if (this.data.indexOf(id) !== -1) {
      this.destroyIndex(id); // First destroy existing index for object
      obj[this.conf.uniqueKey] = id;
      this.conf.driver.setItem(id, obj); // Override object
      this.buildIndex(obj); // Rebuild index
      return obj;
    }
  };

  /**
   * Deleting an entry
   * @param  {Number|Object} arg - unique ID or object to look for before deleting matching entries
   * @returns {Boolean}           - operation status
   */
  Database.prototype.delete = function ( arg ) {
    // If passing an object, search and destroy
    if (Object.prototype.toString.call(arg) === '[object Object]') {
      this.findAndDelete(arg);
    // If passing an id, destroy id
    } else {
      if (this.data.indexOf(arg) !== -1) {
        this.data.splice(this.data.indexOf(arg), 1);
        this.destroyIndex(arg);
        this.conf.driver.removeItem(arg);
        this.conf.driver.setItem('__data', this.data.join(','));
        return this.data.indexOf(arg) === -1;
      }
    }
  };

  /**
   * Find and delete
   * @private
   * @param   {Object}  obj - the object of properties/values to look for
   * @returns {Boolean}     - operation status
   */
  Database.prototype.findAndDelete = function ( obj ) {
    var id, entries = this.find(obj), length = this.data.length;
    for(var i = 0; i < entries.length; i++) {
      id = entries[i][this.conf.uniqueKey];
      if (this.data.indexOf(id) !== -1) {
        this.data.splice(this.data.indexOf(id), 1);
        this.destroyIndex(id);
        this.conf.driver.removeItem(id);
        this.conf.driver.setItem('__data', this.data.join(','));
      }
    }
    return this.data.length < length;
  };

  /**
   * Counting number of entries
   * @returns {Number} - number of entries
   */
  Database.prototype.count = function () {
    return this.data.length;
  };

  /**
   * Dropping the database
   * @returns {Boolean} - operation status
   */
  Database.prototype.drop = function () {
    for (var i = 0, len = this.data.length; i < len; i++) {
      this.delete(this.data[i]);
    }
    this.conf.driver.removeItem('__data');
    this.data.length = 0;
    return this.data.length === 0;
  };

  /**
   * Loading entries from driver
   * @private
   * @returns {Array|null} - operation status
   */
  Database.prototype.load = function () {
    return this.conf.driver.getItem('__data') ?
      this.conf.driver.getItem('__data').split(',') :
      null;
  };

  /**
   * Building the index for an entry
   * @private
   * @param {Object} obj - entry to build index of
   */
  Database.prototype.buildIndex = function ( obj ) {
    var key, index, value = [obj[this.conf.uniqueKey]];
    for (var property in obj) {
      if (this.conf.indexedKeys.indexOf(property) !== -1) {
        key = property + ':' + obj[property];
        index = this.conf.driver.getItem(key);
        if (index !== null) {
          index.push(obj[this.conf.uniqueKey]);
          value = index;
        }
        this.conf.driver.setItem(key, value);
      }
    }
  };

  /**
   * Destroying the index for a entry
   * @private
   * @param  {Number} id - unique key of entry to destroy index for
   */
  Database.prototype.destroyIndex = function ( id ) {
    var key, index, item = this.conf.driver.getItem(id);
    if(item !== null) {
      for(var property in item) {
        if(this.conf.indexedKeys.indexOf(property) !== -1) {
          key = property + ':' + item[property];
          index = this.conf.driver.getItem(key);
          if (index !== null) {
            index.splice(index.indexOf(id), 1);
            if(index.length === 0) {
              this.conf.driver.removeItem(key);
            } else {
              this.conf.driver.setItem(key, index);
            }
          }
        }
      }
    }
  };

  /**
   * Intersecting multiple arrays
   * @param  {Array} arguments - arrays to intersect
   * @return {Array}           - intersection of given array
   */
  var intersect = function () {
    var i, shortest, nShortest, n, len, ret = [], obj = {}, nOthers;
    nOthers = arguments.length - 1;
    nShortest = arguments[0].length;
    shortest = 0;
    for (i = 0; i <= nOthers; i++) {
      n = arguments[i].length;
      if (n < nShortest) {
        shortest = i;
        nShortest = n;
      }
    }

    for (i = 0; i <= nOthers; i++) {
      n = (i === shortest) ? 0 : (i || shortest);
      len = arguments[n].length;
      for (var j = 0; j < len; j++) {
        var elem = arguments[n][j];
        if (obj[elem] === i - 1) {
          if (i === nOthers) {
            ret.push(elem);
            obj[elem] = 0;
          } else {
            obj[elem] = i;
          }
        } else if (i === 0) {
          obj[elem] = 0;
        }
      }
    }
    return ret;
  };

  exports.Database = Database;
  exports.Database.drivers = {};
} (window));

// Storage Driver
(function ( exports ) {
  'use strict';
  
  var StorageDriver = function ( conf ) {
    this.conf = exports.extend({
      name: '',
      storage: exports.localStorage
    }, conf || {});

    if (
      typeof conf.storage.getItem !== "function" || 
      typeof conf.storage.removeItem !== "function" || 
      typeof conf.storage.setItem !== "function"
    ) {
      throw "Given Storage doesn't have methods `getItem`, `setItem` and `removeItem`.";
    }
  };

  StorageDriver.prototype.setItem = function ( key, value ) {
    return this.conf.storage.setItem(this.conf.name + ':' + key, JSON.stringify(value));
  };
  
  StorageDriver.prototype.getItem = function ( key ) {
    return JSON.parse(this.conf.storage.getItem(this.conf.name + ':' + key));
  };
  
  StorageDriver.prototype.removeItem = function ( key ) {
    return this.conf.storage.removeItem(this.conf.name + ':' + key);
  };
  
  if (exports.Database) {
    exports.Database.drivers.StorageDriver = StorageDriver;
  }
} ( window ));
              
            
!
999px

Console