Extended Integration

This example will demonstrate how to integrate @bluepjs more deep: example will control 2 divs and 2 buttons background and text colors and buttons click event:

Example template view

To make example more “demonstratable” new Module will be created with special types defenitions (enums, structs, classes) and Actors will demonstrate OOP Inheritance.

HTML structure for demo is simple:

    <div class="demo-page" ref="demo">
      <div id="div-1">Div 1</div>
      <div id="div-2">Div 2</div>
      <button id="btn-1">Button 1</button>
      <button id="btn-2">Button 2</button>
    </div>

css:

.demo-page {
  margin: 15px;
  padding: 10px;
  background-color: #eee;
}
.demo-page div {
  min-width: 50px;
  min-height: 50px;
  border: 1px solid #ddd;
}

Module

Let’s see all file first, and describe it by parts after:

example/src/vm/page.module.js
  1const { AbstractModule } = require('@bluepjs/vm')
  2
  3const DivActor = require('./div.actor')
  4const ButtonActor = require('./button.actor')
  5
  6class PageModule extends AbstractModule {
  7  /**
  8    static method provides information about module for Vm and IDE
  9  */
 10  static metadata () {
 11    return {
 12      // module code to access module object with vm.M(code) or vm.module(code)
 13      code: 'page',
 14      name: 'Page',
 15      // let's define some module types
 16      enums: {
 17        // html colors
 18        'html/color': { // full type will be `bluep/enum/${enumCode}`
 19          code: 'html/color',
 20          name: 'Html color',
 21          values: {
 22            '#f00': 'Red',
 23            '#0f0': 'Green',
 24            '#00f': 'Blue',
 25            '#fff': 'White',
 26            '#000': 'Black',
 27            '#999': 'Grey'
 28          }
 29        }
 30      },
 31      structs: {
 32        // colors struct for background color and text color
 33        'element/colors': { // full type will be `bluep/struct/${structCode}`
 34          code: 'element/colors',
 35          name: 'Html elements colors',
 36          schema: {
 37            background: {
 38              code: 'background',
 39              name: 'Background color',
 40              type: 'bluep/enum/html/color'
 41            },
 42            text: {
 43              code: 'text',
 44              name: 'Text color',
 45              type: 'bluep/enum/html/color'
 46            }
 47          }
 48        }
 49      },
 50      // let's define IDE color for our structure
 51      types: {
 52        'bluep/struct/element/colors': {
 53          code: 'bluep/struct/element/colors',
 54          name: 'Html color',
 55          color: '#df883d'
 56        }
 57      },
 58      // ElementActor description:
 59      classes: {
 60        'html/element': { // full type will be `bluep/class/${classCode}`
 61          code: 'html/element',
 62          name: 'HTML Element',
 63          module: 'page',
 64          schema: {
 65            colors: {
 66              code: 'colors',
 67              name: 'Colors',
 68              access: 'public',
 69              readonly: true,
 70              type: 'bluep/struct/element/colors'
 71            }
 72          },
 73          methods: {
 74            colorize: {
 75              code: 'colorize',
 76              name: 'Colorize',
 77              access: 'public',
 78              type: 'method',
 79              class: 'html/element',
 80              module: 'page',
 81              context: {
 82                inputs: {
 83                  colors: {
 84                    code: 'colors',
 85                    name: 'Colors',
 86                    type: 'bluep/struct/element/colors'
 87                  }
 88                }
 89              }
 90            }
 91          }
 92        }
 93      }
 94    }
 95  }
 96
 97  // init method will be called before Vm.start and allows module generate Actors for elements
 98  async init (htmlElements) {
 99    if (htmlElements && htmlElements.length) {
100      for (let i = 0; i < htmlElements.length; i++) {
101        const el = htmlElements[i]
102        const tag = el.tagName
103        const id = el.id
104        if (tag === 'DIV') {
105          const actor = new DivActor(id, el)
106          this.vm().M('actor').addActor(actor)
107        }
108        if (tag === 'BUTTON') {
109          const actor = new ButtonActor(id, el)
110          this.vm().M('actor').addActor(actor)
111        }
112      }
113    } else {
114      /**
115        Current example architecture is not really nice.
116        Page module used only on frontend, but we take Vm.ideData() for IDE
117        from "background Vm" instead of "frontend Vm" where page module
118        will be really used.
119
120        So we just know what actors will be used on frontend, and we create
121        them here, to get them on IDE
122      */
123      if (typeof window === 'undefined') {
124        const div1 = new DivActor('div-1', null)
125        const div2 = new DivActor('div-2', null)
126        const btn1 = new ButtonActor('btn-1', null)
127        const btn2 = new ButtonActor('btn-2', null)
128        this.vm().M('actor').addActor(div1)
129        this.vm().M('actor').addActor(div2)
130        this.vm().M('actor').addActor(btn1)
131        this.vm().M('actor').addActor(btn2)
132      }
133    }
134  }
135
136  // executes on Vm.start
137  async start () {
138    // this module will be executed only in browser
139    if (typeof window === 'undefined') {
140      // use vm console for "outputs"
141      this.vm().console().error('Page module can be started only in browser')
142      return
143    }
144    this.vm().console().log('Page module starting')
145  }
146}
147
148module.exports = PageModule

Module defines:

  • enum html/color with pairs of cssColor - Color name. Full type code is bluep/enum/html/color

  • struct element/colors with fileds background and text. Full type code is bluep/struct/element/colors

  • special color for struct

  • class html/element describes parent class of module actors. Because parent class is never directly used by Vm - it’s metadata can’t be directly accessible (from class metadata) and provided in module metadata.

Because of not really nice demo architecture (IDE using “backend Vm”, and demo module runs only on frontent) - module initialization is “tricky” and we initialize “empty” actors just to get module/actors information for IDE

const PageModule = require('./src/vm/page.module')
vm.addModule(PageModule)
vm.M('page').init()

But on frontend module initialized with real DOM elements:

import PageModule from '@/vm/page.module'
    this.vm.addModule(PageModule)
    await this.vm.M('page').init(this.$refs.demo.children)

Note

$refs is part of Vue 3 framework.

Actors

html/element

Base class for actors in this demo named “HTML Element” (with code “html/element”).

example/src/vm/element.actor.js
 1const { AbstractActor } = require('@bluepjs/vm')
 2
 3/**
 4  ElementActor will be base for other actors - DivActor and ButtonActor
 5  and implement parent methods
 6
 7  ElementActor by itself will not be used - it's "abstract" class,
 8  so class will be defined in module classes metadata and here metadata will be skiped
 9*/
10class ElementActor extends AbstractActor {
11  /*
12    override actor constructor to provide html element and set inital states
13  */
14  constructor (actorId, htmlElement) {
15    super(actorId)
16
17    this._element = htmlElement || null
18
19    this._state = {
20      // according to 'html/element' class schema, struct and enums description
21      colors: {
22        background: '#fff',
23        text: '#000'
24      }
25    }
26  }
27
28  /*
29    override non-static metadata method to set nicer names
30  */
31  metadata () {
32    const base = this.constructor.metadata()
33    const id = this.id()
34    // required. exists in parent metadata() method
35    base.id = id
36    // new update
37    base.name += ` (${id})`
38    return base
39  }
40
41  // override async AbstractActor::method(code, inputs)
42  method (code, inputs) {
43    // all methods will be defined as functions named 'code'
44    // so all child classes will not override this method
45    if (!this._element || typeof this[code] !== 'function') return
46    return this[code](inputs)
47  }
48
49  // method described for ElementActor
50  async colorize ({ colors }) {
51    // don't forget to update state
52    this._state.colors = colors
53    this._element.style.color = colors.text
54    this._element.style['background-color'] = colors.background
55  }
56}
57
58module.exports = ElementActor

Take attention to _state defenition (line 19). State described at Module metadata as variable with code colors typeof bluep/struct/element/colors.

          schema: {
            colors: {
              code: 'colors',
              name: 'Colors',
              access: 'public',
              readonly: true,
              type: 'bluep/struct/element/colors'
            }
          },

Struct element/colors described having 2 fileds type of bluep/enum/html/color:

        'element/colors': { // full type will be `bluep/struct/${structCode}`
          code: 'element/colors',
          name: 'Html elements colors',
          schema: {
            background: {
              code: 'background',
              name: 'Background color',
              type: 'bluep/enum/html/color'
            },
            text: {
              code: 'text',
              name: 'Text color',
              type: 'bluep/enum/html/color'
            }
          }
        }
      },

And enum type html/color described with next values:

        'html/color': { // full type will be `bluep/enum/${enumCode}`
          code: 'html/color',
          name: 'Html color',
          values: {
            '#f00': 'Red',
            '#0f0': 'Green',
            '#00f': 'Blue',
            '#fff': 'White',
            '#000': 'Black',
            '#999': 'Grey'
          }
        }

As result state definition is:

    this._state = {
      // according to 'html/element' class schema, struct and enums description
      colors: {
        background: '#fff',
        text: '#000'
      }
    }

colorize method, described at Module metadata is simple - it updated state colors variable and css styles of html element accordingly.

  async colorize ({ colors }) {
    // don't forget to update state
    this._state.colors = colors
    this._element.style.color = colors.text
    this._element.style['background-color'] = colors.background
  }

html/div

Div Actor is very simple:

src/vm/div.actor.js
 1const ElementActor = require('./element.actor')
 2
 3/**
 4  Div actor is simple ElementActor child class
 5*/
 6class DivActor extends ElementActor {
 7  /*
 8    Div actor how nothing specal, so we can describe it only by metadata
 9  */
10  static metadata () {
11    return {
12      code: 'html/div',
13      name: 'Div Element',
14      // all schema and methods are extended fom parent class
15      extends: {
16        'module/page/html/element': {
17          module: 'page',
18          code: 'html/element'
19        }
20      }
21    }
22  }
23}
24
25module.exports = DivActor

Actor just extends html/element class. But actors of this class will be used by Vm, so there is static metadata() method, describing it.

html/button

Button Actor has additional event click

src/vm/button.actor.js
 1const ElementActor = require('./element.actor')
 2
 3/**
 4  Div actor is simple ElementActor child class
 5*/
 6class ButtonActor extends ElementActor {
 7  /*
 8    Div actor how nothing specal, so we can describe it only by metadata
 9  */
10  static metadata () {
11    return {
12      code: 'html/button',
13      name: 'Button Element',
14      // all schema and methods are extended fom parent class
15      extends: {
16        'module/page/html/element': {
17          module: 'page',
18          code: 'html/element'
19        }
20      },
21      events: {
22        click: {
23          code: 'click',
24          name: 'Click'
25        }
26      }
27    }
28  }
29
30  constructor (id, htmlElement) {
31    super(id, htmlElement)
32
33    if (htmlElement) {
34      htmlElement.addEventListener('click', () => {
35        this.emit('click')
36      })
37    }
38  }
39}
40
41module.exports = ButtonActor

Actor constructor is oveeriden to bind event listener on element.

Actors are event emitters, and Vm manages described events.

Bleuprints

Our Module is complete and comnnected to Vm. Now all Actors behavior will be defined with Blueprints.

Vm start

Let’s colorize our actors on Vm start:

Example result view Vm start blueprint

Button 1 click

Click on 1st button will take background color of button and apply it as text color of divs, keeping divs background color same.

Button 1 click

If we click on 1st button - divs text color will become green:

Button 1 click result

Works! Great! :) But let’s do some “code optimization” before we touch 2nd button.

Colorize functions

Let’s create blueprint function to colorize text of our divs. Function will have one input typeof bluep/enum/html/color

Colorize blueprint

Function do same thing as “button 1 click” blueprint with difference that text color is provided as input variable.

Now let’s do button 2 click blueprint.

Button 2 click

Button 2 click

colorize blueprint is called on click with manually defined red color, than function waits for 2000ms (2sec) and call again colorize node with blue color input.

Button 2 click red colorized Button 2 blue after 2 sec

Project sources

Example project with all sources can be found here: https://github.com/bluep-js/example