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:
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:
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 ofcssColor
-Color name
. Full type code isbluep/enum/html/color
struct
element/colors
with filedsbackground
andtext
. Full type code isbluep/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”).
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:
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.
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:
Colorize functions
Let’s create blueprint function to colorize text of our divs. Function will have one input typeof bluep/enum/html/color
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.
Project sources
Example project with all sources can be found here: https://github.com/bluep-js/example