Superfineを使用したMVCの例


fineapple.js
import * as superfine from 'https://unpkg.com/superfine?module';

// MVC概要
// 
// app = Controllers(Model,View)
// 
// Model       : アプリの概念。プラットフォーム入出力のないオブジェクト。
// View        : アプリのGUI。ビューの描画を計算する。
// Controllers : アプリの型。メソッドがアプリの制御機能。
// app         : アプリの実体。Controllersのインスタンス。

class Controllers {
  // アプリに必須な属性を設定・保持・管理。
  constructor(model, container, calculator) {
    this.model = model;
    this.view = {
      container: container,
      calculator: calculator,
    };
  }
  // superfineのマウントポイント設定。ビューレンダー(this.view.render)が生成される。
  mount(node) {
    let v = this.view;
    v.node = node;
    v.render = () => {
      v.node = superfine.patch(v.node, v.calculator(this), v.container);
      return this;
    }
    return this;
  }
  // マウントポイント設定とビュー描画の一連の流れ。
  launch() {
    return this.mount().view.render();
  }
  // おまけ。デバッグに便利なようメソッドチェーンで書ける。
  info() {
    console.info(...arguments, this);
    return this;
  }
}

// エクスポート時のユニーク名。
export const Fineapple = Controllers;

// リサイクルエクスポート。
export const h = superfine.h;

// htmlタグのclass属性をJavaScript表現したものを文字列へ変換するユーティリティ。
export const classJoin = o => o.join(' ');
export const classConcat = (a, b) => a.split(' ').concat(b).join(' ');

// htmlタグのstyle属性をJavaScript表現したものを文字列へ変換するユーティリティ。
export const styleJoin = o => Object.keys(o).map(key=>`${key}:${o[key]};`).join('');
export const styleConcat = (a, b) => a + styleJoin(b);

// var foo = ['aaa','bbb']
// var bar = ['ccc','ddd']
// console.assert(classJoin(foo) === 'aaa bbb')
// console.assert(classConcat(classJoin(foo), bar) === 'aaa bbb ccc ddd')

// var foo = {a:'aaa', b:'bbb'}
// var bar = {c:'ccc', d:'ddd'}
// console.assert(styleJoin(foo) === 'a:aaa;b:bbb;')
// console.assert(styleConcat(styleJoin(foo), bar) === 'a:aaa;b:bbb;c:ccc;d:ddd;')

// var foo = {border:'1px solid red', 'font-size':'2em'}
// console.assert(styleJoin(foo) === 'border:1px solid red;font-size:2em;')
counter.js
import { h, Fineapple } from './fineapple.js';

export class Model {
  constructor(count=0) { this.count=count; }
  add(by=1) { this.count+=by; return this; }
  sub(by=1) { this.count-=by; return this; }
}

export class Controllers extends Fineapple {
  add() {
    this.model.add();
    return this.view.render();
  }
  sub() {
    this.model.sub();
    return this.view.render();
  }
}

export class Components {
  display(app, attributes) {
    return h('h2', attributes, app.model.count);
  }

  addButton(app, attributes, children='+') {
    let handlers = {
      onclick: () => app.add(),
    }
    let properties = Object.assign(handlers, attributes);
    return h('button', properties, children);
  }

  subButton(app, attributes, children='-') {
    let handlers = {
      onclick: () => app.sub(),
    }
    let properties = Object.assign(handlers, attributes);
    return h('button', properties, children);
  }
}

export function view() {
  let c = new Components();
  return app => 
    h('div', {class:'counter'}, [
      c.display(app),
      c.subButton(app),
      c.addButton(app),
    ])
}
app.js
import { h } from './fineapple.js';
import * as Counter from './counter.js';


const app = new Counter.Controllers(new Counter.Model(), document.body, app =>
  h('div', {}, [
    h('h1', {}, 'Fineapple: for building web app.'),
    h('div', {class:'counter'}, [
      h('h2', {}, app.model.count),
      h('button', { onclick: () => app.sub().info('sub',app.model) }, '-'),
      h('button', { onclick: () => app.add().info('add',app.model) }, '+'),
    ]),
  ])
);

app.launch().info('launch');


// const c = new Counter.Components();
//
// new Counter.Controllers(new Counter.Model(), document.body, app =>
//   h('div', {}, [
//     h('h1', {}, 'Fineapple: for building web app.'),
//     h('div', {class:'counter'}, [
//       c.display(app),
//       c.subButton(app),
//       c.addButton(app),
//     ]),
//   ])
// )
// .launch().info('launch');


// new Counter.Controllers(new Counter.Model(), document.body, Counter.view()).launch();

demo