Fluxo is a simple, lightweight (~300LOC) and dependency free data infrastructure lib based on Facebook Flux and Backbone.js. It's compatible with React.js, but you can use with whatever you want to the view/component layer.
JavaScript HTML
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
assets
dist
spec
src
.gitignore
.npmignore
.travis.yml
Gruntfile.js
README.md
bower.json
package.json

README.md

Fluxo

Build Status

Fluxo is a simple, lightweight (~300LOC) and dependency free entity foundation to your javascript app. It's inspired on Backbone.js models/collections and other good ideas.

⚠️ This project is under development and experimental phase and because of this, many things may change.

☑️ Read the Getting started.

☑️ Check the TodoMVC implementation with Fluxo.

##Usage

###Installation

Install with bower or npm and include on your app with some module loader (browserify/webpack/require.js) or include directly on your app through script tag.

$ bower install --save fluxo

or

$ npm install --save fluxo-js

####Using with CommonJS module loaders (Webpack/Browserify)

var Fluxo = require("fluxo-js");
var jon = new Fluxo.ObjectStore({ name: "John Doe" });

Giving super powers to your bare objects

Javascript's literal objects most of the time can be the best tool for your app and you probably will hold the state of your app on one of then, but working with bare objects sometimes can generates some boilerplate. You may need to compute attributes, emit events, manipulate collections and so on. On apps that use some pattern like Facebook's Flux these kind of things is crucial.

So Fluxo is a foundation to "entities" with useful capabilities with a tiny dependency amount on your application. These entities objects we call store.

#Summary

##Fluxo.ObjectStore

For objects that represents a "single entity", you must instantiate the class Fluxo.ObjectStore passing on the first constructor argument the store's attributes, like this:

var jon = new Fluxo.ObjectStore({ name: "John Doe" });

##Reading and Updating your store data

To read your store's data, you need access the data property. To update your data, you should avoid change the data property directly, because Fluxo will run some important tasks, like emits the change events and parse values.

#####setAttribute setAttribute(attributeName, newValue)

Change a store attribute. This emits change and change:<ATTRIBUTE-NAME> events.

jon.setAttribute("name", "John Doe");
jon.data.name //=> "John Doe"

#####set set(attributesSet)

Similar to setAttribute, but receives an object and allows change multiple attributes at the same time. Notice that set merges the actual data with the attributes passed to the method.

jon.set({ "name": "John Bar" });
jon.data.name //=> "John Bar"

#####reset reset(attributes)

Similar to set, but removes the other attributes that aren't included on the attributes object and restore the default values.

jon.set({ "name": "John Bar" });
jon.reset({ "age": 30 });
jon.data.name // => undefined
jon.data.age // => 30

#####clear clear()

Remove all attributes of your store.

jon.set({ "name": "John Bar", age: 25 });
jon.clear();
jon.data.name // => undefined
jon.data.age // => undefined

#####unsetAttribute unsetAttribute(attributeName)

Remove an attribute, this emit change:<ATRIBUTE-NAME>.

jon.unsetAttribute("age");
jon.data.age // => undefined

##Extending a Store

You may want to attach some custom behaviours to your stores, you can do this creating your own ES6 class and extending Fluxo.ObjectStore or Fluxo.CollectionStore.

class Person extends Fluxo.ObjectStore {
  sayHello () {
    return "hello";
  }
};

##Attributes Contracts Attributes contracts allow you to define default values and custom parsing/dumping logics to your store's attributes. To use them, you need to specify the contracts on the class property attributes with the following properties:

  • defaultValue: the default value used on the store initialization and the #reset method.
  • parser: the function that receives the value on the argument and returns the value parsed.
  • dump: the function that receives the value on the argument and returns the result to #toJSON method. The default dump logic calls toJSON on the value.
  • required (boolean): warning on the console when an attribute is required and is missing on the initialization or #unsetAttribute method.

Take a look at the examples below:

class Comment extends Fluxo.ObjectStore {}

Comment.attributes = {
  title: { defaultValue: "This is my title" },
  content: { defaultValue: "This is my default comment" }
};

var comment = new Comment({ content: "This is my comment" });

comment.data // => { title: "This is my title", content: "This is my comment" }

Using the parsers:

class Person extends Fluxo.ObjectStore {};

Person.attributes = {
  age: { parser: function (value) { return parseInt(value); } }
};

var jon = new Person();

jon.setAttribute("age", "30");

jon.data.age //=> 30 (integer)

You can also use the attribute parsers to easily transform objects into nested stores if you need this.

class Author extends Fluxo.ObjectStore {};

class Post extends Fluxo.ObjectStore {};

Post.attributes = {
  author: {
    defaultValue: {},
    parser: function (value) {
      return (value instanceof Author) ? value : new Author(value);
    }
  }
};

var post = new Post({ content: "My Post", author: { name: "John" } });

post.data.author //=> <a instance of Author store>

You can specify how the attribute should be generated to #toJSON method.

class Post extends Fluxo.ObjectStore {};

Post.attributes = {
  date: {
    dump: function (value) {
      return value.customToJSONMethod();
    }
  }
};

##Fluxo.CollectionStore

Sometimes you'll need stores that behaves like collections of stores, so you can manipulate them, emit events, compute attributes based on your children objects and so on, for this task you may use the Fluxo.CollectionStore.

Before anything keep in mind that Fluxo.CollectionStore is a extension of Fluxo.ObjectStore, so all the features available to object stores are available to Fluxo collection stores.

The usage is pretty like the object store usage, but on the first constructor's argument, you should place an array with your children objects and on the second, you should place the collection store's data.

var people =
  new Fluxo.CollectionStore(
    [{ name: "John Doe" }, { name: "Ruth Anderson" }],
    { peopleCountry: "Brazil" }
  );

Every object on the first constructor's argument will be transformed on Fluxo object store.

people.stores[0].data.name //=> "John Doe"
people.stores[1].data.name //=> "Ruth Anderson"

###Updating your collection

You should avoid add or remove items on your stores property directly, instead, you should use the following methods:

#####addStore addStore(object|Fluxo.ObjectStore, options)

Add a single store. If it isn't a Fluxo store it will be parsed. This emits change and add events.

You can specify in what index you want your store with the option atIndex (zero based).

people.addStore({ name: "Neo" });
people.stores[0].data.name //=> "Neo"

#####addStores addStores(array[object|Fluxo.ObjectStore])

Same that addStore, but accepts an array with objects to add at the same time.

people.addStores([{ name: "Neo" }, { name: "John" }]);
people.stores[0].data.name //=> "Neo"
people.stores[1].data.name //=> "John"

⚠️ If you pass an already added store it will be ignored!

#####setStores setStores(array[object|Fluxo.ObjectStore], options={ removeMissing: false })

Like addStores, but if a store on the parameter is already added it will be updated.

You can specify the option removeMissing: true to remove all stores that aren't specified on the payload.

var collection =
  new Fluxo.CollectionStore([
    { id: 1, name: "Foo", gender: "m" },
    { id: 2, name: "Bar" }
  ]);

collection.setStores([{ id: 1, name: "FooBar" }], { removeMissing: true });

collection.stores[0].data.name // => FooBar
collection.stores[0].data.gender // => m
collection.stores[1] // => undefined

#####removeAll removeAll()

Remove all stores. Emits change and remove events.

#####remove remove(Fluxo.ObjectStore)

Remove a single store. Emits change and remove events.

var jon = new Fluxo.ObjectStore({ name: "John" });
people.addStore(jon);
people.stores[0] //=> <the store>
people.remove(jon);
people.stores[0] //=> undefined

#####resetStores resetStores(array[object|Fluxo.ObjectStore])

Remove all previous children stores and add the argument's stores.

people.addStores([{ name: "Neo" }, { name: "John" }]);
people.resetStores([{ name: "Neo" }]);
people.stores[0].data.name //=> "Neo"
people.stores[1] //=> undefined

###Children stores extension

You can specify what class the children stores may have when it's added on collection on the key store of your collection class, like this:

class Person extends Fluxo.ObjectStore {
  sayHello () {
    return this.data.name;
  }
};

class People extends Fluxo.CollectionStore {};

People.store = Person;

people.addStore({ name: "John" });

people.stores[0].sayHello(); //=> "John"

###Searching

Sometimes you will want get some stores of your collection, to this you can use the follow methods:

#####find find(cid|id)

Returns the first store with the matching id attribute or matching cid (what's cid?)

#####findWhere findWhere(criteriaObject)

Returns the first matching store. See #where for more about criteriaObject.

#####where where(criteriaObject)

It returns an array containing the matching stores. You must pass on first argument an object with search criteria.

var jon = new Fluxo.ObjectStore({ name: "John", age: 30 }),
    ruth = new Fluxo.ObjectStore({ name: "Ruth", age: 31  });

people.addStores([jon, ruth]);
people.where({ age: 30 }); //=> [jon];

###Ordering

If you want the stores sorted, you must use a computed property that sorts them, i.e.:

class People extends Fluxo.CollectionStore {
  static computed = {
    sorted: ["add", "remove", "stores:change:name"]
  };

  sorted () {
    return [...this.stores].sort((a, b) => a.data.name.localeCompare(b.data.name));
  }
};

people = new People;
people.addStore({name: 'zach'});
people.addStore({name: 'abraham'});

console.log(people.data.sorted.map(person => person.name));
//=> ['abraham', 'zach']

Note: You must use spread operator to copy the array because Array.prototype.sort mutates the caller and we don't want to mutate this.stores.

###Quick Dumb Collections

You can create collections without create a new class just to specify a different store class.

Just define the store key on the third argument of the Fluxo.CollectionStore constructor with the children store class.

class Person extends Fluxo.ObjectStore {}

var people =
  new Fluxo.CollectionStore([{ name: "John" }], {}, { store: Person });

people.stores[0] //=> instance of Person;

##Collection children delegations

When you are manipulating collections, you usually will manipulate the children stores and you can do this accessing the store directly on the stores property, but it breaks the law of demeter, so, to avoid this you can use the children delegations.

Children delegations, allows you to invoke methods on your children stores through the collection. To delegate, you need to declare on the childrenDelegate class property an array with the name of methods that you want proxy, like this:

class Todo extends Fluxo.ObjectStore {
  setAsDone () {
    this.setAttribute("done", true);
  }
};

class Todos extends Fluxo.CollectionStore {};

Todos.store = Todo;
Todos.childrenDelegate = ["setAsDone"];

var todos = new Todos([{ id: 2, done: "false" }]);

todos.setAsDone(2);

todos.stores[0].data.done // true

The first argument on the delegated method should be the ID of the child that you want to invoke the method (you can use the cid too), the rest will be passed to child's method.

The last line of our example above will call setAsDone on the todo with id 2.

##toJSON

Eventually, you will pass the state that you are holding on your store to other parts of your app, like the view layer. You should avoid pass the data or stores properties directly, the toJSON is the right tool for this job.

On object stores the toJSON will include all the properties of your data and the cid, like this:

{
  cid: "FS:1",
  name: "John Doe"
}

On collection stores the toJSON will include the same things of object store toJSON, but it'll also include the stores property with all data of your children stores (it will call toJSON on its children), like this:

{
  cid: "FS:2",
  doneCount: 1,
  pending: [
    { cid: "FS:4", done: false, content: "Buy a car!" }
  ],
  stores: [
    { cid: "FS:3", done: true, content: "Buy milk!" },
    { cid: "FS:4", done: false, content: "Buy a car!" }
  ]
}

⚠️ The #toJSON method returns a new JSON copy everytime that your store changes, if not, the same copy is returned. It can be very useful for things like React's PureRenderMixin

##CID

Every Fluxo store has an internal CID (client ID) that are used to many internal tasks, but you can use to reference your object on your interface as well. The CID value is placed on the store's cid property.

jon.cid //=> "FS:1"

##Events

Stores can emit and react on events using the on, triggerEvent and triggerEvents methods.

The on method accepts two arguments, the first is an array of events names and the second is the callback.

To cancel the event subscription, you can invoke the returned canceler on the event subscription.

// Registers the event
var eventCanceler = jon.on(["change:name"], function (store) {
  alert("wow, you changed the name");
});

// Cancels the event
eventCanceler.call();

The first argument method of triggerEvents is an array with the events names that you want to trigger, the other arguments are passed to the signed callbacks. If you want to trigger only one event you can use the triggerEvent.

myStore.triggerEvents(["myCustomEvent", "change"], "myCustomArgument");

###Nested Stores event bubbling

Every nested Fluxo store bubbles its events to its parents, like the example below:

var author = new Fluxo.ObjectStore({ name: "John" });
var post = new Fluxo.ObjectStore({ content: "My post", author: author });

post.data.author.setAttribute("name", "Ruth");

// Triggered Events on "post" store:
// * change:author:name
// * change:author
// * change

###Collections and event bubbling

The Fluxo collections propagates all the children store event's with the prefix stores:, so, if you trigger an event like "sayHello" on a store of a collection, your collection will trigger the stores:sayHello event. Take a look on the example below:

var john = new Fluxo.ObjectStore({ name: "John" });
var people = new Fluxo.CollectionStore([john]);

john.setAttribute("name", "John Doe");

// Triggered Events on "people" store:
// * stores:change:name
// * stores:change
// * change

###Wildcard event

Every triggered event emits a wildcard event.

Example: if you trigger the event sayHello on your object, the event called * will be triggered, but the arguments to the callback for this event is diferrent of other events. The first argument will be the name of event that was triggered, the rest is the same of the other events.

jon.on(["sayHello"], function (store) {
  //...
});

jon.on(["*"], function (eventName, store) {
  // triggering the  "sayHello" event will trigger this event with the
  // argument "eventName" with value "sayHello".
});

jon.triggerEvent("sayHello");

##Computed properties

Fluxo object store and collection stores can have computed properties like Ember.js computed properties, this feature allows you to declare attributes that are computed on some events of your store.

Computed properties are very great to normalize the access of store's information (you don't want to deal with store.fullName(), right?) and "caching" the computed results helping to avoid possible not necessary expensive recomputations.

Looks the example below:

class Person extends Fluxo.ObjectStore {
  fullName: function () {
    return (this.data.firstName + " " + this.data.lastName);
  }
};

Person.computed = {
  fullName: ["change:firstName", "change:lastName"]
};

var person = new Person({ firstName: "John", lastName:  "Doe" });

person.data.fullName; // => "John Doe"

person.setAttribute("lastName", "Anderson");

person.data.fullName; // => "John Anderson"

Or a more complex example with collection store:

class Todos extends Fluxo.CollectionStore {
  doneCount () {
    return this.stores.filter(todo => todo.data.done).length;
  }
}

Todos.computed = {
  doneCount: ["add", "remove", "stores:change:done"]
};

var todos = new Todos([{ done: true }, { done: false }]);

todos.data.doneCount; // => 1

todos.stores[1].setAttribute("done", true);

todos.data.doneCount; // => 2

⚠️ Computed properties are computed on the store's creation.

##Using with React.js

Fluxo is view layer agnostic, you can use whatever you want, but we highly recommend the React.js. If you choose the React.js, we already created a way to connect your stores on your React.js components.

Read more: https://github.com/fluxo-js/fluxo-react-connect-stores

##License Fluxo is released under the MIT License.