mvvm.js

Model-View-ViewModel Simple Framework

Usage no npm install needed!

<script type="module">
  import mvvmJs from 'https://cdn.skypack.dev/mvvm.js';
</script>

README

MVVM.js

NPM Build Status Code Coverage

This package provides a simple framework that is implementing the Model-View-ViewModel architectural pattern.

Why?

This repository demonstrates a pure Model-View-ViewModel architectural pattern implementation. The framework provides a component model similar to all the modern frontend frameworks and can be used for reactive rendering in trivial JavaScript or TypeScript applications.

Features

  • Reactivity based on observables similar to Vue.js.
  • Decorators defining a data-flow similar to Angular.
  • JSX syntax support similar to React.
  • JSX Fragments support.
  • Lazy DOM updating based on observables without expensive diffing.

Introduction

Model

The model is a data-layer of an application.

const model = { name: 'John' };

View

The view is a representation of a model describing appearance on the screen and bindings between the model data and the representation.

View-Model

The view-model is an abstraction of the view exposing public properties and commands. A data inside the view can be changed through the view-model, and the user's interaction can be handled from the external code via the commands.

Binding

The binding is an expression bound to a particular part of the representation. The view is evaluating the expression upon rendering. In case, when the expression is relying on some observables, the expression will be re-evaluated upon the observable change. Bindings are working one-way only, and provide one-way incoming data flow. There is no way to update the binding from a view.

@Bind('style.display') visibility() {
  return !this.visible && 'none';
}

Command

The command is a function that will be invoked from the representation upon some user interaction. Commands can be listened from the external code and provide one-way outgoing data-flow.

@Command click() {}

Input

The input is a two-way data binding between a model property and a property available from the representation. Properties marked as inputs are observables. The view subscribes for the changes of used inputs and performs partial rerendering on demand. The inputs provide two-way data-flow for the data between a model and its representation.

@Input name?: string;

Expression

The expression is a value that depends on some data that may change in the future. The view automatically evaluates all the expressions during the rendering. In case, when it is relying on some observable, the value will be reevaluated upon change.

render() {
  return <div class={() => this.visible ? 'show' : 'hide'} />
}

Observable

The observable is a value that may change later, and the changes can be observed from the outside.

Component

The component is a composition of the view and the view-model. There can be Class and Functional components. Components accept properties on input, which represent a model. The code of the functional component is the view. In the case of the class component, the render method with all the bindings correspond to the view. And all the inputs and commands define the view-model.

import { Component, Input } from 'mvvm.js';

class Button extends Component {
  @Input label?: string;

  render() {
    return <button>{this.label}</button>;
  }
}

Reference

The reference is a model value that refers to property from another object. In case, when a model property should be passed down to another view-model, the property should be passed as a reference. Every update of the referring property from the child component will take place in the referred object.

<Toggle active={$reference(this, 'visible')} />

Get Started

Installation

npm install --save mvvm.js

Usage

import { Bind, Command, Component, Default, Input } from 'mvvm.js';
import { render } from 'mvvm.js/lib/dom';

interface ButtonProps {
  icon?: string;
  label?: string;
  visible?: boolean;
  click?(button: UIButton): void;
}

class Button extends Component<ButtonProps> {
  @Input icon?: string;

  @Input label?: string;

  @Input @Default(true) visible?: boolean;

  @Command click() {}

  @Bind('style.display') visibility() {
    return !this.visible && 'none';
  }

  render() {
    return (
      <a role="button" class="btn btn-primary" title={() => this.label} onClick={this.click}>
        {() => this.icon && <i class={`fa fa-${this.icon}`}></i>}
        {() => this.label}
      </a>
    );
  }
}

render(
  <Button label='Button' />,
  document.querySelector('#root')!,
);

API

You can find the complete API reference here.

Example

You can see a working demo here (source code).