Skip to content

Commit d006480

Browse files
committed
docs: Create a new tutorial for building a functional component #7121
1 parent 9647052 commit d006480

2 files changed

Lines changed: 180 additions & 0 deletions

File tree

learn/tree.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "id": "tutorials/RSP", "hidden": true},
6565
{"name": "Earthquakes", "parentId": "Tutorials", "id": "tutorials/Earthquakes"},
6666
{"name": "Todo List", "parentId": "Tutorials", "id": "tutorials/TodoList"},
67+
{"name": "Creating a Functional Button", "parentId": "Tutorials", "id": "tutorials/CreatingAFunctionalButton"},
6768
{"name": "JavaScript Classes", "parentId": null, "isLeaf": false, "id": "JavaScript", "collapsed": true},
6869
{"name": "Classes, Properties, and Methods", "parentId": "JavaScript", "id": "javascript/Classes"},
6970
{"name": "Overriding Methods", "parentId": "JavaScript", "id": "javascript/Overrides"},
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Creating a Custom Functional Button
2+
3+
In the "Describing a View" guide, you learned the basics of functional and class-based components. Now, let's dive
4+
deeper into the modern approach by creating our own custom, reusable functional button.
5+
6+
**Note:** Neo.mjs already provides a powerful, feature-rich functional button (`Neo.functional.button.Base`). The purpose
7+
of this guide is not to replace it, but to use a button as a simple, practical example to teach you the fundamentals of
8+
creating your own functional components.
9+
10+
This guide will walk you through the process of building a `MyCoolButton` component that has its own unique style and
11+
behavior, using the `defineComponent` helper.
12+
13+
## 1. Defining the Component
14+
15+
First, let's create the basic structure of our component. We'll use `defineComponent` and provide a `className`
16+
and a `createVdom` method.
17+
18+
```javascript readonly
19+
import {defineComponent} from '../../src/functional/_export.mjs';
20+
21+
const MyCoolButton = defineComponent({
22+
className: 'My.CoolButton',
23+
24+
createVdom(config) {
25+
// We will build our VDOM here
26+
return {
27+
tag : 'button',
28+
cls : ['my-cool-button'],
29+
text: 'Click Me'
30+
}
31+
}
32+
});
33+
34+
export default MyCoolButton;
35+
```
36+
37+
## 2. Adding Custom Configs
38+
39+
A component isn't very reusable without configs. Let's add `text_` and `iconCls_` to our component's public API.
40+
Remember, the trailing underscore `_` makes the config reactive.
41+
42+
We'll also update `createVdom` to use these configs. The `config` parameter of `createVdom` is a reactive proxy to the
43+
component's instance, so we can access our configs directly from it.
44+
45+
```javascript readonly
46+
import {defineComponent} from '../../src/functional/_export.mjs';
47+
48+
const MyCoolButton = defineComponent({
49+
className: 'My.CoolButton',
50+
51+
// 1. Define the public API
52+
config: {
53+
iconCls_: null,
54+
text_ : 'Default Text'
55+
},
56+
57+
// 2. Use the configs in createVdom
58+
createVdom(config) {
59+
const {iconCls, text} = config;
60+
61+
return {
62+
tag: 'button',
63+
cls: ['my-cool-button'],
64+
cn : [{
65+
tag : 'span',
66+
cls : ['fa', iconCls],
67+
removeDom: !iconCls // Don't render the span if no iconCls is provided
68+
}, {
69+
tag: 'span',
70+
cls: ['my-cool-button-text'],
71+
text
72+
}]
73+
}
74+
}
75+
});
76+
77+
export default MyCoolButton;
78+
```
79+
80+
## 3. Handling User Events
81+
82+
Static buttons are boring. Let's make it interactive. We can add a `handler_` config and an `onClick` method to our
83+
component. The `addDomListeners` method in the `construct` hook allows us to listen for native DOM events.
84+
85+
```javascript readonly
86+
import {defineComponent} from '../../src/functional/_export.mjs';
87+
88+
const MyCoolButton = defineComponent({
89+
className: 'My.CoolButton',
90+
91+
config: {
92+
handler_: null, // A function to call on click
93+
iconCls_: null,
94+
text_ : 'Default Text'
95+
},
96+
97+
construct(config) {
98+
// The super.construct call is important!
99+
// It sets up the component's lifecycle and effects.
100+
this.super(config);
101+
102+
this.addDomListeners({
103+
click: this.onClick,
104+
scope: this
105+
});
106+
},
107+
108+
createVdom(config) {
109+
// ... (same as before)
110+
},
111+
112+
onClick(data) {
113+
// If a handler function is provided, call it.
114+
this.handler?.(this);
115+
}
116+
});
117+
118+
export default MyCoolButton;
119+
```
120+
121+
## 4. Using Your Custom Component
122+
123+
Now you can use `MyCoolButton` just like any other Neo.mjs component, either in a functional or a class-based view.
124+
125+
```javascript live-preview
126+
import {defineComponent} from '../functional/_export.mjs';
127+
import Container from '../container/Base.mjs';
128+
129+
// 1. Define our custom button
130+
const MyCoolButton = defineComponent({
131+
className: 'My.CoolButton',
132+
config: {
133+
handler_: null,
134+
iconCls_: null,
135+
text_ : 'Default Text'
136+
},
137+
construct(config) {
138+
this.super(config);
139+
this.addDomListeners({click: this.onClick, scope: this});
140+
},
141+
createVdom(config) {
142+
const {iconCls, text} = config;
143+
return {
144+
tag: 'button',
145+
cls: ['my-cool-button'],
146+
cn : [
147+
{tag: 'span', cls: ['fa', iconCls], removeDom: !iconCls},
148+
{tag: 'span', cls: ['my-cool-button-text'], text}
149+
]
150+
}
151+
},
152+
onClick(data) {
153+
this.handler?.(this);
154+
}
155+
});
156+
157+
// 2. Use it in a MainView
158+
class MainView extends Container {
159+
static config = {
160+
className: 'GS.guides.CustomButtonMainView',
161+
layout : {ntype: 'vbox', align: 'start'},
162+
items : [{
163+
module : MyCoolButton,
164+
iconCls: 'fa-star',
165+
text : 'My Button!',
166+
handler(button) {
167+
console.log('Button clicked!', button);
168+
button.text = 'Clicked!'; // It's reactive!
169+
}
170+
}]
171+
}
172+
}
173+
174+
MainView = Neo.setupClass(MainView);
175+
```
176+
177+
This example demonstrates the full power of the functional component model. You can quickly create reusable, reactive,
178+
and encapsulated components with a clean and modern API. From here, you could add more configs, more complex VDOM logic,
179+
or even internal state using the `useConfig` hook to build even more powerful components.

0 commit comments

Comments
 (0)