front
A package that desperately needs to be renamed
Overview
This package contains the Model and ModelListener classes that make up the core of how data is passed around Postmates. Model sets up a living data contract between frontend services, transforming and validating data, sending events on changes, and setting up rules for model hierarchy and initialization. ModelListener is a wrapper around React.Component that takes over the duty of storing information on the view layer and keeps it in the model layer, setting up binding routes on the way. Any time a model updates, the view updates, and the view can send information to the model to allow it to modify the underlying data model. Setting all this up removes the siloing of data structure definition in the view (props) and allows that structure to be used in services, api calls, application logic, etc.
Model
Model has a simple interface, all documented in the source code, but sometimes examples are easier to parse. The most basic constructor looks like this:
class MyFirstModel extends Model {
constructor(data) {
super({
id: 0,
name: ''
});
this.fill(data);
}
}
This example creates a model called MyFirstModel
that defines it having the fields id
and name
with default values and hydrates based on it's constructor parameters.
hierarchy
You can add hierarchy to models by including the class name as a default parameter:
class Name extends Model {
constructor(data) {
super({
first: '',
last: ''
});
this.fill(data);
}
fullName() {
return this.first + ' ' + this.last;
}
}
class Permission extends Model {
constructor(data) {
super({
id: 0,
code: '',
name: ''
});
this.fill(data);
}
}
class AuthUser extends Model {
constructor(data) {
super({
name: Name,
perms: [ Permission ]
});
this.fill(data);
}
can(perm) {
for (var i = 0; i < this.perms.length; i++) {
if (this.perms[i].code === perm) {
return true;
}
}
return false;
}
}
var MyAuth = new AuthUser({
name: {
first: 'super',
last: 'dev'
},
perms: [{
id: 12,
code: 'delete',
name: 'User can delete posts'
}, {
id: 64,
code: 'edit',
name: 'User can edit posts'
}]
});
console.log(MyAuth.can('edit')); // outputs true
console.log(MyAuth.name.fullName()); // outputs 'super dev'
inheritance
If you want to inherit from a model, you can use the extend
function:
class Animal extends Model {
constructor(data) {
super({
species: '',
name: ''
});
this.fill(data);
}
}
class LoudAnimal extends Animal {
constructor(data) {
super().extend({
sound: '',
level: 0
});
this.fill(data);
}
}
transformation
Sometimes data doesn't always come in clean. Sometimes you want to represent your data differently inside of your application logic than outside. There are even times when you have to take a data format and tear it to a million pieces to make it work. You can do this by overwriting the model's fill
function. The out
function works exactly the same, but in reverse.
class MyFirstTransform extends Model {
constructor(data) {
super({
name: '',
itemCount: 0
});
this.fill(data);
}
fill(data) {
if (!data) {
return this;
}
if (data.hasOwnProperty('items')) {
data.itemCount = data.items.length;
delete data.items;
}
super.fill(data);
return this;
}
}
ModelListener
The point of the ModelListener is to be as transparent as possible. If you want to reuse your data layer across your application, you need only to extend from ModelListener instead of React.Component and pass that data contract as prop model
. This will instantly send all model updates to the view using the model's internal dirty flag and rate limiter. Lets look at a simple example:
class MyComponent extends ModelListener {
static defaultProps = {
model: MyFirstModel
};
render() {
return <div>{ this.model.name }</div>
}
}
React.createElement(<MyComponent model={ { name: 'super dev' } } />);
A two way street
The ModelListener also provides a way to update your model from the view through the update function. Here we are pumping the value from the input
field into the model's name
field.
class MyComponent extends ModelListener {
static defaultProps = {
model: MyFirstModel
};
update(field, evt) {
super.update(field, evt.target.value);
}
render() {
return (
<input defaultValue={ this.model.name }
onChange={ this.update.bind(this, 'name') } />
);
}
}