npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tintoa-mvc

v1.5.0

Published

Tintoa Model View Controller

Downloads

5

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.