0.1.1 • Published 9 years ago

tc-flux v0.1.1

Weekly downloads
2
License
MIT
Repository
bitbucket
Last release
9 years ago

Tincore-flux

Yet another Flux implementation.

Why yet another one? For the following reasons:

  • I needed a Flux for my needs,
  • I needed a Flux I could fully control and understand.

If Flux was a mature concept, and if there were some mature libraries for Flux, I could've used them. But neither Flux is mature, nor the libraries. So I decided it would be better to start with my own, than use something else that is not mature, nor stable. In this case, other wheels are not good enough for me.

How to use it?

Provided you've installed the module (using npm), there are two concepts you should become familiar with: stores and actions.

Wait, shouldn't it be three concepts, and not only two ones? What about the dispatcher? Well, it's a good question, indeed. Yes, the dispatcher is used in the library, but you don't have to bother with it. All you really need are actions and stores. Go ahead, read further, and you'll see how it all works (and how easy it is to use).

So, let's start with importing the module:

var flux = require('tc-flux');

Stores

Stores contain data for you (and, probably, for your React components).

Whenever you need a new store, you can create a new one using the createStore function. This function takes one argument, the new store's specification. Let's create a simple store, which doesn't contain any value, and does nothing interesting at all. (The purpose of such a store is to introduce the createStore function and the specification object.)

flux.createStore({
  name: 'Users'
});

And here we have our first store: it is called "Users".

So far, the store is not very useful, and all we can do with it is to request its value:

var users = flux.getStore( 'Users' );

Well, everything we get is simply undefined, since nothing is stored in our store (yet).

So, let's try to create a store, which actually stores something:

flux.createStore({
  name: 'Users',
  init: []
});

Now, we have a store "Users" with an empty array. The array can be used to store some data about users. Now, when we used the getStore function, we could get the array:

var users = flux.getStore( 'Users' );
// users is []

And now, before we proceed further, let's ask one interesting question: what would happen, if we modified the array? Let's try to do it:

var users = flux.getStore( 'Users' );
// users is []

users.push( 'yada' );
// users is ['yada']

users = flux.getStore( 'Users' );
// users is []

Don't worry about modification of the value returned by the getStore function: it won't affect the store's value at all. The store's value is safe, and can be modified only in the onData handler.

The onData handler is a function, which takes two arguments: callbacks and data. The callbacks is object that provides four functions: callbacks.setValue, callbacks.mergeValue, callbacks.updated and callbacks.notUpdated. The data is simply... data, that can affect the store's value.

The purpose of the onData handler is to process the passed data, and eventually update the store's value (using either the callbacks.setValue, or the callbacks.mergeValue function). The handler should be finished by calling either the callbacks.updated or the callbacks.notUpdated function.

Let's try to use the onData handler in our store:

flux.createStore({
  name: 'Users',
  init: [],
  onData: function(callbacks, data) {
    var new_user = data,
        store = flux.getStore('Users');
    if ( _.find( store, { name: new_user.name } ) !== undefined ) {
      // the user is already in the store
      callbacks.notUpdated();
    }
    else {
      // add a new user to the store
      store.push( new_user );
      callbacks.setValue( store );
      callbacks.updated();
    }
  }
});

Our onData handler is quite simple: it is supposed to receive a single user data. If the user is already in the store, the received data is ignored. Otherwise, the user is added to the array contained in the store.

Why the updated and notUpdated callbacks are needed? Because it's the way the onData handler tells Flux that it has finished its job (with the result of either updated or not updated data). We can listen to store's updates by registering a listener:

var usersListener = function( users_array ) {
  // do something here
};

flux.registerListener( usersListener, 'Users' );

Whenever the onData handler of the "Users" store is called, and whenever it's finished using the updated callback, our handler will be called and passed the updated data contained in the store.

Sometimes, the value contained in a store is an object. In such cases, using the callbacks.setValue requires to provide a new value for the object. Very often it would be more convenient to simpy update the object; to simply override some properties. For such purpose, the callbacks.mergeValue can be used:

flux.createStore({
  name: 'User',
  init: {
    name: 'Bob',
    age:  24
  },
  onData: function(callbacks, data) {
    if ( data.hasOwnProperty('age') ) {
      callbacks.mergeValue({age: data.age});
      callbacks.updated();
    }
    else
      callbacks.notUpdated();
  }
});

Ok, it seems that we know, how to create a store, how to update its value using the onData handler, and how to listen to such updates. But how the onData handler can be called? Well, it can be called as a result of executing some action.

Actions

Actions perform tasks for you (and, probably, for your React components).

The task can be everything: calculating things, sending and receiving data between client and servers, or whatever else you'd like. Probably the most important thing about actions is the fact, that as a result of action, some data may be obtained. From any action, the obtained data may be sent to stores (to the onData handlers).

But let's start with creating an action:

flux.createAction({
  name: 'new-user',
  onRun: function(callbacks, data) {
    callbacks.data( 'add-user', data );
    callbacks.done();
  }
});

As you can see, each action has some name. It can be any string, yet it would be wise to use some pattern for naming actions.

The onRun handler is called, when the action is executed. This handler is passed two arguments: callbacks and data.

Let's start with the callbacks. It is an object very similar to the one passed to the onData handler. The object provides two callbacks: callbacks.data and callbacks.done.

The callbacks.done callback should be called when the action is finished.

The callbacks.data callback should be called whenever new data is available, and stores should (or could) be updated. This callback is passed two arguments: the data signature and the data itself.

What is data signature? It's a string describing data. Whenever new data is received (or computed, or obtained from any other source), it is about something. This something can be expressed in the form of a string, called data signature. Using data signatures, we can describe our data as "add-user", "user-creditentials", or whatever it is. The idea behind data signatures is to classify data in some way.

Our action uses the string "add-user" as the data signature. It is information, that the data passed to the onData handlers is about a new user to add. Some stores may be interested in receiving such data, while other store may not be interested at all. How can we know that? Well, it depends on the data field in the store specification object, a field that hasn't been showed so far. So, let's show the field now:

flux.createStore({
  name: 'Users',
  data: 'add-user',
  init: [],
  onData: function(callbacks, data) {
    // do something here
  }
});

Our store's onData handler will be called only for data sent with the "add-user" signature.

It is also possible to use many signatures for one store:

flux.createStore({
  name: 'Users',
  data: ['add-user', 'modify-user', 'remove-user'],
  init: [],
  onData: function(callbacks, data) {
    // do something here
    // the data signature is in callbacks.signature
  }
});

Let's get back to actions, and to the callbacks.data callback. Now we know, that the first argument to this callback is the data signature. The second argument is the data object passed to the onData handlers of all those stores, which are interested in the used data signature. This is the way new data is passed to onData handlers (and the way the onData handlers are called).

But how the actions are actually run? Well, the bad news ;-) is that we cannot directly run them. The good news is that we can queue them to be run:

flux.queue( 'new-user', some_user_data );

Examples

In the examples directory, simple examples can be found (ok, so far there's only one...).

In order to check an example:

  1. Go to the example directory. (This is the one with the packages.json file.)
  2. Run npm install.
  3. Optional: you can run the tests with the command npm run test.
  4. Build the example with the npm run build command.
  5. Open the file build/index.html in your browser.

All the examples are made in the following steps:

  1. The tests for the example's model are written.
  2. The example's model is implemented, so the tests pass.
  3. The tests for the tincore-flux model are written.
  4. The tincore-flux model is implemented, so the tests pass.
  5. The React application is written.

The future

So far, tc-flux has been tested in some simple applications. The tests went well, and the decision of making the project Open Source was made. It doesn't matter, though, that the library is finished. It is possible, that in the future the following features will be added. The first on the list is some way for blocking/unblocking actions (as a result of other actions).

0.1.1

9 years ago

0.1.0

9 years ago

0.0.1

9 years ago