├── .gitignore ├── LICENSE ├── Readme.md ├── atom-browser.js ├── atom.js ├── buster.js ├── package.json └── test └── atom-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 2 | Christian Johansen 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # js-atom 2 | 3 | Clojure(Script) atoms in plain JavaScript. 4 | 5 | ## Why? 6 | 7 | Immutable values require some sort of external state management in order for 8 | your app to change state. One option is to use variables in scope to hold the 9 | current state of your app: 10 | 11 | ```js 12 | function startApp(root) { 13 | var data; 14 | 15 | function render() { 16 | React.renderComponent(AppUI(data), root); 17 | } 18 | 19 | pollForData(function (newData) { 20 | data = newData; 21 | render(); 22 | }); 23 | 24 | // ... 25 | } 26 | ``` 27 | 28 | This sort of works, but quickly becomes unwieldy. Atoms offer a formal mechanism 29 | for maintaining a single mutable reference. An atom is used to hold the current 30 | state. It offers a small API for changing (or replacing) this state, subscribing 31 | to changes, and for validating state changes. 32 | 33 | ```js 34 | function startApp(root) { 35 | var state = atom.createAtom({}); 36 | 37 | function render() { 38 | React.renderComponent(AppUI(state.deref()), root); 39 | } 40 | 41 | // Render when the state changes 42 | state.addWatch("poll-update", render); 43 | 44 | pollForNewData(function (newData) { 45 | state.replace(newData); 46 | }); 47 | 48 | // ... 49 | } 50 | ``` 51 | 52 | ## Immutability 53 | 54 | Atoms are most useful when containing immutable values, but there's nothing 55 | stopping you from sticking whatever you want in them. If you put a mutable value 56 | in the atom, you either have to make sure you don't actually mutate it, or lose 57 | some of the benefits (e.g. being able to trust past versions of the state). 58 | 59 | ## API 60 | 61 | The API is designed to mirror Clojure's atoms as closely as possible. Because 62 | atoms are references, and not values, I didn't see any problems with defining 63 | the API as methods on the atom object. 64 | 65 | #### `createAtom(val[, options])` 66 | 67 | Creates a new atom wrapping the provided value. `options` is optional, and 68 | currently only supports one option: `validator`: 69 | 70 | ```js 71 | var createAtom = require("js-atom"); 72 | var atom = createAtom([], { validator: Array.isArray }); 73 | 74 | atom.replace([1, 2, 3]); // OK 75 | atom.replace({}); // Throws exception 76 | ``` 77 | 78 | #### `atom.deref()` 79 | 80 | Returns the contained value. 81 | 82 | #### `atom.reset(val)` 83 | 84 | Replace the current state with a new value 85 | 86 | #### `atom.swap(fn[, ...])` 87 | 88 | Update the state by applying the function to the current value, and setting the 89 | return value as the new value of the atom. Any additional arguments are passed 90 | to the function as well, after the atom value, e.g.: `atom.swap(fn, 1, 2, 3)` 91 | will replace the current value with what is returned from 92 | `fn(atomValue, 1, 2, 3)`. 93 | 94 | #### `atom.addWatch(key, function (key, ref, old, new) {})` 95 | 96 | Add a function that will be called whenever the atom value changes. The key is 97 | just a string identifying this watcher - it can be used to remove the watcher 98 | again. The callback is called with four arguments whenever the state changes 99 | (e.g. with `reset` or `swap`): 100 | 101 | - `key` - The key used to register the watcher 102 | - `ref` - The atom reference 103 | - `old` - The previous value 104 | - `new` - The new value 105 | 106 | #### `removeWatch(key)` 107 | 108 | Removes the previously added watcher. 109 | 110 | #### `toString` 111 | 112 | Prints a useful string representation of the contents of the atom. 113 | 114 | ## License 115 | 116 | Copyright © 2014, Christian Johansen. js-atom uses semantic versioning. Code 117 | released under the BSD license. Documentation released under CC 118 | Attribution-Share Alike. 119 | -------------------------------------------------------------------------------- /atom-browser.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.atom=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o