Skip to content

Commit

Permalink
Updates docs and bumps to v0.12.0
Browse files Browse the repository at this point in the history
  • Loading branch information
arqex committed May 13, 2017
1 parent 9c8c119 commit b7f64f6
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 47 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Freezer Changelog
###v0.12.0
* Adds a warning mechanism to detect errors quickly.
* Adds warning for setting non numeric attributes for arrays.
* Adds warning for updating detached nodes.
* Adds method `sort` for array.
* `trigger` method for events has been renamed to `emit`, added a deprecation warning for `trigger` method.
* Updates to the docs. Thanks to @YPCrumble @ahmadnassri @dantman.
* Fixes errors with strict Content Security Policy. Thanks to @greghuc.


###v0.11.2
* Fixes deep children losing the parent reference on resetting the state.

Expand Down
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Freezer

A tree data structure that emits events on updates, even if the modification is triggered by one of the leaves, making it easier to think in a reactive way.
A tree data structure that emits events on updates, even if the modification is emited by one of the leaves, making it easier to think in a reactive way.

[![Build Status](https://secure.travis-ci.org/arqex/freezer.svg)](https://travis-ci.org/arqex/freezer)
[![npm version](https://badge.fury.io/js/freezer-js.svg)](http://badge.fury.io/js/freezer-js)
Expand Down Expand Up @@ -196,8 +196,8 @@ var freezer = new Freezer({hi: 'hello'}, {mutable: true, live:true});
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| **mutable** | boolean | `false` | Once you get used to freezer, you can see that immutability is not necessary if you learn that you shouldn't update the data directly. In that case, disable immutability in the case that you need a small performance boost. |
| **live** | boolean | `false` | With live mode on, freezer triggers the update events just when the changes happen, instead of batching all the changes and triggering the event on the next tick. This is useful if you want freezer to store input field values. |
| **freezeInstances** | boolean | `false` | It's possible to store class instances in freezer. They are handled like strings or numbers, added to the state like non-frozen leaves. Keep in mind that if their internal state changes, freezer won't `trigger` any update event. If you want freezer to handle them as freezer nodes, set 'freezerInstances: true'. They will be frozen and you will be able to update their attributes using freezer methods, but remember that any instance method that update its internal state may fail (the instance is frozen) and wouldn't trigger any `update` event. |
| **live** | boolean | `false` | With live mode on, freezer emits the update events just when the changes happen, instead of batching all the changes and emiting the event on the next tick. This is useful if you want freezer to store input field values. |
| **freezeInstances** | boolean | `false` | It's possible to store class instances in freezer. They are handled like strings or numbers, added to the state like non-frozen leaves. Keep in mind that if their internal state changes, freezer won't `emit` any update event. If you want freezer to handle them as freezer nodes, set 'freezerInstances: true'. They will be frozen and you will be able to update their attributes using freezer methods, but remember that any instance method that update its internal state may fail (the instance is frozen) and wouldn't emit any `update` event. |

And then, Freezer's API is really simple and only has 2 methods: `get` and `set`. A freezer object also implements the [listener API](#listener-api).

Expand Down Expand Up @@ -231,7 +231,7 @@ console.log( freezer.get().c ); // false

#### getEventHub()

Every time the data is updated, an `update` event is triggered on the freezer object. In order to use those events, *Freezer* implements the [listener API](#listener-api), and `on`, `once`, `off` and `trigger` methods are available on them.
Every time the data is updated, an `update` event is emited on the freezer object. In order to use those events, *Freezer* implements the [listener API](#listener-api), and `on`, `once`, `off` and `emit` methods are available on them.

If you need to use the events but you don't want to give access to the complete store, you can use the `getEventHub` function:
```js
Expand All @@ -241,7 +241,7 @@ var f = new Freezer({my: 'data'}),

// Now you can use freezer's event with hub
hub.on('do:action', function(){ console.log('Do it!') });
hub.trigger('do:action'); // logs Do it!
hub.emit('do:action'); // logs Do it!

// But you don't have access to the store data with it
hub.get(); // undefined
Expand Down Expand Up @@ -337,7 +337,7 @@ other children.
The pivot is removed on the next tick. This way it won't interfere with other parts of the app.

#### now()
Using `now` in a node triggers the `update` method immediately.
Using `now` in a node emits the `update` method immediately.
```js
var freezer = new Freezer({ test: 'hola' });

Expand All @@ -353,7 +353,7 @@ freezer.get().set({test: 'adios'}).now();
console.log('changed');
// logs 'event' first and 'changed' after
```
Use it in cases that you need immediate updates. For example, if you are using React and you want to store an input value outside its component, you'll need to use `now` because the user can type more than one character before the update method is triggered, losing data.
Use it in cases that you need immediate updates. For example, if you are using React and you want to store an input value outside its component, you'll need to use `now` because the user can type more than one character before the update method is emited, losing data.

Using Freezer's [`live` option](#api) is like using `now` on every update.

Expand All @@ -374,11 +374,12 @@ console.log( updated ); //{a: 'hola'}


## Array methods
Array nodes have modified versions of the `push`, `pop`, `unshift`, `shift` and `splice` methods that update the cursor and return the new node, instead of updating the immutable array node ( that would be impossible ).
Array nodes have modified versions of the `push`, `pop`, `unshift`, `shift`, `splice` and `sort` methods that update the cursor and return the new node, instead of updating the immutable array node ( that would be impossible ).
```js
var freezer = new Freezer({ arr: [0,1,2,3,4] });
var freezer = new Freezer({ arr: [4,3,2,1,0] });

freezer.get().arr
.sort( (a,b) => a < b ? -1 : 1 ) // [0,1,2,3,4]
.push( 5 ) // [0,1,2,3,4,5]
.pop() // [0,1,2,3,4]
.unshift( 'a' ) // ['a',0,1,2,3,4]
Expand Down Expand Up @@ -425,8 +426,8 @@ Register a function to be called when an event occurs.
Register a function to be called once when an event occurs. After being called the callback is unregistered.
#### off( eventName, callback )
Can unregister all callbacks from a listener if the `eventName` parameter is omitted, or all the callbacks for a `eventName` if the `callback` parameter is omitted.
#### trigger( eventName [, param, param, ...] )
Trigger an event on the listener. All the extra parameters will be passed to the registered callbacks. `trigger` returns the return value of the latest callback that doesn't return `undefined`.
#### emit( eventName [, param, param, ...] )
Trigger an event on the listener. All the extra parameters will be passed to the registered callbacks. `emit` returns the return value of the latest callback that doesn't return `undefined`.
```
freezer
.on('whatever', function(){
Expand All @@ -437,32 +438,32 @@ freezer
})
;
console.log(freezer.trigger('whatever')); // logs 'ok'
console.log(freezer.emit('whatever')); // logs 'ok'
```
Use `trigger` with promises to return the value of an asynchronous function when it has completed. For instance:
Use `emit` with promises to return the value of an asynchronous function when it has completed. For instance:
```
freezer
.on('whatever', function(data){
return Ajax.post('/path', data); // This async operation will return a promise
})
;
freezer.trigger('whatever', data)
freezer.emit('whatever', data)
.then((ajax_response) => {
// Now you can work with ajax_response, the result of the async operation
});
```

### Event hooks
Freezer objects and nodes also emit `beforeAll` and `afterAll` events before and after any other event. Listeners bound to these events also receive the name of the triggered event in the arguments.
Freezer objects and nodes also emit `beforeAll` and `afterAll` events before and after any other event. Listeners bound to these events also receive the name of the emited event in the arguments.
```js
var Store = new Freezer({a: 1});
Store.on('beforeAll', function( eventName, arg1, arg2 ){
console.log( event, arg1, arg2 );
});

Store.get().set({a: 2}); // Will log 'update', {a:2}, {a:1}
Store.trigger('add', 4, 5); // Will log 'add', 4, 5
Store.emit('add', 4, 5); // Will log 'add', 4, 5
```
This is a nice way of binding [reactions](#usage-with-react) to more than one type of event.
It is possible to add some changes to the state inside the `beforeAll` event, so they will be available for the `update` event handlers.
Expand Down Expand Up @@ -543,11 +544,11 @@ var AppContainer = React.createClass({
```
You can use freezer's update methods in your components, or *use it in a Flux-like way without any framework*.

Instead of calling actions we can trigger custom events, thanks to the open event system built in Freezer. Those events accept any number of parameters.
Instead of calling actions we can emit custom events, thanks to the open event system built in Freezer. Those events accept any number of parameters.

```js
// State is the Freezer object
freezer.trigger('products:addToCart', product, cart);
freezer.emit('products:addToCart', product, cart);
```

A dispatcher is not needed either, you can listen to those events directly in the Freezer object.
Expand All @@ -560,7 +561,7 @@ freezer.on('products:addToCart', function (product, cart) {

Listener methods that update the state are called **reactions**, ( we are building reactive applications, are't we? ). It is nice to organize them in files by their domain, as though they were flux stores, but with the difference that all the domains store the data in the same Freezer object.

If you need to coordinate state updates, you can trigger new events when a reaction finishes, or listen to specific nodes, without the need for `waitFor`.
If you need to coordinate state updates, you can emit new events when a reaction finishes, or listen to specific nodes, without the need for `waitFor`.

This is all it takes to understand Flux apps with Freezer. No complex concepts like observers, reducers, payloads or action creators. Just events and almost no boilerplate code. It's [the simplest way of using React](https://medium.com/@arqex/react-the-simple-way-cabdf1f42f12).

Expand Down
81 changes: 54 additions & 27 deletions build/freezer.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,28 +183,33 @@ var Utils = {
);
},

warn: function( condition, msg ){
warn: function(){
var args;
if( typeof process === 'undefined' || process.env.NODE_ENV !== 'production' ){
if( !condition && typeof console !== 'undefined' ){
console.warn( 'Freezer.js WARNING: ' + msg );
if( !arguments[0] && typeof console !== 'undefined' ){
args = Array.prototype.slice.call( arguments, 1 );
args[0] = 'Freezer.js WARNING: ' + args[0];
console.warn.apply( console, args );
}
}
}
};


var nodeCreator = {
init: function( Frozen ){

var commonMethods = {
set: function( attr, value ){
var attrs = attr,
update = this.__.trans,
isArray = this.constructor === Array
isArray = this.constructor === Array,
msg = 'Freezer arrays only accept numeric attributes, given: '
;

if( typeof attr !== 'object' ){
if( isArray && parseInt(attr) != attr ){
Utils.warn( true, 'Freezer arrays only accept numeric attributes, given: ' + attr );
Utils.warn( 0, msg + attr );
return Utils.findPivot( this ) || this;
}
attrs = {};
Expand All @@ -214,7 +219,8 @@ var nodeCreator = {
if( !update ){
for( var key in attrs ){
if( isArray && parseInt(key) != key ){
Utils.warn( true, 'Freezer arrays only accept numeric attributes, given: ' + key );
Utils.warn( 0, msg + key );
return Utils.findPivot( this ) || this;
}
else {
update = update || this[ key ] !== attrs[ key ];
Expand All @@ -226,11 +232,12 @@ var nodeCreator = {
return Utils.findPivot( this ) || this;
}

return this.__.store.notify( 'merge', this, attrs );
var name = isArray ? 'array.set' : 'object.set';
return this.__.store.notify( 'merge', this, attrs, name );
},

reset: function( attrs ) {
return this.__.store.notify( 'replace', this, attrs );
return this.__.store.notify( 'replace', this, attrs, 'object.replace' );
},

getListener: function(){
Expand Down Expand Up @@ -275,41 +282,47 @@ var nodeCreator = {

var arrayMethods = Utils.extend({
push: function( el ){
return this.append( [el] );
return this.append( [el], 'array.push' );
},

append: function( els ){
append: function( els, name ){
if( els && els.length )
return this.__.store.notify( 'splice', this, [this.length, 0].concat( els ) );
return this.__.store.notify( 'splice', this, [this.length, 0].concat( els ), name || 'array.append' );
return this;
},

pop: function(){
if( !this.length )
return this;

return this.__.store.notify( 'splice', this, [this.length -1, 1] );
return this.__.store.notify( 'splice', this, [this.length -1, 1], 'array.pop' );
},

unshift: function( el ){
return this.prepend( [el] );
return this.prepend( [el], 'array.unshift' );
},

prepend: function( els ){
if( els && els.length )
return this.__.store.notify( 'splice', this, [0, 0].concat( els ) );
return this.__.store.notify( 'splice', this, [0, 0].concat( els ), 'array.prepend' );
return this;
},

shift: function(){
if( !this.length )
return this;

return this.__.store.notify( 'splice', this, [0, 1] );
return this.__.store.notify( 'splice', this, [0, 1], 'array.shift' );
},

splice: function( index, toRemove, toAdd ){
return this.__.store.notify( 'splice', this, arguments );
return this.__.store.notify( 'splice', this, arguments, 'array.splice' );
},

sort: function(){
var mutable = this.slice();
mutable.sort.apply(mutable, arguments);
return this.__.store.notify( 'replace', this, mutable, 'array.sort' );
}
}, commonMethods );

Expand All @@ -330,7 +343,7 @@ var nodeCreator = {
}

if( filtered.length )
return this.__.store.notify( 'remove', this, filtered );
return this.__.store.notify( 'remove', this, filtered, 'object.remove' );
return this;
}
}, commonMethods));
Expand Down Expand Up @@ -420,15 +433,15 @@ var emitterProto = {
return this;
},

trigger: function( eventName ){
emit: function( eventName ){
var args = [].slice.call( arguments, 1 ),
listeners = this._events[ eventName ] || [],
onceListeners = [],
special = specialEvents.indexOf( eventName ) !== -1,
i, listener, returnValue, lastValue
;

special || this.trigger.apply( this, [BEFOREALL, eventName].concat( args ) );
special || this.emit.apply( this, [BEFOREALL, eventName].concat( args ) );

// Call listeners
for (i = 0; i < listeners.length; i++) {
Expand All @@ -454,9 +467,14 @@ var emitterProto = {
listeners.splice( onceListeners[i], 1 );
}

special || this.trigger.apply( this, [AFTERALL, eventName].concat( args ) );
special || this.emit.apply( this, [AFTERALL, eventName].concat( args ) );

return returnValue;
},

trigger: function(){
Utils.warn( false, 'Method `trigger` is deprecated and will be removed from freezer in upcoming releases. Please use `emit`.' );
return this.emit.apply( this, arguments );
}
};

Expand Down Expand Up @@ -837,7 +855,7 @@ var Frozen = {
oldChild.__.updateRoot( oldChild, newChild );
}
if( newChild ){
this.trigger( oldChild, 'update', newChild, _.store.live );
this.emit( oldChild, 'update', newChild, _.store.live );
}
if( parents ){
for (i = parents - 1; i >= 0; i--) {
Expand Down Expand Up @@ -866,7 +884,7 @@ var Frozen = {
}
},

trigger: function( node, eventName, param, now ){
emit: function( node, eventName, param, now ){
var listener = node.__.listener;
if( !listener )
return;
Expand All @@ -876,7 +894,7 @@ var Frozen = {
if( now ){
if( ticking || param ){
listener.ticking = 0;
listener.trigger( eventName, ticking || param, node );
listener.emit( eventName, ticking || param, node );
}
return;
}
Expand All @@ -896,7 +914,7 @@ var Frozen = {
listener.ticking = 0;
listener.prevState = 0;

listener.trigger( eventName, updated, node );
listener.emit( eventName, updated, node );
}
});
}
Expand Down Expand Up @@ -942,7 +960,7 @@ var Freezer = function( initialValue, options ) {
if( _.listener ){
var prevState = _.listener.prevState || node;
_.listener.prevState = 0;
Frozen.trigger( prevState, 'update', node, true );
Frozen.emit( prevState, 'update', node, true );
}

for (i = 0; i < _.parents.length; i++) {
Expand All @@ -961,7 +979,13 @@ var Freezer = function( initialValue, options ) {
}
};

store.notify = function notify( eventName, node, options ){
// Last call to display info about orphan calls
var lastCall;
store.notify = function notify( eventName, node, options, name ){
if( name ){
lastCall = {name: name, node: node, options: options};
}

if( eventName === 'now' ){
if( pivotTriggers.length ){
while( pivotTriggers.length ){
Expand Down Expand Up @@ -999,14 +1023,17 @@ var Freezer = function( initialValue, options ) {
if( prevNode === frozen ){
frozen = updated;
}
else if( lastCall ) {
Utils.warn( false, 'Method ' + lastCall.name + ' called on a detached node.', lastCall.node, lastCall.options );
}
}

// Listen to its changes immediately
var listener = frozen.getListener(),
hub = {}
;

Utils.each(['on', 'off', 'once', 'trigger'], function( method ){
Utils.each(['on', 'off', 'once', 'emit'], function( method ){
var attrs = {};
attrs[ method ] = listener[method].bind(listener);
Utils.addNE( me, attrs );
Expand Down
Loading

0 comments on commit b7f64f6

Please sign in to comment.