tintoa-mvc
v1.5.0
Published
Tintoa Model View Controller
Downloads
3
Maintainers
Readme
Tintoa Model-View-Controller
Features
- Runs on both, server and client
- Simple handling of applications state
- Dynamically renders react components for controllers and lists
- Re-renders components when the controller changes
- Uses localStorage to resist browser reload
- Works best with TypeScript
Visit https://mvc-demo.tintoa.net for a demo.
Installation
>> npm install tintoa-mvc
Basic Usage
Model
A model is a simple TypeScript interface with just primitive datatypes and is required for full intellisense support. If you are using plain JavaScript, you will not need a model.
interface UserModel {
name: string;
age: number;
active: boolean;
}
Controller
Simple Controller
class Controller<Model>(identifier: string [,defaultValues: Model])
With this model above, we can create a controller for our current user to handle the model data.
The passed identifier must be unique.
If no default values are passed, the values are undefined
.
import {Controller} from "tintoa-mvc";
let CurrentUserController = new Controller<UserModel>("CurrentUser", {
name: "Tony Stark",
age: 50,
active: true
});
Now we can change the model data with the controller.
CurrentUserController.getValue("name"); // returns "Tony Stark";
CurrentUserController.setValue("name", "Iron Man"); // set the name to "Iron Man";
CurrentUserController.inc("age"); // increases the attribute "age" by 1;
CurrentUserController.dec("age"); // decreases the attribute "age" by 1;
ListController
class ListController<Model>(identifier: string [,childPrefix: string])
For other users, we can create a list controller.
Since the other users have the same data structure as the current user,
reusing the existing UserModel
is perfect.
import {ListController} from "tintoa-mvc";
let UserListController = new ListController<UserModel>("UserList", "User");
With this ListController, we can dynamically add new Users and control their data.
The method add([defaultValues: Model])
creates and returns a new simple controller with the
specified childPrefix as name (e.g. "User_1") ad the optional passed default values;
UserListController.add({
name: "Natasha Romanova",
age: 30,
active: true
});
UserListController.add({
name: "Steven Rogers",
age: 90,
active: true
});
There are different methods to access the children of the list
let allChildren = UserListController.children; // returns every child controller as an array
let firstChild = UserListController.first; // returns the first controller of the child list
let lastChild = UserListController.last; // returns the last 1controller of the child list
let result = UserListController.query("name", "natasha"); // Returns the controller where "name" includes "natasha" (not case sensitive)
You can also apply a filter function to the ListController.
This will fire a ModelUpdate event and will force the views to rerender.
The clearFilter() method will remove the applied filter.
!!!The children property is not affected by the filter and filtering this property wont update the views!!!
// this will apply the filter and force the views to rerender
UserListController.filter = (controller: Controller<UserModel>) => {
return controller.getValue("age") < 50;
}
// X will just contain the user controller with name "Natasha Romanova" since her age is smaller than 50:
let X = UserListController.filtered;
// This will return all Controllers of the list:
let all = UserListController.children;
// The following wont affect the views
UserListController.children.filter((controller: Controller<UserModel>) => {
return controller.getValue("age") < 50;
})
// This will remove the apllied filters
UserListController.clearFilter();
View
To represent the models data, the ViewFactory
can create 2 types of views.
First, we will create a simple view, to show details about the current user:
import * as ReactDOM from "react-dom"
import * as react from "react"
import {ViewFactory} from "tintoa-mvc";
let CurrentUserView = ViewFactory.getComponent(CurrentUserController, (controller) => {
return (<div className={"current-user-info"}>
<div>{controller.getValue("name")}</div>
<div>{controller.getValue("age")}</div>
</div>)
});
ReactDOM.render(<CurrentUserView />, document.getElementsByTagName("app"));
The returned ReactComponent class CurrentUserView
is bound to the passed CurrentUserController
and it will rerender if we change the underlying data model.
- We should not change the model in the render function!
It is quite easy to implement a component that will display the count of all users, since we also can pass ListControllers into the getComponent-Function.
import * as ReactDOM from "react-dom"
import * as react from "react"
import {ViewFactory} from "tintoa-mvc";
let UserCounterView = ViewFactory.getComponent(UserListController, (controller) => {
return (<div className={"user-counter"}>
<div>Number of users: {controller.count}</div>
</div>)
});
ReactDOM.render(
<div>
<CurrentUserView />
<UserCounterView />
</div>, document.getElementsByTagName("app"));
To render a detailed list of all users, the ViewFactory.getListComponent method will help. The first parameter is the ListController, the second is a render function for each child element!
import * as ReactDOM from "react-dom"
import * as react from "react"
import {ViewFactory} from "tintoa-mvc";
let UserListView = ViewFactory.getListComponent(UserListController, (childController, listController, index) => {
return (<div className={"user-info"} onClick={() => {
listController.remove(childController.id);
}}>
<div>Name: {childController.getValue("name")}</div>
<div>Age: {childController.getValue("age")}</div>
</div>)
});
ReactDOM.render(
<div>
<CurrentUserView />
<UserCounterView />
</div>, document.getElementsByTagName("app"));
Events
Every Controller fires events we can listen to. These events are defined in the EventTypes
enumerator.
Use the on(type: EventType, callback: CallbackFunction)
method to register event listener.
import {EventTypes} from "tintoa-mvc";
CurrentUserController.on(EventTypes.StateChange, (oldState, newState) => {
console.log("The CurrentUser has changed!");
});
CurrentUserController.on(EventTypes.KeyChange, "name", (oldValue, newValue, controller) => {
console.log(`The CurrentUser "${oldValue}" has a new name! It's ${newValue} now.`);
});
// The setValue method will lead to the StateChange and to the KeyChange event;
CurrentUserController.setValue("name", "Power Paul");
- These Events are just emitted if the value really changes!
If there is the need to make more than one change, it could be helpful to hold on the emitting of events. For example if there is a view that would render very often.
CurrentUserController.preventEvents();
CurrentUserController.setValue("name", "Tony Stark");
CurrentUserController.inc("age");
CurrentUserController.inc("age");
CurrentUserController.setValue("active", "false");
CurrentUserController.releaseEvents();
The releaseEvents()
method enables the event emitting and also fires a StateChange
Event
with the old state (when preventEvents was called) and the new state. To resume emitting events without
triggering the StateChange
, you can use resumeEvents()
Whats New?
- use Controller.registerView(cmp: React.Component) to update react components when the model changes.
- ListController throws now StateChange Events for every change on the children or the list itself.