metaobject-mvc

1.2.21 • Public • Published

MVC design pattern for ReactJS

Install

NPM

> npm install --save metaobject-mvc
> npm install --save babel-plugin-transform-decorators-legacy

Edit package.json

...
"babel": {
  "presets": [
    "react-app"
  ],
  "plugins": [
    "transform-decorators-legacy"
  ]
},
...

Counter Example

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Models from 'metaobject-mvc/Models';
import AppStore from 'mvc/AppStore';
import HistoryStore from 'mvc/HistoryStore';
import App from 'mvc/App';

Models.register(new AppStore('appStore'));
Models.register(new HistoryStore('historyStore'));

ReactDOM.render(<App />, document.getElementById('root'));

App.js

import React from 'react';
import Controller, { connect, disconnect, signal, slot } from 'metaobject-mvc/Controller';
import Models from 'MetaObject-mvc/Models';

@Controller({
  models:['appStore','historyStore'],
  mount:function (appStore,historyStore) {
    connect(this,'increase',appStore,'increase');
    connect(this,'decrease',appStore,'decrease');
    connect(appStore,'stateChanged',this,'stateChanged');
    connect(appStore,'stateChanged',historyStore,'push');
    connect(historyStore,'stateChanged',this,'historyChanged');
  },
  unmount:function (appStore,historyStore) {
    disconnect(this,'increase',appStore,'increase');
    disconnect(this,'decrease',appStore,'decrease');
    disconnect(appStore,'stateChanged',this,'stateChanged');
    disconnect(appStore,'stateChanged',historyStore,'push');
    disconnect(historyStore,'stateChanged',this,'historyChanged');
  }
})
export default class App extends React.Component {
  controllerDidMount() {
    console.log('controllerDidMount');
  }

  controllerWillUnmount() {
    console.log('controllerWillUnmount');
  }

  render() {
    let {appStore} = this.props;
    let {historyStore} = this.props;
    let logs = historyStore.records.map((r,i)=>{
      return (
        <div
          key={i}
          style={{
            borderStyle:'solid',
            borderWidth:1
          }}
          >
          {JSON.stringify(r)}
        </div>
      )}
    );
    return (
      <div
        >
        <div>{appStore.count}</div>
        <button onClick={this.onIncrement(1)}>+1</button>
        <button onClick={this.onDecrement(1)}>-1</button>
        <button onClick={this.onIncrement(5)}>+5</button>
        <button onClick={this.onDecrement(5)}>-5</button>
        <button onClick={this.incrementIfOdd}>Increment 1 if odd</button>
        <button onClick={this.incrementAsync}>Increment 1 async</button>
        <textarea ref={'input'} rows={4} cols={80}
          defaultValue={'{"appStore":{"count":5},"historyStore":{"records":[{"0":{"count":0},"1":{"count":1},"when":1533238613508},{"0":{"count":1},"1":{"count":0},"when":1533238614260},{"0":{"count":0},"1":{"count":5},"when":1533238614700}]}}'}>
        </textarea>
        <button onClick={this.updateStates}>Update states</button>
        {logs}
      </div>
    );
  }

  @slot
  stateChanged(oldState,newState) {
    console.log('stateChanged');
  }

  @slot
  historyChanged(oldState,newState) {
    console.log('historyChanged',JSON.stringify(Models.getStates()));
    this.forceUpdate();
  }

  @signal
  increase(number) {
  }

  @signal
  decrease(number) {
  }

  onIncrement = (number)=> (e)=>{
    this.increase(number);
  }

  onDecrement = (number)=> (e)=>{
    this.decrease(number);
  }

  incrementIfOdd = (e)=>{
    if (this.props.appStore.count % 2 !== 0) {
      this.increase(1);
    }
  }

  incrementAsync = (e)=>{
    setTimeout(()=>this.increase(1), 1000);
  }

  updateStates = (e)=>{
    try {
      Models.setStates(JSON.parse(this.refs.input.value));
    } catch(e) {
    } finally {
      this.refs.input.value = '';
    }
  }
}

or if you use only one model, you can

@Controller({
  model:'appStore',
  mount:function (appStore) {
    ...
  },
  unmount:function (appStore) {
    ...
  },
  ...
})

or if you need initializing by props, you can

ReactDOM.render(<App appStore={'appStore'} historyStore={'historyStore'}/>, document.getElementById('root'));
...
@Controller((props)=>({
  models:[props.appStore,props.historyStore],
  mount:function (appStore,historyStore) {
    ...
  },
  unmount:function (appStore,historyStore) {
    ...
  },
  ...
}))

AppStore.js

import Model, { signal, slot } from 'metaobject-mvc/Model';

export default class AppStore extends Model {
  constructor(name) {
    super(name);
    this.state({
      count: 0,
    });
  }

  @signal
  @slot
  increase(number) {
    let count = this.state().count;
    this.state({
      count: count+number,
    });
  }

  @signal
  @slot
  decrease(number) {
    let count = this.state().count;
    this.state({
      count: Math.max(0,count-number),
    });
  }
}

HistoryStore.js

import Model, { slot } from 'metaobject-mvc/Model';

export default class HistoryStore extends Model {
  constructor(name) {
    super(name);
    this.state({
      records:[],
    });
  }

  @slot
  push(...record) {
    let records = this.state().records;
    this.state({
      records: [...records,{...record,when:Date.now()}],
    });
  }
}

Package Sidebar

Install

npm i metaobject-mvc

Weekly Downloads

2

Version

1.2.21

License

none

Unpacked Size

44.7 kB

Total Files

12

Last publish

Collaborators

  • janghaksang