From 23a9638c9a313d1910fdf46bc0799ab25672a0ea Mon Sep 17 00:00:00 2001 From: marquex Date: Thu, 3 Mar 2016 18:50:00 +0100 Subject: [PATCH] Adds support for class instances. --- CHANGELOG.md | 7 + README.md | 64 ++- build/freezer.js | 717 +++++++++++++++------------------ build/freezer.min.js | 4 +- gulpfile.js | 13 +- package.json | 20 +- src/emitter.js | 2 +- src/freezer.js | 93 ++--- src/frozen.js | 167 ++------ src/mixins.js | 164 -------- src/nodeCreator.js | 181 +++++++++ src/utils.js | 121 +++--- tests/browser.html | 3 +- tests/freezer-spec.js | 29 +- tests/listener-spec.js | 47 ++- tests/mutable/listener-spec.js | 8 +- 16 files changed, 778 insertions(+), 862 deletions(-) delete mode 100644 src/mixins.js create mode 100644 src/nodeCreator.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9335d8c..3f550d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ # Freezer Changelog +###v0.10.0 +* Fixes not returning the pivot on non-modifying updates. Thanks @oigewan +* Adds `getEventHub` method. +* Refactors event triggering on nodes. Now the freezer object shares the events with the root node. +* The prototype of the objects are now preserved, so class instances can now be saved in a freezer store. + + ###v0.9.6 * Fixes orphan markDirty call. * Fixes setting null value. Thanks @kuraga diff --git a/README.md b/README.md index 521bdf6..4593be1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # Freezer -A tree data structure that is always updated from the root, 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 triggered 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) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/arqex/freezer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -Are you looking for an immutable.js alternative? Freezer is made with React.js in mind and uses real immutable structures. It is the perfect store for your Flux implementation. +Are you looking for an immutable.js alternative? Freezer is made with React.js in mind and it uses real immutable structures. It is the perfect store for you application. + +Using freezer you don't need a flux framework, just listen to its `update` events to refresh your UI. [Goodbye boilerplate code!](https://medium.com/@arqex/react-the-simple-way-cabdf1f42f12). What makes Freezer special is: @@ -22,7 +24,7 @@ Do you want to know more? * [Demos](#demos) * [Installation](#installation) * [Example](#example-of-use) -* [Motivation](#why-another-store) +* [Motivation](#why-another-state-holder) * [Freezer API](#api) * [Updating the data](#update-methods) * [Events](#events-1) @@ -38,7 +40,7 @@ Do you want to know more? * [How to use React and Freezer together](https://medium.com/@arqex/react-the-simple-way-cabdf1f42f12). * [A JSON editor with undo and redo](http://jsbin.com/hugusi/1/edit?js,output), and [here the blog article](http://arqex.com/991/json-editor-react-immutable-data) explaining it . * [The flux comparison project](https://github.com/voronianski/flux-comparison). -* [Freezer receiving data from websockets in the Flux Challenge.](https://github.com/staltz/flux-challenge/tree/master/submissions/arqex). +* [Freezer receiving data from websockets in the Flux Challenge](https://github.com/staltz/flux-challenge/tree/master/submissions/arqex). * [Use freezer with redux-devtools](https://github.com/arqex/freezer-redux-devtools). @@ -48,7 +50,7 @@ Freezer is available as a npm package. npm install freezer-js ``` -It is possible to download the [full version](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.js) (~20KB) or [minified](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.min.js) (~9KB). +It is possible to download a file to use it directly in the browser. Grab the [full version](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.js) (~20KB) or [minified one](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.min.js) (~9KB). ## Example @@ -136,13 +138,15 @@ freezer.get() === state; // true **Freezer** is inspired by other tree cursor libraries, specifically [Cortex](https://github.com/mquan/cortex), that try to solve an inconvenience of the Flux architecture: -* If you have a store with deep nested data and you need to update some value from a child component that reflects that data, you need to dispatch an action and from the top of the store look for the bit of data again to update it. That may involve a lot of extra code to propagate the change and it is more painful when you consider that the component already knew what data to update. +* If you have a store with deep nested data and you need to update some value from a child component that reflects that data, you need to dispatch an action that needs to look for the bit of data again to update it. That may involve a lot of extra code to propagate the change and it is more painful when you consider that the component already knew what data to update. On the other hand, data changes always flowing in the same direction is what makes the Flux architecure so easy to reason about. If we let every component update the data independently, we are building a mess again. So *Freezer*, instead of letting the child component update the data directly, gives every node the tools to make the change. The updates are always made by the root of the store and the data can keep flowing in just one direction. -Imagine that we have the following tree structure as our app state: ![Initial tree](img/initialTree.png) +Imagine that we have the following tree structure as our app state: + +![Initial tree](img/initialTree.png) And we have a component responsible for handling the `state.c.f` ( the yellow node ) part of the data. Its scope is just that part of the tree, so the component receives it as a prop: ```js @@ -196,7 +200,7 @@ And then, Freezer's API is really simple and only has 2 methods: `get` and `set` Returns a frozen object with the freezer data. ```js // Logs: {a: 'hola', b:[1,2, [3,4,5]], c: false } -console.log( freezer.get() ); +console.log( freezer.get() ); ``` The data returned is actually formed by arrays and objects, but they are sealed to prevent their mutation and they have some methods in them to update the store. Everytime an update is performed, `get` will return a new frozen object. @@ -219,13 +223,27 @@ freezer.set( state ); console.log( freezer.get().c ); // false ``` -#### Events +#### 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. +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 +var f = new Freezer({my: 'data'}), + hub = f.getEventHub() +; + +// 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! + +// But you don't have access to the store data with it +hub.get(); // undefined +``` + ## Update methods -Freezer data has three different types of nodes: *Hashes*, *Arrays* and *leaf nodes*. A leaf node can't be updated by itself and needs to be updated using its parent node. Every updating method returns a new immutable object with the new node result of the update: +Freezer data has three different types of nodes: *Objects*, *Arrays* and *leaf nodes*. A leaf node can't be updated by itself and needs to be updated using its parent node. Every updating method returns a new immutable object with the new node result of the update: ```js var freezer = new Freezer({obj: {a:'hola', b:'adios'}, arr: [1,2]}); @@ -236,12 +254,14 @@ var updatedArr = freezer.get().arr.unshift( 0 ); console.log( updatedArr ); // [0,1,2] // {obj: {a:'hello', b:'adios'}, arr: [0,1,2]} -console.log( freezer.get() ); +console.log( freezer.get() ); ``` -Both *Array* and *Hash* nodes have a `set` method to update or add elements to the node and a `reset` method to replace the node with other data. +Both *Array* and *Object* nodes have a `set` method to update or add elements to the node and a `reset` method to replace the node with other data. -#### set( keyOrHash, value ) +Class instances can be stored in a freezer store. They will be handled like objects but its methods will be preserved. Since the instance will be frozen, instance setter methods could be not working for update the object. + +#### set( keyOrObject, value ) Arrays and hashes can update their children using the `set` method. It accepts a hash with the keys and values to update or two arguments: the key and the value. ```js var freezer = new Freezer({obj: {a:'hola', b:'adios'}, arr: [1,2]}); @@ -257,7 +277,7 @@ console.log( freezer.get() ) ``` #### reset( newData ) -Reset/replaces the node with new data. Listeners are preserved if the new data is an `array` or `object`, so it is possible to listen to reset calls. +Reset/replaces the node with new data. Listeners are preserved if the new data is an `array` or `object`, so it is possible to listen to reset calls. ```js var freezer = new Freezer({ foobar: {a: 'a', b: 'b', c: [0, 1, 2] } }); @@ -303,7 +323,7 @@ var update = freezer.get().people.pivot() .John.set({age: 30}) .Alice.set({age: 30}) ; -console.log( update ); +console.log( update ); // {people:{ John: {age: 30}, Alice: {age: 30} } ``` @@ -322,7 +342,7 @@ freezer.on('update', function( update ){ }); freezer.get().set({test: 'change'}); -console.log('changed'); +console.log('changed'); // logs 'changed' and then 'event' on the next tick freezer.get().set({test: 'adios'}).now(); @@ -333,7 +353,7 @@ Use it in cases that you need immediate updates. For example, if you are using R Using Freezer's [`live` option](#api) is like using `now` on every update. -## Hash methods +## Object methods #### remove( keyOrKeys ) Removes elements from a hash node. It accepts a string or an array with the names of the strings to remove. @@ -418,7 +438,7 @@ Store.trigger('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. ## Batch updates -At some point you will find yourself wanting to apply multiple changes at a time. The full tree is re-generated on each change, but the only tree you probably need is the final result of all those changes. +At some point you will find yourself wanting to apply multiple changes at a time. The full tree is re-generated on each change, but the only tree you probably need is the final result of all those changes. Freezer nodes offer a `transact` method to make local modifications to them without generating intermediate frozen trees, and a `run` method to commit all the changes at once. This way your app can have really good performance. @@ -450,10 +470,10 @@ freezer.get().list; // [1000, 1, 2, ..., 999] Transactions are designed to always commit the changes, so if you start a transaction but you forget to call `run`, it will be called automatically on the next tick. -It is possible to update the child nodes of a node that is making a transaction, but it is not really recommended. Those updates will not update the store until the transaction in the parent node is commited, and that may lead to confusion if you use child nodes as common freezer nodes. Updating child nodes doesn't improve the performance much because they have a transacting parent, so it is recommended to make the changes in the transaction node and run it as soon as you have finished with the modifications to prevent undesired behavior. +It is possible to update the child nodes of a node that is making a transaction, but it is not really recommended. Those updates will not update the store until the transaction in the parent node is commited, and that may lead to confusion if you use child nodes as common freezer nodes. Updating child nodes doesn't improve the performance much because they have a transacting parent, so it is recommended to make the changes in the transaction node and run it as soon as you have finished with the modifications to prevent undesired behavior. ## Usage with React -Creating data-driven React applications using Freezer is really simple. Just wrap your top React component in order to pass the app state as a prop. Then, re-render on any state change. +Creating data-driven React applications using Freezer is really simple. Freezer is meant to be the only store for your app, then just wrap your top React component in order to pass the app state as a prop. Re-render on any state change you will have a reactive app out of the box. ```js var AppContainer = React.createClass({ @@ -468,7 +488,7 @@ var AppContainer = React.createClass({ }); ``` -Freezer can be used along with any Flux library, but it is also possible to use it in a Flux-like way without any framework. +Freezer can be used along with any Flux library, but it is also possible to *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. @@ -489,7 +509,7 @@ Listener methods that update the state are called **reactions**, ( we are buildi 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`. -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. +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). You can check this approach working in the [TodoMVC sample app](https://github.com/arqex/freezer-todomvc), or in the [flux comparison project](https://github.com/voronianski/flux-comparison). diff --git a/build/freezer.js b/build/freezer.js index fb9f84d..b6e6448 100644 --- a/build/freezer.js +++ b/build/freezer.js @@ -1,4 +1,4 @@ -/* freezer-js v0.9.6 (18-1-2016) +/* freezer-js v0.10.0 (3-3-2016) * https://github.com/arqex/freezer * By arqex * License: MIT @@ -63,55 +63,75 @@ var Utils = { } }, + /** + * Creates non-enumerable property descriptors, to be used by Object.create. + * @param {Object} attrs Properties to create descriptors + * @return {Object} A hash with the descriptors. + */ + createNE: function( attrs ){ + var ne = {}; + + for( var key in attrs ){ + ne[ key ] = { + writable: true, + configurable: true, + enumerable: false, + value: attrs[ key ] + } + } + + return ne; + }, + // nextTick - by stagas / public domain - nextTick: (function () { - var queue = [], - dirty = false, - fn, - hasPostMessage = !!global.postMessage && (typeof Window != 'undefined') && (global instanceof Window), - messageName = 'nexttick', - trigger = (function () { - return hasPostMessage - ? function trigger () { - global.postMessage(messageName, '*'); - } - : function trigger () { - setTimeout(function () { processQueue() }, 0); - }; - }()), - processQueue = (function () { - return hasPostMessage - ? function processQueue (event) { - if (event.source === global && event.data === messageName) { - event.stopPropagation(); - flushQueue(); - } + nextTick: (function () { + var queue = [], + dirty = false, + fn, + hasPostMessage = !!global.postMessage && (typeof Window != 'undefined') && (global instanceof Window), + messageName = 'nexttick', + trigger = (function () { + return hasPostMessage + ? function trigger () { + global.postMessage(messageName, '*'); + } + : function trigger () { + setTimeout(function () { processQueue() }, 0); + }; + }()), + processQueue = (function () { + return hasPostMessage + ? function processQueue (event) { + if (event.source === global && event.data === messageName) { + event.stopPropagation(); + flushQueue(); } - : flushQueue; - })() - ; - - function flushQueue () { - while (fn = queue.shift()) { - fn(); - } - dirty = false; - } - - function nextTick (fn) { - queue.push(fn); - if (dirty) return; - dirty = true; - trigger(); - } - - if (hasPostMessage) global.addEventListener('message', processQueue, true); - - nextTick.removeListener = function () { - global.removeEventListener('message', processQueue, true); - } - - return nextTick; + } + : flushQueue; + })() + ; + + function flushQueue () { + while (fn = queue.shift()) { + fn(); + } + dirty = false; + } + + function nextTick (fn) { + queue.push(fn); + if (dirty) return; + dirty = true; + trigger(); + } + + if (hasPostMessage) global.addEventListener('message', processQueue, true); + + nextTick.removeListener = function () { + global.removeEventListener('message', processQueue, true); + } + + return nextTick; })(), findPivot: function( node ){ @@ -147,15 +167,194 @@ var Utils = { } return found; - } + }, + + isLeaf: function( node ){ + var cons = node && node.constructor; + return !cons || cons == String || cons == Number || cons == Boolean; + } }; +var nodeCreator = { + init: function( Frozen ){ + + var commonMethods = { + set: function( attr, value ){ + var attrs = attr, + update = this.__.trans + ; + + if( typeof attr != 'object' ){ + attrs = {}; + attrs[ attr ] = value; + } + + if( !update ){ + for( var key in attrs ){ + update = update || this[ key ] !== attrs[ key ]; + } + + // No changes, just return the node + if( !update ) + return Utils.findPivot( this ) || this; + } + + return this.__.store.notify( 'merge', this, attrs ); + }, + + reset: function( attrs ) { + return this.__.store.notify( 'replace', this, attrs ); + }, + + getListener: function(){ + return Frozen.createListener( this ); + }, + + toJS: function(){ + var js; + if( this.constructor == Array ){ + js = new Array( this.length ); + } + else { + js = {}; + } + + Utils.each( this, function( child, i ){ + if( child && child.__ ) + js[ i ] = child.toJS(); + else + js[ i ] = child; + }); + + return js; + }, + + transact: function(){ + return this.__.store.notify( 'transact', this ); + }, + + run: function(){ + return this.__.store.notify( 'run', this ); + }, + + now: function(){ + return this.__.store.notify( 'now', this ); + }, + + pivot: function(){ + return this.__.store.notify( 'pivot', this ); + } + }; + + var arrayMethods = Utils.extend({ + push: function( el ){ + return this.append( [el] ); + }, + + append: function( els ){ + if( els && els.length ) + return this.__.store.notify( 'splice', this, [this.length, 0].concat( els ) ); + return this; + }, + + pop: function(){ + if( !this.length ) + return this; + + return this.__.store.notify( 'splice', this, [this.length -1, 1] ); + }, + + unshift: function( el ){ + return this.prepend( [el] ); + }, + + prepend: function( els ){ + if( els && els.length ) + return this.__.store.notify( 'splice', this, [0, 0].concat( els ) ); + return this; + }, + + shift: function(){ + if( !this.length ) + return this; + + return this.__.store.notify( 'splice', this, [0, 1] ); + }, + + splice: function( index, toRemove, toAdd ){ + return this.__.store.notify( 'splice', this, arguments ); + } + }, commonMethods ); + + var FrozenArray = Object.create( Array.prototype, Utils.createNE( arrayMethods ) ); + + var objectMethods = Utils.createNE( Utils.extend({ + remove: function( keys ){ + var filtered = [], + k = keys + ; + + if( keys.constructor != Array ) + k = [ keys ]; + + for( var i = 0, l = k.length; i= 0; i--) { - this.refresh( _.parents[i], node, frozen ); + if( frozen ){ + this.fixChildren( frozen, node ); } + this.refreshParents( node, frozen ); + return frozen; }, @@ -602,7 +588,7 @@ var Frozen = { frozen[ key ] = child; }); - node.__.freezeFn( frozen ); + node.__.store.freezeFn( frozen ); this.refreshParents( node, frozen ); return frozen; @@ -622,7 +608,7 @@ var Frozen = { frozen = this.copyMeta( node ), index = args[0], deleteIndex = index + args[1], - con, child + child ; // Clone the array @@ -643,10 +629,9 @@ var Frozen = { if( args.length > 1 ){ for (var i = args.length - 1; i >= 2; i--) { child = args[i]; - con = child && child.constructor; - if( con == Array || con == Object ) - child = this.freeze( child, _.notify, _.freezeFn, _.live ); + if( !Utils.isLeaf( child ) ) + child = this.freeze( child, _.store ); if( child && child.__ ) this.addParent( child, frozen ); @@ -658,7 +643,7 @@ var Frozen = { // splice Array.prototype.splice.apply( frozen, args ); - node.__.freezeFn( frozen ); + _.store.freezeFn( frozen ); this.refreshParents( node, frozen ); return frozen; @@ -765,7 +750,7 @@ var Frozen = { frozen[ key ] = child; }); - node.__.freezeFn( frozen ); + node.__.store.freezeFn( frozen ); this.refreshParents( node, frozen ); }, @@ -796,26 +781,17 @@ var Frozen = { copyMeta: function( node ){ var me = this, - frozen + frozen = nodeCreator.clone( node ), + _ = node.__ ; - if( node.constructor == Array ){ - frozen = this.createArray( node.length ); - } - else { - frozen = Object.create( Mixins.Hash ); - } - - var _ = node.__; - Utils.addNE( frozen, {__: { - notify: _.notify, + store: _.store, + updateRoot: _.updateRoot, listener: _.listener, parents: _.parents.slice( 0 ), trans: _.trans, - freezeFn: _.freezeFn, pivot: _.pivot, - live: _.live }}); if( _.pivot ) @@ -826,18 +802,18 @@ var Frozen = { refreshParents: function( oldChild, newChild ){ var _ = oldChild.__, + parents = _.parents.length, i ; - this.trigger( newChild, 'update', newChild, _.live ); - - if( !_.parents.length ){ - if( _.listener ){ - _.listener.trigger( 'immediate', oldChild, newChild ); - } + if( oldChild.__.updateRoot ){ + oldChild.__.updateRoot( oldChild, newChild ); } - else { - for (i = _.parents.length - 1; i >= 0; i--) { + if( newChild ){ + this.trigger( newChild, 'update', newChild, _.store.live ); + } + if( parents ){ + for (i = parents - 1; i >= 0; i--) { this.refresh( _.parents[i], oldChild, newChild ); } } @@ -905,32 +881,17 @@ var Frozen = { } return l; - }, - - createArray: (function(){ - // Set createArray method - if( [].__proto__ ) - return function( length ){ - var arr = new Array( length ); - arr.__proto__ = Mixins.List; - return arr; - } - return function( length ){ - var arr = new Array( length ), - methods = Mixins.arrayMethods - ; - for( var m in methods ){ - arr[ m ] = methods[ m ]; - } - return arr; - } - })() + } }; +nodeCreator.init( Frozen ); + var Freezer = function( initialValue, options ) { var me = this, - mutable = ( options && options.mutable ) || false, - live = ( options && options.live ) || live + ops = options || {}, + store = { + live: ops.live || false + } ; // Immutable data @@ -942,15 +903,13 @@ var Freezer = function( initialValue, options ) { ; if( _.listener ){ Frozen.trigger( node, 'update', 0, true ); - - if( !_.parents.length ) - _.listener.trigger('immediate', 'now'); } for (i = 0; i < _.parents.length; i++) { - notify('now', _.parents[i]); + _.store.notify( 'now', _.parents[i] ); } }; + var addToPivotTriggers = function( node ){ pivotTriggers.push( node ); if( !pivotTicking ){ @@ -960,81 +919,58 @@ var Freezer = function( initialValue, options ) { pivotTicking = 0; }); } - } - var notify = function notify( eventName, node, options ){ - var _ = node.__, - nowNode - ; - - if( eventName == 'listener' ) - return Frozen.createListener( node ); + }; + store.notify = function notify( eventName, node, options ){ if( eventName == 'now' ){ if( pivotTriggers.length ){ while( pivotTriggers.length ){ - nowNode = pivotTriggers.shift(); - triggerNow( nowNode ); + triggerNow( pivotTriggers.shift() ); } } else { triggerNow( node ); } + return node; } - var update = Frozen.update( eventName, node, options ); + var update = Frozen[eventName]( node, options ); if( eventName != 'pivot' ){ var pivot = Utils.findPivot( update ); if( pivot ) { addToPivotTriggers( update ); - return pivot; + return pivot; } } return update; }; - var freeze = function(){}; - if( !mutable ) - freeze = function( obj ){ Object.freeze( obj ); }; + store.freezeFn = ops.mutable === true ? + function(){} : + function( obj ){ Object.freeze( obj ); } + ; // Create the frozen object - frozen = Frozen.freeze( initialValue, notify, freeze, live ); - - // Listen to its changes immediately - var listener = frozen.getListener(); - - // Updating flag to trigger the event on nextTick - var updating = false; - - listener.on( 'immediate', function( prevNode, updated ){ - - if( prevNode == 'now' ){ - if( !updating ) - return; - updating = false; - return me.trigger( 'update', frozen ); + frozen = Frozen.freeze( initialValue, store ); + frozen.__.updateRoot = function( prevNode, updated ){ + if( prevNode === frozen ){ + frozen = updated; } + } - if( prevNode != frozen ) - return; - - frozen = updated; - - if( live ) - return me.trigger( 'update', updated ); + // Listen to its changes immediately + var listener = frozen.getListener(), + hub = {} + ; - // Trigger on next tick - if( !updating ){ - updating = true; - Utils.nextTick( function(){ - if( updating ){ - updating = false; - me.trigger( 'update', frozen ); - } - }); - } + Utils.each(['on', 'off', 'once', 'trigger'], function( method ){ + var attrs = {}; + attrs[ method ] = listener[method].bind(listener); + Utils.addNE( me, attrs ); + Utils.addNE( hub, attrs ); }); Utils.addNE( this, { @@ -1042,18 +978,17 @@ var Freezer = function( initialValue, options ) { return frozen; }, set: function( node ){ - var newNode = notify( 'reset', frozen, node ); - newNode.__.listener.trigger( 'immediate', frozen, newNode ); + console.log('setting'); + frozen.reset( node ); + }, + getEventHub: function(){ + return hub; } }); Utils.addNE( this, { getData: this.get, setData: this.set } ); +}; - // The event store - this._events = []; -} - -Freezer.prototype = Utils.createNonEnumerable({constructor: Freezer}, Emitter); return Freezer; })); \ No newline at end of file diff --git a/build/freezer.min.js b/build/freezer.min.js index 982181e..ca5c567 100644 --- a/build/freezer.min.js +++ b/build/freezer.min.js @@ -1,6 +1,6 @@ /* -freezer-js v0.9.6 +freezer-js v0.10.0 https://github.com/arqex/freezer MIT: https://github.com/arqex/freezer/raw/master/LICENSE */ -!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.Freezer=t()}(this,function(){"use strict";var e=new Function("return this")(),t={extend:function(e,t){for(var r in t)e[r]=t[r];return e},createNonEnumerable:function(e,t){var r={};for(var n in e)r[n]={value:e[n]};return Object.create(t||{},r)},error:function(e){var t=new Error(e);if(console)return console.error(t);throw t},each:function(e,t){var r,n,i;if(e&&e.constructor==Array)for(r=0,n=e.length;n>r;r++)t(e[r],r);else for(i=Object.keys(e),r=0,n=i.length;n>r;r++)t(e[i[r]],i[r])},addNE:function(e,t){for(var r in t)Object.defineProperty(e,r,{enumerable:!1,configurable:!0,writable:!0,value:t[r]})},nextTick:function(){function t(){for(;n=i.shift();)n();a=!1}function r(e){i.push(e),a||(a=!0,f())}var n,i=[],a=!1,o=!!e.postMessage&&"undefined"!=typeof Window&&e instanceof Window,s="nexttick",f=function(){return o?function(){e.postMessage(s,"*")}:function(){setTimeout(function(){c()},0)}}(),c=function(){return o?function(r){r.source===e&&r.data===s&&(r.stopPropagation(),t())}:t}();return o&&e.addEventListener("message",c,!0),r.removeListener=function(){e.removeEventListener("message",c,!0)},r}(),findPivot:function(e){if(e&&e.__){if(e.__.pivot)return e;for(var t,r=0,n=e.__.parents,i=0;!r&&i=0;r--)n[r].callback===t&&n.splice(r,1)}return this},trigger:function(e){var t,a,o=[].slice.call(arguments,1),s=this._events[e]||[],f=[],c=-1!=i.indexOf(e);for(c||this.trigger.apply(this,[r,e].concat(o)),t=0;t=0;t--)s.splice(f[t],1);return c||this.trigger.apply(this,[n,e].concat(o)),this}},o=t.createNonEnumerable(a),s=function(e){var t={};for(var r in e)t[r]={writable:!0,configurable:!0,enumerable:!1,value:e[r]};return t},f={set:function(e,t){var r=e,n=this.__.trans;if("object"!=typeof e&&(r={},r[e]=t),!n){for(var i in r)n=n||this[i]!==r[i];if(!n)return this}return this.__.notify("merge",this,r)},reset:function(e){return this.__.notify("replace",this,e)},getListener:function(){return this.__.notify("listener",this)},toJS:function(){var e;return e=this.constructor==Array?new Array(this.length):{},t.each(this,function(t,r){e[r]=t&&t.__?t.toJS():t}),e},transact:function(){return this.__.notify("transact",this)},run:function(){return this.__.notify("run",this)},now:function(){return this.__.notify("now",this)},pivot:function(){return this.__.notify("pivot",this)}},c=t.extend({push:function(e){return this.append([e])},append:function(e){return e&&e.length?this.__.notify("splice",this,[this.length,0].concat(e)):this},pop:function(){return this.length?this.__.notify("splice",this,[this.length-1,1]):this},unshift:function(e){return this.prepend([e])},prepend:function(e){return e&&e.length?this.__.notify("splice",this,[0,0].concat(e)):this},shift:function(){return this.length?this.__.notify("splice",this,[0,1]):this},splice:function(){return this.__.notify("splice",this,arguments)}},f),u=Object.create(Array.prototype,s(c)),h={Hash:Object.create(Object.prototype,s(t.extend({remove:function(e){var t=[],r=e;e.constructor!=Array&&(r=[e]);for(var n=0,i=r.length;i>n;n++)this.hasOwnProperty(r[n])&&t.push(r[n]);return t.length?this.__.notify("remove",this,t):this}},f))),List:u,arrayMethods:c},_={freeze:function(e,r,n,i){if(e&&e.__)return e;var a,o,s=this;return a=e.constructor==Array?this.createArray(e.length):Object.create(h.Hash),t.addNE(a,{__:{listener:!1,parents:[],notify:r,freezeFn:n,live:i||!1}}),t.each(e,function(e,t){o=e&&e.constructor,(o==Array||o==Object)&&(e=s.freeze(e,r,n,i)),e&&e.__&&s.addParent(e,a),a[t]=e}),n(a),a},update:function(e,r,n){return this[e]?this[e](r,n):t.error("Unknown update type: "+e)},reset:function(e,r){var n=this,i=e.__,a=r;return a.__||(a=this.freeze(r,i.notify,i.freezeFn,i.live)),a.__.listener=i.listener,a.__.parents=i.parents,this.fixChildren(a,e),t.each(a,function(t){t&&t.__&&(n.removeParent(e),n.addParent(t,a))}),a},merge:function(e,r){var n=e.__,i=n.trans,r=t.extend({},r);if(i){for(var a in r)i[a]=r[a];return e}var o,s,f,c,u=this,h=this.copyMeta(e),_=n.notify;t.each(e,function(t,i){return c=t&&t.__,c&&u.removeParent(t,e),(o=r[i])?(s=o&&o.constructor,(s==Array||s==Object)&&(o=u.freeze(o,_,n.freezeFn,n.live)),o&&o.__&&u.addParent(o,h),delete r[i],void(h[i]=o)):(c&&u.addParent(t,h),h[i]=t)});for(f in r)o=r[f],s=o&&o.constructor,(s==Array||s==Object)&&(o=u.freeze(o,_,n.freezeFn,n.live)),o&&o.__&&u.addParent(o,h),h[f]=o;return n.freezeFn(h),this.refreshParents(e,h),h},replace:function(e,t){var r=this,n=t&&t.constructor,i=e.__,a=t;(n==Array||n==Object)&&(a=r.freeze(t,i.notify,i.freezeFn,i.live),a.__.parents=i.parents,i.listener&&(a.__.listener=i.listener),this.trigger(a,"update",a,i.live)),!i.parents.length&&i.listener&&i.listener.trigger("immediate",e,a);for(var o=i.parents.length-1;o>=0;o--)this.refresh(i.parents[o],e,a);return a},remove:function(e,r){var n=e.__.trans;if(n){for(var i=r.length-1;i>=0;i--)delete n[r[i]];return e}var a,o=this,s=this.copyMeta(e);return t.each(e,function(t,n){a=t&&t.__,a&&o.removeParent(t,e),-1==r.indexOf(n)&&(a&&o.addParent(t,s),s[n]=t)}),e.__.freezeFn(s),this.refreshParents(e,s),s},splice:function(e,r){var n=e.__,i=n.trans;if(i)return i.splice.apply(i,r),e;var a,o,s=this,f=this.copyMeta(e),c=r[0],u=c+r[1];if(t.each(e,function(t,r){t&&t.__&&(s.removeParent(t,e),(c>r||r>=u)&&s.addParent(t,f)),f[r]=t}),r.length>1)for(var h=r.length-1;h>=2;h--)o=r[h],a=o&&o.constructor,(a==Array||a==Object)&&(o=this.freeze(o,n.notify,n.freezeFn,n.live)),o&&o.__&&this.addParent(o,f),r[h]=o;return Array.prototype.splice.apply(f,r),e.__.freezeFn(f),this.refreshParents(e,f),f},transact:function(e){var r,n=this,i=e.__.trans;return i?i:(r=e.constructor==Array?[]:{},t.each(e,function(e,t){r[t]=e}),e.__.trans=r,t.nextTick(function(){e.__.trans&&n.run(e)}),r)},run:function(e){var r=this,n=e.__.trans;if(!n)return e;t.each(n,function(t){t&&t.__&&r.removeParent(t,e)}),delete e.__.trans;var i=this.replace(e,n);return i},pivot:function(e){return e.__.pivot=1,this.unpivot(e),e},unpivot:function(e){t.nextTick(function(){e.__.pivot=0})},refresh:function(e,r,n){var i=this,a=e.__.trans,o=0;if(a)return t.each(a,function(t,s){o||t===r&&(a[s]=n,o=1,n&&n.__&&i.addParent(n,e))}),e;var s,f=this.copyMeta(e);t.each(e,function(t,a){t===r&&(t=n),t&&(s=t.__)&&(i.removeParent(t,e),i.addParent(t,f)),f[a]=t}),e.__.freezeFn(f),this.refreshParents(e,f)},fixChildren:function(e,r){var n=this;t.each(e,function(t){if(t&&t.__){if(-1!=t.__.parents.indexOf(e))return n.fixChildren(t);if(1==t.__.parents.length)return t.__.parents=[e];r&&n.removeParent(t,r),n.addParent(t,e)}})},copyMeta:function(e){var r;r=e.constructor==Array?this.createArray(e.length):Object.create(h.Hash);var n=e.__;return t.addNE(r,{__:{notify:n.notify,listener:n.listener,parents:n.parents.slice(0),trans:n.trans,freezeFn:n.freezeFn,pivot:n.pivot,live:n.live}}),n.pivot&&this.unpivot(r),r},refreshParents:function(e,t){var r,n=e.__;if(this.trigger(t,"update",t,n.live),n.parents.length)for(r=n.parents.length-1;r>=0;r--)this.refresh(n.parents[r],e,t);else n.listener&&n.listener.trigger("immediate",e,t)},removeParent:function(e,t){var r=e.__.parents,n=r.indexOf(t);-1!=n&&r.splice(n,1)},addParent:function(e,t){var r=e.__.parents,n=r.indexOf(t);-1==n&&(r[r.length]=t)},trigger:function(e,r,n,i){var a=e.__.listener;if(a){var o=a.ticking;if(i)return void((o||n)&&(a.ticking=0,a.trigger(r,o||n)));a.ticking=n,o||t.nextTick(function(){if(a.ticking){var e=a.ticking;a.ticking=0,a.trigger(r,e)}})}},createListener:function(e){var t=e.__.listener;return t||(t=Object.create(o,{_events:{value:{},writable:!0}}),e.__.listener=t),t},createArray:function(){return[].__proto__?function(e){var t=new Array(e);return t.__proto__=h.List,t}:function(e){var t=new Array(e),r=h.arrayMethods;for(var n in r)t[n]=r[n];return t}}()},l=function(e,r){var n,i=this,a=r&&r.mutable||!1,o=r&&r.live||o,s=[],f=0,c=function(e){var t,r=e.__;for(r.listener&&(_.trigger(e,"update",0,!0),r.parents.length||r.listener.trigger("immediate","now")),t=0;tn;n++)e(t[n],n);else for(i=Object.keys(t),n=0,r=i.length;r>n;n++)e(t[i[n]],i[n])},addNE:function(t,e){for(var n in e)Object.defineProperty(t,n,{enumerable:!1,configurable:!0,writable:!0,value:e[n]})},createNE:function(t){var e={};for(var n in t)e[n]={writable:!0,configurable:!0,enumerable:!1,value:t[n]};return e},nextTick:function(){function e(){for(;r=i.shift();)r();o=!1}function n(t){i.push(t),o||(o=!0,f())}var r,i=[],o=!1,s=!!t.postMessage&&"undefined"!=typeof Window&&t instanceof Window,a="nexttick",f=function(){return s?function(){t.postMessage(a,"*")}:function(){setTimeout(function(){c()},0)}}(),c=function(){return s?function(n){n.source===t&&n.data===a&&(n.stopPropagation(),e())}:e}();return s&&t.addEventListener("message",c,!0),n.removeListener=function(){t.removeEventListener("message",c,!0)},n}(),findPivot:function(t){if(t&&t.__){if(t.__.pivot)return t;for(var e,n=0,r=t.__.parents,i=0;!n&&ir;r++)this.hasOwnProperty(n[r])&&e.push(n[r]);return e.length?this.__.store.notify("remove",this,e):this}},n)),s=Object.create(Object.prototype,o),a=function(){return[].__proto__?function(t){var e=new Array(t);return e.__proto__=i,e}:function(t){var e=new Array(t);for(var n in r)e[n]=r[n];return e}}();this.clone=function(t){var e=t.constructor;return e==Array?a(t.length):e===Object?Object.create(s):(console.log("instance"),Object.create(e.prototype,o))}}},r="beforeAll",i="afterAll",o=[r,i],s={on:function(t,e,n){var r=this._events[t]||[];return r.push({callback:e,once:n}),this._events[t]=r,this},once:function(t,e){return this.on(t,e,!0)},off:function(t,e){if("undefined"==typeof t)this._events={};else if("undefined"==typeof e)this._events[t]=[];else{var n,r=this._events[t]||[];for(n=r.length-1;n>=0;n--)r[n].callback===e&&r.splice(n,1)}return this},trigger:function(t){var e,n,s=[].slice.call(arguments,1),a=this._events[t]||[],f=[],c=-1!=o.indexOf(t);for(c||this.trigger.apply(this,[r,t].concat(s)),e=0;e=0;e--)a.splice(f[e],1);return c||this.trigger.apply(this,[i,t].concat(s)),this}},a=e.createNonEnumerable(s),f={freeze:function(t,r){if(t&&t.__)return t;var i=this,o=n.clone(t);return e.addNE(o,{__:{listener:!1,parents:[],store:r}}),e.each(t,function(t,n){e.isLeaf(t)||(t=i.freeze(t,r)),t&&t.__&&i.addParent(t,o),o[n]=t}),r.freezeFn(o),o},merge:function(t,n){var r=t.__,i=r.trans,n=e.extend({},n);if(i){for(var o in n)i[o]=n[o];return t}var s,a,f,c=this,u=this.copyMeta(t),h=r.store;e.each(t,function(r,i){return f=r&&r.__,f&&c.removeParent(r,t),(s=n[i])?(e.isLeaf(s)||(s=c.freeze(s,h)),s&&s.__&&c.addParent(s,u),delete n[i],void(u[i]=s)):(f&&c.addParent(r,u),u[i]=r)});for(a in n)s=n[a],e.isLeaf(s)||(s=c.freeze(s,h)),s&&s.__&&c.addParent(s,u),u[a]=s;return r.store.freezeFn(u),this.refreshParents(t,u),u},replace:function(t,n){var r=this,i=t.__,o=n;return e.isLeaf(n)||(o=r.freeze(n,i.store),o.__.parents=i.parents,o.__.updateRoot=i.updateRoot,i.listener&&(o.__.listener=i.listener)),o&&this.fixChildren(o,t),this.refreshParents(t,o),o},remove:function(t,n){var r=t.__.trans;if(r){for(var i=n.length-1;i>=0;i--)delete r[n[i]];return t}var o,s=this,a=this.copyMeta(t);return e.each(t,function(e,r){o=e&&e.__,o&&s.removeParent(e,t),-1==n.indexOf(r)&&(o&&s.addParent(e,a),a[r]=e)}),t.__.store.freezeFn(a),this.refreshParents(t,a),a},splice:function(t,n){var r=t.__,i=r.trans;if(i)return i.splice.apply(i,n),t;var o,s=this,a=this.copyMeta(t),f=n[0],c=f+n[1];if(e.each(t,function(e,n){e&&e.__&&(s.removeParent(e,t),(f>n||n>=c)&&s.addParent(e,a)),a[n]=e}),n.length>1)for(var u=n.length-1;u>=2;u--)o=n[u],e.isLeaf(o)||(o=this.freeze(o,r.store)),o&&o.__&&this.addParent(o,a),n[u]=o;return Array.prototype.splice.apply(a,n),r.store.freezeFn(a),this.refreshParents(t,a),a},transact:function(t){var n,r=this,i=t.__.trans;return i?i:(n=t.constructor==Array?[]:{},e.each(t,function(t,e){n[e]=t}),t.__.trans=n,e.nextTick(function(){t.__.trans&&r.run(t)}),n)},run:function(t){var n=this,r=t.__.trans;if(!r)return t;e.each(r,function(e,r){e&&e.__&&n.removeParent(e,t)}),delete t.__.trans;var i=this.replace(t,r);return i},pivot:function(t){return t.__.pivot=1,this.unpivot(t),t},unpivot:function(t){e.nextTick(function(){t.__.pivot=0})},refresh:function(t,n,r){var i=this,o=t.__.trans,s=0;if(o)return e.each(o,function(e,a){s||e===n&&(o[a]=r,s=1,r&&r.__&&i.addParent(r,t))}),t;var a,f=this.copyMeta(t);e.each(t,function(e,o){e===n&&(e=r),e&&(a=e.__)&&(i.removeParent(e,t),i.addParent(e,f)),f[o]=e}),t.__.store.freezeFn(f),this.refreshParents(t,f)},fixChildren:function(t,n){var r=this;e.each(t,function(e){if(e&&e.__){if(-1!=e.__.parents.indexOf(t))return r.fixChildren(e);if(1==e.__.parents.length)return e.__.parents=[t];n&&r.removeParent(e,n),r.addParent(e,t)}})},copyMeta:function(t){var r=n.clone(t),i=t.__;return e.addNE(r,{__:{store:i.store,updateRoot:i.updateRoot,listener:i.listener,parents:i.parents.slice(0),trans:i.trans,pivot:i.pivot}}),i.pivot&&this.unpivot(r),r},refreshParents:function(t,e){var n,r=t.__,i=r.parents.length;if(t.__.updateRoot&&t.__.updateRoot(t,e),e&&this.trigger(e,"update",e,r.store.live),i)for(n=i-1;n>=0;n--)this.refresh(r.parents[n],t,e)},removeParent:function(t,e){var n=t.__.parents,r=n.indexOf(e);-1!=r&&n.splice(r,1)},addParent:function(t,e){var n=t.__.parents,r=n.indexOf(e);-1==r&&(n[n.length]=e)},trigger:function(t,n,r,i){var o=t.__.listener;if(o){var s=o.ticking;if(i)return void((s||r)&&(o.ticking=0,o.trigger(n,s||r)));o.ticking=r,s||e.nextTick(function(){if(o.ticking){var t=o.ticking;o.ticking=0,o.trigger(n,t)}})}},createListener:function(t){var e=t.__.listener;return e||(e=Object.create(a,{_events:{value:{},writable:!0}}),t.__.listener=e),e}};n.init(f);var c=function(t,n){var r,i=this,o=n||{},s={live:o.live||!1},a=[],c=0,u=function(t){var e,n=t.__;for(n.listener&&f.trigger(t,"update",0,!0),e=0;e= 0; i--) { - this.refresh( _.parents[i], node, frozen ); + if( frozen ){ + this.fixChildren( frozen, node ); } + this.refreshParents( node, frozen ); + return frozen; }, @@ -216,7 +160,7 @@ var Frozen = { frozen[ key ] = child; }); - node.__.freezeFn( frozen ); + node.__.store.freezeFn( frozen ); this.refreshParents( node, frozen ); return frozen; @@ -236,7 +180,7 @@ var Frozen = { frozen = this.copyMeta( node ), index = args[0], deleteIndex = index + args[1], - con, child + child ; // Clone the array @@ -257,10 +201,9 @@ var Frozen = { if( args.length > 1 ){ for (var i = args.length - 1; i >= 2; i--) { child = args[i]; - con = child && child.constructor; - if( con == Array || con == Object ) - child = this.freeze( child, _.notify, _.freezeFn, _.live ); + if( !Utils.isLeaf( child ) ) + child = this.freeze( child, _.store ); if( child && child.__ ) this.addParent( child, frozen ); @@ -272,7 +215,7 @@ var Frozen = { // splice Array.prototype.splice.apply( frozen, args ); - node.__.freezeFn( frozen ); + _.store.freezeFn( frozen ); this.refreshParents( node, frozen ); return frozen; @@ -379,7 +322,7 @@ var Frozen = { frozen[ key ] = child; }); - node.__.freezeFn( frozen ); + node.__.store.freezeFn( frozen ); this.refreshParents( node, frozen ); }, @@ -410,26 +353,17 @@ var Frozen = { copyMeta: function( node ){ var me = this, - frozen + frozen = nodeCreator.clone( node ), + _ = node.__ ; - if( node.constructor == Array ){ - frozen = this.createArray( node.length ); - } - else { - frozen = Object.create( Mixins.Hash ); - } - - var _ = node.__; - Utils.addNE( frozen, {__: { - notify: _.notify, + store: _.store, + updateRoot: _.updateRoot, listener: _.listener, parents: _.parents.slice( 0 ), trans: _.trans, - freezeFn: _.freezeFn, pivot: _.pivot, - live: _.live }}); if( _.pivot ) @@ -440,18 +374,18 @@ var Frozen = { refreshParents: function( oldChild, newChild ){ var _ = oldChild.__, + parents = _.parents.length, i ; - this.trigger( newChild, 'update', newChild, _.live ); - - if( !_.parents.length ){ - if( _.listener ){ - _.listener.trigger( 'immediate', oldChild, newChild ); - } + if( oldChild.__.updateRoot ){ + oldChild.__.updateRoot( oldChild, newChild ); } - else { - for (i = _.parents.length - 1; i >= 0; i--) { + if( newChild ){ + this.trigger( newChild, 'update', newChild, _.store.live ); + } + if( parents ){ + for (i = parents - 1; i >= 0; i--) { this.refresh( _.parents[i], oldChild, newChild ); } } @@ -519,27 +453,10 @@ var Frozen = { } return l; - }, - - createArray: (function(){ - // Set createArray method - if( [].__proto__ ) - return function( length ){ - var arr = new Array( length ); - arr.__proto__ = Mixins.List; - return arr; - } - return function( length ){ - var arr = new Array( length ), - methods = Mixins.arrayMethods - ; - for( var m in methods ){ - arr[ m ] = methods[ m ]; - } - return arr; - } - })() + } }; + +nodeCreator.init( Frozen ); //#build module.exports = Frozen; diff --git a/src/mixins.js b/src/mixins.js deleted file mode 100644 index b0976ce..0000000 --- a/src/mixins.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -var Utils = require( './utils.js' ); - -//#build - -/** - * Creates non-enumerable property descriptors, to be used by Object.create. - * @param {Object} attrs Properties to create descriptors - * @return {Object} A hash with the descriptors. - */ -var createNE = function( attrs ){ - var ne = {}; - - for( var key in attrs ){ - ne[ key ] = { - writable: true, - configurable: true, - enumerable: false, - value: attrs[ key ] - } - } - - return ne; -}; - -var commonMethods = { - set: function( attr, value ){ - var attrs = attr, - update = this.__.trans - ; - - if( typeof attr != 'object' ){ - attrs = {}; - attrs[ attr ] = value; - } - - if( !update ){ - for( var key in attrs ){ - update = update || this[ key ] !== attrs[ key ]; - } - - // No changes, just return the node - if( !update ) - return Utils.findPivot( this ) || this; - } - - return this.__.notify( 'merge', this, attrs ); - }, - - reset: function( attrs ) { - return this.__.notify( 'replace', this, attrs ); - }, - - getListener: function(){ - return this.__.notify( 'listener', this ); - }, - - toJS: function(){ - var js; - if( this.constructor == Array ){ - js = new Array( this.length ); - } - else { - js = {}; - } - - Utils.each( this, function( child, i ){ - if( child && child.__ ) - js[ i ] = child.toJS(); - else - js[ i ] = child; - }); - - return js; - }, - - transact: function(){ - return this.__.notify( 'transact', this ); - }, - - run: function(){ - return this.__.notify( 'run', this ); - }, - - now: function(){ - return this.__.notify( 'now', this ); - }, - - pivot: function(){ - return this.__.notify( 'pivot', this ); - } -}; - -var arrayMethods = Utils.extend({ - push: function( el ){ - return this.append( [el] ); - }, - - append: function( els ){ - if( els && els.length ) - return this.__.notify( 'splice', this, [this.length, 0].concat( els ) ); - return this; - }, - - pop: function(){ - if( !this.length ) - return this; - - return this.__.notify( 'splice', this, [this.length -1, 1] ); - }, - - unshift: function( el ){ - return this.prepend( [el] ); - }, - - prepend: function( els ){ - if( els && els.length ) - return this.__.notify( 'splice', this, [0, 0].concat( els ) ); - return this; - }, - - shift: function(){ - if( !this.length ) - return this; - - return this.__.notify( 'splice', this, [0, 1] ); - }, - - splice: function( index, toRemove, toAdd ){ - return this.__.notify( 'splice', this, arguments ); - } -}, commonMethods ); - -var FrozenArray = Object.create( Array.prototype, createNE( arrayMethods ) ); - -var Mixins = { - -Hash: Object.create( Object.prototype, createNE( Utils.extend({ - remove: function( keys ){ - var filtered = [], - k = keys - ; - - if( keys.constructor != Array ) - k = [ keys ]; - - for( var i = 0, l = k.length; i -
@@ -25,4 +24,4 @@ mocha.run(); - \ No newline at end of file + diff --git a/tests/freezer-spec.js b/tests/freezer-spec.js index 8bc4803..bcd9c9b 100644 --- a/tests/freezer-spec.js +++ b/tests/freezer-spec.js @@ -38,7 +38,7 @@ describe("Freezer test", function(){ it( "Reset with a previous state", function( done ){ var counter = 0; data.set({b: 5}); - freezer.on('update', function(){ + freezer.getEventHub().on('update', function(){ if( !counter ){ freezer.set( data ); counter++; @@ -62,7 +62,7 @@ describe("Freezer test", function(){ it( "Reset with a value", function( done ){ var counter = 0; data.set({b: 5}); - freezer.on('update', function(){ + freezer.getEventHub().on('update', function(){ if( !counter ){ freezer.set( {z:{a:1}} ); counter++; @@ -228,7 +228,7 @@ describe("Freezer test", function(){ freezer.get().set( {c: freezer.get().b[0] } ); var count = 0; - freezer.on('update', function(){ + freezer.getEventHub().on('update', function(){ count++; }); @@ -315,7 +315,7 @@ describe("Freezer test", function(){ assert.equal( newData.__.pivot, 0 ); done(); } - freezer.on( 'update', handler); + freezer.getEventHub().on( 'update', handler); var updated = data.pivot() .b.set({u: 10}) @@ -335,7 +335,7 @@ describe("Freezer test", function(){ data.getListener().on('update', handler(2)); data.c.getListener().on('update', handler(3)); data.c[2].getListener().on('update', handler(4)); - freezer.on('update', handler(1)); + freezer.getEventHub().on('update', handler(1)); data = data.pivot().c[2].set( {w:4} ).now(); data = data.pivot().c[2].set( {w:5} ).now(); @@ -360,7 +360,7 @@ describe("Freezer test", function(){ data.getListener().on('update', handler(2)); data.c.getListener().on('update', handler(3)); data.c[2].getListener().on('update', handler(4)); - freezer.on('update', handler(1)); + freezer.getEventHub().on('update', handler(1)); data = data.pivot().c[2].set( {w:4} ); data = data.pivot().c[2].set( {w:5} ); @@ -390,4 +390,21 @@ describe("Freezer test", function(){ assert.strictEqual(pivotNode.a.b.c.test, 2); }); + it('Preserve instance methods', function(){ + var MyClass = function(){ console.log('oh my') }, + freezer = new Freezer({}) + ; + + MyClass.prototype.sayHello = function() { return 'Hello' }; + + var instance = new MyClass(); + freezer.get().set({instance: instance}); + freezer.get().instance.set({a:1}); + assert.equal(freezer.get().instance.a, 1); + assert.equal(freezer.get().instance.sayHello(), 'Hello'); + freezer.get().instance.set({a:2}); + assert.equal(freezer.get().instance.a, 2); + assert.equal(freezer.get().instance.sayHello(), 'Hello'); + }) + }); diff --git a/tests/listener-spec.js b/tests/listener-spec.js index e94d6a4..7217e0b 100644 --- a/tests/listener-spec.js +++ b/tests/listener-spec.js @@ -52,7 +52,7 @@ describe("Freezer events test", function(){ count = 0 ; - freezer.on( 'update', function( data ){ + freezer.getEventHub().on( 'update', function( data ){ assert.equal( data.b.c, 3 ); done(); }); @@ -74,7 +74,7 @@ describe("Freezer events test", function(){ data.getListener().on('update', handler(2)); data.c.getListener().on('update', handler(3)); data.c[2].getListener().on('update', handler(4)); - freezer.on('update', handler(1)); + freezer.getEventHub().on('update', handler(1)); data.c[2].set( {w:4} ); @@ -87,7 +87,7 @@ describe("Freezer events test", function(){ count = 0 ; - freezer.on( 'update', function( data ){ + freezer.getEventHub().on( 'update', function( data ){ if( ++count == 3 ){ assert.equal( data.b.c, 3 ); done(); @@ -110,19 +110,18 @@ describe("Freezer events test", function(){ } ; - freezer.on('update', handler(1)); - data.getListener().on('update', handler(2)); + freezer.getEventHub().on('update', handler(1)); data.c.getListener().on('update', handler(3)); data.c[2].getListener().on('update', handler(4)); data.c[2].set( {w:4} ); - assert.equal( triggered, '4321' ); + assert.equal( triggered, '431' ); }); it( "Listen to root updates", function( done ){ - freezer.on( 'update', function(){ + freezer.getEventHub().on( 'update', function(){ assert.equal( freezer.getData().b.c, 3 ); done(); }); @@ -131,7 +130,7 @@ describe("Freezer events test", function(){ }); it( "Listen to multiple root updates", function( done ){ - freezer.on( 'update', function( data ){ + freezer.getEventHub().on( 'update', function( data ){ assert.equal( data.b.c, 3 ); assert.equal( freezer.get().b.c, 3 ); done(); @@ -148,7 +147,7 @@ describe("Freezer events test", function(){ count = 0 ; - freezer.on( 'update', function( data ){ + freezer.getEventHub().on( 'update', function( data ){ if( ++count == 3 ){ assert.equal( data.b.c, 3 ); assert.equal( freezer.get().b.c, 3 ); @@ -195,7 +194,7 @@ describe("Freezer events test", function(){ data.b.set( {c: 3} ); - freezer.on( 'update', function(){ + freezer.getEventHub().on( 'update', function(){ assert.deepEqual( freezer.getData(), data ); done(); }); @@ -257,7 +256,7 @@ describe("Freezer events test", function(){ it( "Reset of node should trigger an update", function( done ){ var foobar = { foo: 'bar', bar: 'foo' }; - freezer.on( 'update', function( newData ){ + freezer.getEventHub().on( 'update', function( newData ){ assert.deepEqual( newData.b, foobar ); done(); }); @@ -294,13 +293,13 @@ describe("Freezer events test", function(){ called = true; assert.equal( update.b, undefined ); - freezer.off('update', handler); + freezer.getEventHub().off('update', handler); update.remove('a'); setTimeout( done, 100 ); } ; - freezer.on('update', handler); + freezer.getEventHub().on('update', handler); data.remove('b'); }); @@ -312,9 +311,9 @@ describe("Freezer events test", function(){ assert.equal( freezer.get().a, 2 ); triggered++; }; - freezer.on( 'update', handler ); + freezer.getEventHub().on( 'update', handler ); freezer.get().set({a:2}).now(); - freezer.off( 'update', handler ); + freezer.getEventHub().off( 'update', handler ); handler = function( update ){ @@ -322,9 +321,9 @@ describe("Freezer events test", function(){ assert.equal( freezer.get().b, undefined ); triggered++; }; - freezer.on( 'update', handler ); + freezer.getEventHub().on( 'update', handler ); freezer.get().remove('b').now(); - freezer.off( 'update', handler ); + freezer.getEventHub().off( 'update', handler ); handler = function( update ){ @@ -332,9 +331,9 @@ describe("Freezer events test", function(){ assert.equal( freezer.get().c[3], 3 ); triggered++; }; - freezer.on( 'update', handler ); + freezer.getEventHub().on( 'update', handler ); freezer.get().c.push(3).now(); - freezer.off( 'update', handler ); + freezer.getEventHub().off( 'update', handler ); handler = function( update ){ @@ -343,9 +342,9 @@ describe("Freezer events test", function(){ assert.equal( update.c.length, 3 ); triggered++; }; - freezer.on( 'update', handler ); + freezer.getEventHub().on( 'update', handler ); freezer.get().c.shift().now(); - freezer.off( 'update', handler ); + freezer.getEventHub().off( 'update', handler ); assert.equal( triggered, 4 ); }); @@ -362,7 +361,7 @@ describe("Freezer events test", function(){ data.getListener().on('update', handler(2)); data.c.getListener().on('update', handler(3)); data.c[2].getListener().on('update', handler(4)); - freezer.on('update', handler(1)); + freezer.getEventHub().on('update', handler(1)); data.c[2].set( {w:4} ).now(); @@ -370,7 +369,7 @@ describe("Freezer events test", function(){ }); it( "Now must trigger just one event", function( done ){ - freezer.on('update', function( update ){ + freezer.getEventHub().on('update', function( update ){ // If we get here and call done twice // an error will be thrown assert.equal( update.a, 10 ); @@ -393,7 +392,7 @@ describe("Freezer events test", function(){ }) ; - freezer.trigger('someEvent', 1, 2); + freezer.getEventHub().trigger('someEvent', 1, 2); setTimeout( function(){ assert.equal( out, 'someEvent12 event someEvent12' ); diff --git a/tests/mutable/listener-spec.js b/tests/mutable/listener-spec.js index d004f99..fc80ccf 100644 --- a/tests/mutable/listener-spec.js +++ b/tests/mutable/listener-spec.js @@ -47,7 +47,7 @@ describe("Freezer events test", function(){ it( "Listen to root updates", function( done ){ - freezer.on( 'update', function(){ + freezer.getEventHub().on( 'update', function(){ assert.equal( freezer.getData().b.c, 3 ); done(); }); @@ -88,7 +88,7 @@ describe("Freezer events test", function(){ data.b.set( {c: 3} ); - freezer.on( 'update', function(){ + freezer.getEventHub().on( 'update', function(){ assert.deepEqual( freezer.getData(), data ); done(); }); @@ -150,7 +150,7 @@ describe("Freezer events test", function(){ it( "Reset of node should trigger an update", function( done ){ var foobar = { foo: 'bar', bar: 'foo' }; - freezer.on( 'update', function( newData ){ + freezer.getEventHub().on( 'update', function( newData ){ assert.deepEqual( newData.b, foobar ); done(); }); @@ -178,4 +178,4 @@ describe("Freezer events test", function(){ freezer.getData().b.set('foo', 'bar'); }); -}); \ No newline at end of file +});