Skip to content

Commit dfec6c4

Browse files
committed
Create a Real-World Example #7144
1 parent 78cdf3c commit dfec6c4

8 files changed

Lines changed: 135 additions & 22 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {defineComponent, html, useConfig, useEvent} from '../../../src/functional/_export.mjs';
2+
3+
export default defineComponent({
4+
config: {
5+
/**
6+
* @member {String} className='Neo.examples.functional.templateComponent.Component'
7+
*/
8+
className: 'Neo.examples.functional.templateComponent.Component',
9+
/**
10+
* This is the key to unlock the `Template literals` based syntax for VDOM.
11+
* @member {Boolean} enableHtmlTemplates=true
12+
* @reactive
13+
*/
14+
enableHtmlTemplates: true,
15+
/**
16+
* @member {String} greeting_='Hello'
17+
* @reactive
18+
*/
19+
greeting_: 'Hello'
20+
},
21+
22+
createTemplateVdom(config) {
23+
const [name, setName] = useConfig('World');
24+
25+
useEvent('click', () => setName(prev => prev === 'Neo' ? 'World' : 'Neo'));
26+
27+
return html`
28+
<div>${config.greeting}, ${name}</div>
29+
`
30+
}
31+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import ConfigurationViewport from '../../ConfigurationViewport.mjs';
2+
import MyFunctionalComponent from './Component.mjs';
3+
import TextField from '../../../src/form/field/Text.mjs';
4+
5+
/**
6+
* @class Neo.examples.functional.templateComponent.MainContainer
7+
* @extends Neo.examples.ConfigurationViewport
8+
*/
9+
class MainContainer extends ConfigurationViewport {
10+
static config = {
11+
className : 'Neo.examples.functional.templateComponent.MainContainer',
12+
configItemLabelWidth: 160,
13+
configItemWidth : 280,
14+
layout : {ntype: 'hbox', align: 'stretch'}
15+
}
16+
17+
createConfigurationComponents() {
18+
let me = this;
19+
20+
return [{
21+
module : TextField,
22+
clearable : true,
23+
labelText : 'greeting',
24+
listeners : {change: me.onConfigChange.bind(me, 'greeting')},
25+
style : {marginTop: '10px'},
26+
value : me.exampleComponent.greeting
27+
}]
28+
}
29+
30+
/**
31+
* @returns {Neo.functional.Component}
32+
*/
33+
createExampleComponent() {
34+
return {
35+
module : MyFunctionalComponent,
36+
greeting: 'Hi'
37+
}
38+
}
39+
}
40+
41+
export default Neo.setupClass(MainContainer);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import MainContainer from './MainContainer.mjs';
2+
3+
export const onStart = () => Neo.app({
4+
mainView: MainContainer,
5+
name : 'Neo.examples.functional.templateComponent'
6+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1">
5+
<meta charset="UTF-8">
6+
<title>Neo Functional Template Component</title>
7+
</head>
8+
<body>
9+
<script src="../../../src/MicroLoader.mjs" type="module"></script>
10+
</body>
11+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"appPath" : "examples/functional/templateComponent/app.mjs",
3+
"basePath" : "../../../",
4+
"environment": "development",
5+
"mainPath" : "./Main.mjs"
6+
}

learn/guides/uibuildingblocks/HtmlTemplates.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Using HTML Templates for VDOM
22

3-
This guide covers the purpose, syntax, and trade-offs of using tagged template literals (HTML-like syntax) to define VDOM structures in neo.mjs. This feature provides an intuitive and familiar alternative to the traditional JSON-based VDOM approach, especially for developers coming from other modern web frameworks.
3+
This guide covers the purpose, syntax, and trade-offs of using tagged template literals (HTML-like syntax) to define VDOM structures in neo.mjs. This feature provides an intuitive and familiar alternative to the traditional JSON-based VDOM approach, especially for developers coming from other traditional, single-threaded frameworks.
44

55
## The "Why": An Alternative, Not a Replacement
66

src/functional/_export.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Component from './component/Base.mjs';
22
import defineComponent from './defineComponent.mjs';
3+
import {html} from './util/html.mjs';
34
import useConfig from './useConfig.mjs';
45
import useEvent from './useEvent.mjs';
56

6-
export {Component, defineComponent, useConfig, useEvent};
7+
export {Component, defineComponent, html, useConfig, useEvent};

src/functional/component/Base.mjs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ class FunctionalBase extends Abstract {
9090
})
9191
}
9292

93+
/**
94+
* Triggered after the isReady config got changed
95+
* @param {Boolean} value
96+
* @param {Boolean} oldValue
97+
* @protected
98+
*/
99+
afterSetIsReady(value, oldValue) {
100+
const me = this;
101+
102+
if (value && me.missedReadyState) {
103+
me.vdomEffect.run();
104+
delete me.missedReadyState
105+
}
106+
}
107+
93108
/**
94109
* Triggered after the mounted config got changed
95110
* @param {Boolean} value
@@ -109,25 +124,6 @@ class FunctionalBase extends Abstract {
109124
}
110125
}
111126

112-
/**
113-
* Triggered after the enableHtmlTemplates config got changed.
114-
* @param {Boolean} value
115-
* @param {Boolean} oldValue
116-
* @protected
117-
*/
118-
afterSetEnableHtmlTemplates(value, oldValue) {
119-
if (value && !this.htmlTemplateProcessor) {
120-
// Required for unit testing
121-
if (Neo.ns('Neo.functional.util.HtmlTemplateProcessor')) {
122-
this.htmlTemplateProcessor = Neo.functional.util.HtmlTemplateProcessor
123-
} else {
124-
import('../util/HtmlTemplateProcessor.mjs').then(module => {
125-
this.htmlTemplateProcessor = module.default
126-
})
127-
}
128-
}
129-
}
130-
131127
/**
132128
* Triggered after the windowId config got changed
133129
* @param {Number|null} value
@@ -351,6 +347,23 @@ class FunctionalBase extends Abstract {
351347
}
352348
}
353349

350+
/**
351+
* @returns {Promise<void>}
352+
*/
353+
async initAsync() {
354+
await super.initAsync();
355+
356+
if (this.enableHtmlTemplates) {
357+
// Required for unit testing
358+
if (Neo.ns('Neo.functional.util.HtmlTemplateProcessor')) {
359+
this.htmlTemplateProcessor = Neo.functional.util.HtmlTemplateProcessor
360+
} else {
361+
const module = await import('../util/HtmlTemplateProcessor.mjs');
362+
this.htmlTemplateProcessor = module.default
363+
}
364+
}
365+
}
366+
354367
/**
355368
* This handler runs when the effect's `isRunning` state changes.
356369
* It runs outside the effect's tracking scope, preventing feedback loops.
@@ -369,7 +382,11 @@ class FunctionalBase extends Abstract {
369382
if (me.htmlTemplateProcessor) {
370383
me.htmlTemplateProcessor.process(newVdom, me);
371384
} else {
372-
console.error('enableHtmlTemplates is true, but HtmlTemplateProcessor is not available.');
385+
me.missedReadyState = true;
386+
// By calling this with an empty object, we ensure that the parent container
387+
// renders a placeholder DOM node for this component, which we can then
388+
// populate later once the template processor is ready.
389+
me.continueUpdateWithVdom({});
373390
}
374391
return; // Stop execution, the processor will call back
375392
}

0 commit comments

Comments
 (0)