neo.mjs 10.0.0-beta.2 → 10.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ServiceWorker.mjs +2 -2
- package/apps/form/view/FormPageContainer.mjs +2 -3
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/portal/view/learn/ContentComponent.mjs +18 -11
- package/learn/README.md +9 -14
- package/learn/guides/Collections.md +436 -0
- package/learn/guides/CustomComponents.md +256 -14
- package/learn/guides/ExtendingNeoClasses.md +331 -0
- package/learn/guides/Forms.md +449 -1
- package/learn/guides/Layouts.md +246 -1
- package/learn/guides/Records.md +286 -0
- package/learn/guides/form_fields/ComboBox.md +241 -0
- package/learn/tree.json +8 -3
- package/package.json +1 -1
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +9 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/form/field/ComboBox.mjs +6 -1
- package/src/vdom/Helper.mjs +7 -5
package/learn/guides/Forms.md
CHANGED
@@ -1 +1,449 @@
|
|
1
|
-
|
1
|
+
The Neo.mjs Forms Engine provides a powerful and flexible way to build
|
2
|
+
user interfaces for data input and validation. This guide will walk you
|
3
|
+
through the core concepts and practical usage of forms in Neo.mjs,
|
4
|
+
from basic field creation to advanced validation and nested structures.
|
5
|
+
|
6
|
+
## 1. Basic Form Creation
|
7
|
+
|
8
|
+
At its core, a form in Neo.mjs is a `Neo.form.Container`. This container
|
9
|
+
manages a collection of form fields and provides methods for data
|
10
|
+
retrieval, validation, and submission.
|
11
|
+
|
12
|
+
To create a simple form, you define a `Neo.form.Container` and add
|
13
|
+
`Neo.form.field.Text` or other field modules to its `items` config.
|
14
|
+
|
15
|
+
```javascript readonly
|
16
|
+
import FormContainer from '../../src/form/Container.mjs';
|
17
|
+
import TextField from '../../src/form/field/Text.mjs';
|
18
|
+
|
19
|
+
class MySimpleForm extends FormContainer {
|
20
|
+
static config = {
|
21
|
+
className: 'MySimpleForm',
|
22
|
+
layout : {ntype: 'vbox', align: 'start'},
|
23
|
+
items : [{
|
24
|
+
module : TextField,
|
25
|
+
labelText: 'First Name',
|
26
|
+
name : 'firstName',
|
27
|
+
required : true
|
28
|
+
}, {
|
29
|
+
module : TextField,
|
30
|
+
labelText: 'Last Name',
|
31
|
+
name : 'lastName'
|
32
|
+
}]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
```
|
36
|
+
|
37
|
+
In this example:
|
38
|
+
* `module: TextField` specifies the type of form field.
|
39
|
+
* `labelText` defines the visible label for the field.
|
40
|
+
* `name` is crucial for data management; it defines the key under
|
41
|
+
which the field's value will be stored when retrieving form data.
|
42
|
+
* `required: true` enables basic validation, ensuring the field is
|
43
|
+
not left empty.
|
44
|
+
|
45
|
+
## 2. Field Types
|
46
|
+
|
47
|
+
Neo.mjs offers a rich set of pre-built form field types, all extending
|
48
|
+
`Neo.form.field.Base`. These fields cover a wide range of input needs:
|
49
|
+
|
50
|
+
* **Text-based Inputs**: `TextField`, `TextArea`, `EmailField`,
|
51
|
+
`PasswordField`, `PhoneField`, `UrlField`, `SearchField`,
|
52
|
+
`DisplayField` (read-only), `HiddenField`.
|
53
|
+
* **Numeric Inputs**: `NumberField`, `CurrencyField`, `RangeField`.
|
54
|
+
* **Selection Inputs**: `ComboBox`, `Chip`, `ColorField`, `DateField`,
|
55
|
+
`TimeField`, `CountryField`, `ZipCodeField`.
|
56
|
+
* **Choice Inputs**: `CheckBox`, `Radio`, `Switch`.
|
57
|
+
* **File Upload**: `FileUpload`.
|
58
|
+
|
59
|
+
You can find the full list of available fields and their configurations
|
60
|
+
in the `src/form/field/` directory.
|
61
|
+
|
62
|
+
## 3. Data Management
|
63
|
+
|
64
|
+
A key strength of Neo.mjs forms is their integrated state management. The `Neo.form.Container` automatically manages form data based on field names, eliminating the need for external state management libraries or manual state tree definitions. This significantly simplifies data handling and reduces boilerplate.
|
65
|
+
|
66
|
+
The `Neo.form.Container` provides powerful methods for managing form data.
|
67
|
+
|
68
|
+
### Getting Form Values
|
69
|
+
|
70
|
+
To retrieve all values from a form, use the asynchronous `getSubmitValues()`
|
71
|
+
method. This method returns a plain JavaScript object where keys correspond
|
72
|
+
to the `name` attributes of your fields.
|
73
|
+
|
74
|
+
```javascript readonly
|
75
|
+
// Assuming 'myForm' is an instance of your form container
|
76
|
+
const formValues = await myForm.getSubmitValues();
|
77
|
+
console.log(formValues);
|
78
|
+
// Example output: { firstName: 'John', lastName: 'Doe' }
|
79
|
+
```
|
80
|
+
|
81
|
+
### Nested Data Structures
|
82
|
+
|
83
|
+
The `name` attribute supports dot notation to create nested data structures.
|
84
|
+
This is particularly useful for organizing complex form data.
|
85
|
+
|
86
|
+
```javascript readonly
|
87
|
+
import FormContainer from '../../src/form/Container.mjs';
|
88
|
+
import TextField from '../../src/form/field/Text.mjs';
|
89
|
+
|
90
|
+
class UserForm extends FormContainer {
|
91
|
+
static config = {
|
92
|
+
className: 'UserForm',
|
93
|
+
layout : {ntype: 'vbox', align: 'start'},
|
94
|
+
items : [{
|
95
|
+
module : TextField,
|
96
|
+
labelText: 'User First Name',
|
97
|
+
name : 'user.profile.firstName'
|
98
|
+
}, {
|
99
|
+
module : TextField,
|
100
|
+
labelText: 'User Last Name',
|
101
|
+
name : 'user.profile.lastName'
|
102
|
+
}, {
|
103
|
+
module : TextField,
|
104
|
+
labelText: 'Address Street',
|
105
|
+
name : 'user.address.street'
|
106
|
+
}]
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
// ... later, after the form is rendered and values are entered
|
111
|
+
const userFormValues = await myUserForm.getSubmitValues();
|
112
|
+
console.log(userFormValues);
|
113
|
+
/*
|
114
|
+
Output:
|
115
|
+
{
|
116
|
+
user: {
|
117
|
+
profile: {
|
118
|
+
firstName: 'Jane',
|
119
|
+
lastName : 'Doe'
|
120
|
+
},
|
121
|
+
address: {
|
122
|
+
street: '123 Main St'
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
*/
|
127
|
+
```
|
128
|
+
|
129
|
+
### Setting Form Values
|
130
|
+
|
131
|
+
You can pre-populate a form or update its values programmatically using
|
132
|
+
the `setValues(values, suspendEvents)` method. The `values` object should
|
133
|
+
mirror the structure of the data returned by `getSubmitValues()`.
|
134
|
+
|
135
|
+
```javascript readonly
|
136
|
+
// To set values for the UserForm example above:
|
137
|
+
await myUserForm.setValues({
|
138
|
+
user: {
|
139
|
+
profile: {
|
140
|
+
firstName: 'Alice',
|
141
|
+
lastName : 'Smith'
|
142
|
+
},
|
143
|
+
address: {
|
144
|
+
street: '456 Oak Ave'
|
145
|
+
}
|
146
|
+
}
|
147
|
+
});
|
148
|
+
```
|
149
|
+
|
150
|
+
The optional `suspendEvents` parameter (default `false`) can be set to
|
151
|
+
`true` to prevent `change` events from firing for each field during the
|
152
|
+
update, which can be useful for large data sets.
|
153
|
+
|
154
|
+
### Resetting Forms
|
155
|
+
|
156
|
+
The `reset(values)` method allows you to clear or reset form fields.
|
157
|
+
If no `values` object is provided, fields will be reset to `null` or their
|
158
|
+
`emptyValue` (if configured). If `values` are provided, fields will be
|
159
|
+
reset to those specific values.
|
160
|
+
|
161
|
+
```javascript readonly
|
162
|
+
// Reset all fields to their default empty state
|
163
|
+
await myForm.reset();
|
164
|
+
|
165
|
+
// Reset specific fields to new values
|
166
|
+
await myForm.reset({
|
167
|
+
firstName: 'Default Name'
|
168
|
+
});
|
169
|
+
```
|
170
|
+
|
171
|
+
## 4. Validation
|
172
|
+
|
173
|
+
Neo.mjs forms provide robust validation capabilities, both built-in and
|
174
|
+
customizable.
|
175
|
+
|
176
|
+
### Built-in Validation
|
177
|
+
|
178
|
+
Many field types come with built-in validation rules:
|
179
|
+
|
180
|
+
* **`required`**: Ensures a field is not empty.
|
181
|
+
* **`minLength` / `maxLength`**: For text-based fields, validates the
|
182
|
+
length of the input.
|
183
|
+
* **`minValue` / `maxValue`**: For numeric fields, validates the range
|
184
|
+
of the input.
|
185
|
+
* **`inputPattern`**: A regular expression to validate the input format.
|
186
|
+
(e.g., `EmailField`, `UrlField`, `ZipCodeField` use this internally).
|
187
|
+
|
188
|
+
You can configure these directly on the field:
|
189
|
+
|
190
|
+
```javascript readonly
|
191
|
+
import TextField from '../../src/form/field/Text.mjs';
|
192
|
+
|
193
|
+
// ...
|
194
|
+
items: [{
|
195
|
+
module : TextField,
|
196
|
+
inputPattern: /^[a-zA-Z0-9_]+$/, // Alphanumeric and underscore only
|
197
|
+
labelText : 'Username',
|
198
|
+
name : 'username',
|
199
|
+
required : true,
|
200
|
+
minLength : 5,
|
201
|
+
maxLength : 20
|
202
|
+
}]
|
203
|
+
```
|
204
|
+
|
205
|
+
Error messages for built-in validations can be customized using `errorText*`
|
206
|
+
configs (e.g., `errorTextRequired`, `errorTextMaxLength`).
|
207
|
+
|
208
|
+
### Custom Validation (`validator`)
|
209
|
+
|
210
|
+
For more complex validation logic, you can use the `validator` config on
|
211
|
+
any field. This should be a function that receives the field instance as
|
212
|
+
its argument and returns `true` if the value is valid, or a string
|
213
|
+
(the error message) if it's invalid.
|
214
|
+
|
215
|
+
```javascript readonly
|
216
|
+
import TextField from '../../src/form/field/Text.mjs';
|
217
|
+
|
218
|
+
// ...
|
219
|
+
items: [{
|
220
|
+
module : TextField,
|
221
|
+
labelText: 'Password',
|
222
|
+
name : 'password',
|
223
|
+
reference: 'passwordField' // Add a reference to the password field
|
224
|
+
}, {
|
225
|
+
module : TextField,
|
226
|
+
labelText: 'Confirm Password',
|
227
|
+
name : 'confirmPassword',
|
228
|
+
validator: function(field) {
|
229
|
+
// Access the password field using getClosestForm().getReference()
|
230
|
+
const passwordField = field.getClosestForm().getReference('passwordField');
|
231
|
+
|
232
|
+
if (field.value !== passwordField.value) {
|
233
|
+
return 'Passwords do not match'
|
234
|
+
}
|
235
|
+
return true
|
236
|
+
}
|
237
|
+
}]
|
238
|
+
```
|
239
|
+
|
240
|
+
### Displaying Errors
|
241
|
+
|
242
|
+
Errors are automatically displayed below the field when validation fails.
|
243
|
+
The `useAlertState` config (globally set in `apps/form/Overwrites.mjs`)
|
244
|
+
can change the visual styling of required but empty fields from red to orange.
|
245
|
+
|
246
|
+
The `clean` property on a field determines if errors are shown immediately.
|
247
|
+
By default, `clean` is `true` until the user interacts with the field or
|
248
|
+
`validate(false)` is called.
|
249
|
+
|
250
|
+
### Form Validation State
|
251
|
+
|
252
|
+
You can check the overall validity of a form using:
|
253
|
+
|
254
|
+
* **`isValid()`**: An asynchronous method that returns `true` if all
|
255
|
+
fields in the form (and its nested forms) are valid, `false` otherwise.
|
256
|
+
It also triggers validation for all fields.
|
257
|
+
|
258
|
+
```javascript readonly
|
259
|
+
const formIsValid = await myForm.isValid();
|
260
|
+
if (formIsValid) {
|
261
|
+
console.log('Form is valid!');
|
262
|
+
} else {
|
263
|
+
console.log('Form has errors.');
|
264
|
+
}
|
265
|
+
```
|
266
|
+
|
267
|
+
* **`getFormState()`**: An asynchronous method that returns a string
|
268
|
+
indicating the overall state of the form:
|
269
|
+
* `'clean'`: All fields are untouched and valid.
|
270
|
+
* `'valid'`: All fields are valid.
|
271
|
+
* `'invalid'`: At least one field is invalid.
|
272
|
+
* `'inProgress'`: Some fields are valid, some are clean.
|
273
|
+
|
274
|
+
```javascript readonly
|
275
|
+
const state = await myForm.getFormState();
|
276
|
+
console.log('Form state:', state);
|
277
|
+
```
|
278
|
+
|
279
|
+
## 5. Nested Forms
|
280
|
+
|
281
|
+
Neo.mjs allows for true nested forms, providing unparalleled structural
|
282
|
+
flexibility. This is achieved by nesting `Neo.form.Container` instances
|
283
|
+
or using `Neo.form.Fieldset`.
|
284
|
+
|
285
|
+
### Using `Neo.form.Fieldset`
|
286
|
+
|
287
|
+
`Neo.form.Fieldset` extends `Neo.form.Container` and is ideal for
|
288
|
+
grouping related fields visually. It can also be collapsed.
|
289
|
+
|
290
|
+
```javascript readonly
|
291
|
+
import Fieldset from '../../src/form/Fieldset.mjs';
|
292
|
+
import FormContainer from '../../src/form/Container.mjs';
|
293
|
+
import TextField from '../../src/form/field/Text.mjs';
|
294
|
+
|
295
|
+
class NestedFieldsetForm extends FormContainer {
|
296
|
+
static config = {
|
297
|
+
className: 'NestedFieldsetForm',
|
298
|
+
layout : {ntype: 'vbox', align: 'start'},
|
299
|
+
items : [{
|
300
|
+
module : Fieldset,
|
301
|
+
title : 'Personal Information',
|
302
|
+
formGroup: 'person', // Data will be nested under 'person'
|
303
|
+
items : [{
|
304
|
+
module : TextField,
|
305
|
+
labelText: 'First Name',
|
306
|
+
name : 'firstName',
|
307
|
+
required : true
|
308
|
+
}, {
|
309
|
+
module : TextField,
|
310
|
+
labelText: 'Last Name',
|
311
|
+
name : 'lastName'
|
312
|
+
}]
|
313
|
+
}, {
|
314
|
+
module : Fieldset,
|
315
|
+
title : 'Contact Information',
|
316
|
+
formGroup: 'contact', // Data will be nested under 'contact'
|
317
|
+
items : [{
|
318
|
+
module : TextField,
|
319
|
+
labelText: 'Email',
|
320
|
+
name : 'email',
|
321
|
+
required : true
|
322
|
+
}, {
|
323
|
+
module : TextField,
|
324
|
+
labelText: 'Phone',
|
325
|
+
name : 'phone'
|
326
|
+
}]
|
327
|
+
}]
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
331
|
+
// Example getSubmitValues() output:
|
332
|
+
/*
|
333
|
+
{
|
334
|
+
person: {
|
335
|
+
firstName: 'John',
|
336
|
+
lastName : 'Doe'
|
337
|
+
},
|
338
|
+
contact: {
|
339
|
+
email: 'john.doe@example.com',
|
340
|
+
phone: '123-456-7890'
|
341
|
+
}
|
342
|
+
}
|
343
|
+
*/
|
344
|
+
```
|
345
|
+
|
346
|
+
The `formGroup` config on `Fieldset` (or any `Form.Container`) automatically
|
347
|
+
nests the data of its child fields under the specified key.
|
348
|
+
|
349
|
+
### Nesting `Form.Container` Instances
|
350
|
+
|
351
|
+
You can directly nest `Form.Container` instances to create more complex
|
352
|
+
hierarchies. This is demonstrated in `apps/form/view/FormPageContainer.mjs`,
|
353
|
+
which extends `Neo.form.Container` but uses a `div` for its `vdom` tag
|
354
|
+
to avoid invalid HTML (`<form>` inside `<form>`).
|
355
|
+
|
356
|
+
```javascript readonly
|
357
|
+
// Example from apps/form/view/FormPageContainer.mjs
|
358
|
+
import BaseFormContainer from '../../../src/form/Container.mjs';
|
359
|
+
|
360
|
+
class FormPageContainer extends BaseFormContainer {
|
361
|
+
static config = {
|
362
|
+
className: 'Form.view.FormPageContainer',
|
363
|
+
// ... other configs
|
364
|
+
tag: 'div' // Using a div instead of a form tag
|
365
|
+
}
|
366
|
+
}
|
367
|
+
```
|
368
|
+
|
369
|
+
This allows you to treat each nested container as a sub-form, which can
|
370
|
+
be validated or have its values retrieved independently, or as part of
|
371
|
+
the top-level form.
|
372
|
+
|
373
|
+
## 6. Field Triggers
|
374
|
+
|
375
|
+
Field triggers are small, interactive icons or buttons that appear within
|
376
|
+
or alongside a form field, providing additional functionality. Examples
|
377
|
+
include clear buttons, date pickers, or spin buttons for number fields.
|
378
|
+
|
379
|
+
Triggers are configured via the `triggers` array on a field. You can
|
380
|
+
configure multiple triggers for a single field, and control their placement
|
381
|
+
(left or right of the input) using the `align` config on the trigger.
|
382
|
+
|
383
|
+
```javascript readonly
|
384
|
+
import DateField from '../../src/form/field/Date.mjs';
|
385
|
+
import ClearTrigger from '../../src/form/field/trigger/Clear.mjs';
|
386
|
+
|
387
|
+
// ...
|
388
|
+
items: [{
|
389
|
+
module : DateField,
|
390
|
+
labelText: 'Event Date',
|
391
|
+
name : 'eventDate',
|
392
|
+
triggers : [{
|
393
|
+
module: ClearTrigger // Adds a clear button to the date field
|
394
|
+
}]
|
395
|
+
}]
|
396
|
+
```
|
397
|
+
|
398
|
+
Many fields automatically include default triggers (e.g., `DateField`
|
399
|
+
includes a `DateTrigger`). You can override or add to these defaults.
|
400
|
+
|
401
|
+
## 7. Form and Field Events
|
402
|
+
|
403
|
+
Neo.mjs forms and fields emit various events that you can listen to for
|
404
|
+
custom logic:
|
405
|
+
|
406
|
+
* **`change`**: Fired when a field's `value` config changes.
|
407
|
+
* **`userChange`**: Fired when a field's value changes due to direct
|
408
|
+
user interaction (e.g., typing in a text field).
|
409
|
+
* **`fieldChange`**: Fired on the `Form.Container` when any of its
|
410
|
+
child fields' `value` changes.
|
411
|
+
* **`fieldUserChange`**: Fired on the `Form.Container` when any of its
|
412
|
+
child fields' value changes due to user interaction.
|
413
|
+
* **`focusEnter` / `focusLeave`**: Fired when a field gains or loses focus.
|
414
|
+
|
415
|
+
You can listen to these events using the `on` method:
|
416
|
+
|
417
|
+
```javascript readonly
|
418
|
+
manyFormField.on('change', (data) => {
|
419
|
+
console.log('Field value changed:', data.value);
|
420
|
+
});
|
421
|
+
|
422
|
+
myFormContainer.on('fieldUserChange', (data) => {
|
423
|
+
console.log('User changed field:', data.component.name, data.value);
|
424
|
+
});
|
425
|
+
```
|
426
|
+
|
427
|
+
## 8. Best Practices and Tips
|
428
|
+
|
429
|
+
* **`itemDefaults`**: Use `itemDefaults` on containers to apply common
|
430
|
+
configurations to all child items, reducing boilerplate.
|
431
|
+
* **`formGroup`**: Leverage `formGroup` for logical grouping of data,
|
432
|
+
especially in complex forms, to create clean nested data structures.
|
433
|
+
* **`readOnly` vs. `editable`**:
|
434
|
+
* `readOnly: true`: Prevents user interaction from changing the value.
|
435
|
+
The field is still part of the form data.
|
436
|
+
* `editable: false`: Similar to `readOnly`, but specifically for
|
437
|
+
input elements, preventing direct typing. Other interactions (like
|
438
|
+
picker selection) might still be possible unless also `readOnly`.
|
439
|
+
* **Lazy Loading**: For forms with many pages or complex sections, consider
|
440
|
+
lazy loading modules for individual pages or fieldsets to improve initial
|
441
|
+
application load times. This is demonstrated in `apps/form/view/FormContainer.mjs`
|
442
|
+
where pages are imported dynamically.
|
443
|
+
* **Explicit Module Imports**: While the core `Neo` global namespace is always available, it's a best practice to
|
444
|
+
explicitly import all Neo.mjs modules you use (e.g., `import FormContainer from '../../src/form/Container.mjs';`).
|
445
|
+
Relying on implicit availability of classes within `Neo`'s sub-namespaces can lead to less readable and maintainable code.
|
446
|
+
Explicit imports improve code readability, maintainability, and ensure consistent behavior.
|
447
|
+
* **`Neo.overwrites`**: Use global overwrites (as seen in `apps/form/Overwrites.mjs`)
|
448
|
+
to enforce consistent styling or behavior across all instances of a
|
449
|
+
component type.
|
package/learn/guides/Layouts.md
CHANGED
@@ -1 +1,246 @@
|
|
1
|
-
##
|
1
|
+
## Understanding Layouts in Neo.mjs
|
2
|
+
|
3
|
+
Layouts are fundamental to arranging components within your application's user interface. In Neo.mjs, layouts are
|
4
|
+
managed declaratively through the `layout` configuration property of container components. This system provides a
|
5
|
+
powerful and flexible way to control the positioning, sizing, and alignment of child components.
|
6
|
+
|
7
|
+
### How Layouts Work
|
8
|
+
|
9
|
+
Every container component (any class extending `Neo.container.Base`) can have a `layout` config. This config defines
|
10
|
+
how the container's `items` (its child components) are arranged. When you set a `layout` on a container, the framework
|
11
|
+
automatically handles the positioning and sizing of its children, adapting to different screen sizes and dynamic content.
|
12
|
+
|
13
|
+
### The `layout` Config
|
14
|
+
|
15
|
+
The `layout` config is an object that typically includes an `ntype` property, specifying the type of layout to use.
|
16
|
+
Depending on the `ntype`, additional properties can be provided to customize the layout's behavior.
|
17
|
+
|
18
|
+
Example:
|
19
|
+
|
20
|
+
```javascript
|
21
|
+
layout: {
|
22
|
+
ntype: 'vbox',
|
23
|
+
align: 'center'
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
### The 'base' Layout (`ntype: 'base'`)
|
28
|
+
|
29
|
+
In scenarios where you prefer to manage the positioning and sizing of child components entirely through custom CSS or
|
30
|
+
in-line styles, you can use the `'base'` layout. This layout type provides minimal interference, essentially acting as a
|
31
|
+
pass-through, allowing you full control over the styling of your container's children.
|
32
|
+
|
33
|
+
When `ntype: 'base'` is used, the container will not apply any specific flexbox or grid-based layout rules to its children.
|
34
|
+
This is useful for highly customized components or when integrating with external styling libraries.
|
35
|
+
|
36
|
+
### Common Layout Types
|
37
|
+
|
38
|
+
Neo.mjs provides several built-in layout types to cover a wide range of UI design needs. Here, we'll explore some of the
|
39
|
+
most commonly used ones.
|
40
|
+
|
41
|
+
#### 1. VBox Layout (`ntype: 'vbox'`)
|
42
|
+
|
43
|
+
The VBox (Vertical Box) layout arranges child components in a single vertical column. It's ideal for creating stacked
|
44
|
+
sections or forms where elements flow from top to bottom.
|
45
|
+
|
46
|
+
**Key Properties for VBox Layouts:**
|
47
|
+
|
48
|
+
- `align`: Controls the horizontal alignment of items within the column.
|
49
|
+
- `'left'` (default): Aligns items to the left.
|
50
|
+
- `'center'`: Centers items horizontally.
|
51
|
+
- `'right'`: Aligns items to the right.
|
52
|
+
- `'stretch'`: Stretches items to fill the available width of the container.
|
53
|
+
|
54
|
+
- `pack`: Controls how items are packed along the vertical axis (main axis).
|
55
|
+
- `'start'` (default): Items are packed towards the top.
|
56
|
+
- `'center'`: Items are centered vertically.
|
57
|
+
- `'end'`: Items are packed towards the bottom.
|
58
|
+
- `'space-between'`: Items are evenly distributed with space between them.
|
59
|
+
- `'space-around'`: Items are evenly distributed with space around them (including half-space at ends).
|
60
|
+
|
61
|
+
- `flex`: A property applied to individual child items, not the layout itself. It determines how an item grows or
|
62
|
+
shrinks to fill available space within the VBox. A `flex` value of `1` means the item will expand to fill remaining
|
63
|
+
space.
|
64
|
+
|
65
|
+
**Example:**
|
66
|
+
|
67
|
+
```javascript live-preview
|
68
|
+
import Container from '../container/Base.mjs';
|
69
|
+
import Button from '../button/Base.mjs';
|
70
|
+
|
71
|
+
class VBoxExample extends Container {
|
72
|
+
static config = {
|
73
|
+
className: 'Example.view.VBoxExample',
|
74
|
+
layout: {
|
75
|
+
ntype: 'vbox',
|
76
|
+
align: 'center', // Center items horizontally
|
77
|
+
pack: 'center' // Center items vertically
|
78
|
+
},
|
79
|
+
items: [{
|
80
|
+
module: Button,
|
81
|
+
text: 'Button 1',
|
82
|
+
width: 100
|
83
|
+
}, {
|
84
|
+
module: Button,
|
85
|
+
text: 'Button 2',
|
86
|
+
width: 150
|
87
|
+
}, {
|
88
|
+
module: Button,
|
89
|
+
text: 'Button 3',
|
90
|
+
flex: 1 // This button will expand to fill remaining vertical space
|
91
|
+
}]
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
Neo.setupClass(VBoxExample);
|
96
|
+
```
|
97
|
+
|
98
|
+
#### 2. HBox Layout (`ntype: 'hbox'`)
|
99
|
+
|
100
|
+
The HBox (Horizontal Box) layout arranges child components in a single horizontal row. It's commonly used for toolbars,
|
101
|
+
navigation menus, or any scenario where elements need to be displayed side-by-side.
|
102
|
+
|
103
|
+
**Key Properties for HBox Layouts:**
|
104
|
+
|
105
|
+
- `align`: Controls the vertical alignment of items within the row.
|
106
|
+
- `'top'` (default): Aligns items to the top.
|
107
|
+
- `'center'`: Centers items vertically.
|
108
|
+
- `'bottom'`: Aligns items to the bottom.
|
109
|
+
- `'stretch'`: Stretches items to fill the available height of the container.
|
110
|
+
|
111
|
+
- `pack`: Controls how items are packed along the horizontal axis (main axis).
|
112
|
+
- `'start'` (default): Items are packed towards the left.
|
113
|
+
- `'center'`: Items are centered horizontally.
|
114
|
+
- `'end'`: Items are packed towards the right.
|
115
|
+
- `'space-between'`: Items are evenly distributed with space between them.
|
116
|
+
- `'space-around'`: Items are evenly distributed with space around them (including half-space at ends).
|
117
|
+
|
118
|
+
- `flex`: Similar to VBox, `flex` applied to individual child items determines how they grow or shrink to fill
|
119
|
+
available horizontal space within the HBox.
|
120
|
+
|
121
|
+
**Example:**
|
122
|
+
|
123
|
+
```javascript live-preview
|
124
|
+
import Container from '../container/Base.mjs';
|
125
|
+
import Button from '../button/Base.mjs';
|
126
|
+
|
127
|
+
class HBoxExample extends Container {
|
128
|
+
static config = {
|
129
|
+
className: 'Example.view.HBoxExample',
|
130
|
+
layout: {
|
131
|
+
ntype: 'hbox',
|
132
|
+
align: 'center', // Center items vertically
|
133
|
+
pack: 'start' // Pack items to the left
|
134
|
+
},
|
135
|
+
items: [{
|
136
|
+
module: Button,
|
137
|
+
text: 'Button A',
|
138
|
+
height: 50
|
139
|
+
}, {
|
140
|
+
module: Button,
|
141
|
+
text: 'Button B',
|
142
|
+
height: 70
|
143
|
+
}, {
|
144
|
+
module: Button,
|
145
|
+
text: 'Button C',
|
146
|
+
flex: 1 // This button will expand to fill remaining horizontal space
|
147
|
+
}]
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
Neo.setupClass(HBoxExample);
|
152
|
+
```
|
153
|
+
|
154
|
+
#### 3. Card Layout (`ntype: 'card'`)
|
155
|
+
|
156
|
+
The Card layout is designed to display one child component at a time, making it ideal for tab panels, wizards, or any
|
157
|
+
interface where content needs to be switched without navigating away. Only the active card is visible, while others are
|
158
|
+
hidden.
|
159
|
+
|
160
|
+
**Key Properties for Card Layouts:**
|
161
|
+
|
162
|
+
- `activeIndex_`: This is the most important config. Changing its value activates a different child component (card).
|
163
|
+
The framework automatically handles showing the new card and hiding the old one.
|
164
|
+
|
165
|
+
- `removeInactiveCards`: A boolean (default `true`). If `true`, the DOM elements of inactive cards are removed from the
|
166
|
+
document flow, keeping only their instances and VDOM trees. This is useful for performance, especially with many
|
167
|
+
cards, as it reduces the number of elements the browser has to render. If `false`, inactive cards remain in the DOM
|
168
|
+
but are hidden via CSS.
|
169
|
+
|
170
|
+
- `slideDirection_`: A string (`'horizontal'`, `'vertical'`, or `null` - default `null`). This property enables
|
171
|
+
animated transitions when switching between cards. Setting it to `'horizontal'` or `'vertical'` will make the cards
|
172
|
+
slide into view.
|
173
|
+
|
174
|
+
**Example:**
|
175
|
+
|
176
|
+
```javascript live-preview
|
177
|
+
import Container from '../container/Base.mjs';
|
178
|
+
import Button from '../button/Base.mjs';
|
179
|
+
|
180
|
+
class CardExample extends Container {
|
181
|
+
static config = {
|
182
|
+
className: 'Example.view.CardExample',
|
183
|
+
layout: {
|
184
|
+
ntype: 'card',
|
185
|
+
activeIndex: 0 // Start with the first card active
|
186
|
+
},
|
187
|
+
items: [{
|
188
|
+
module: Container,
|
189
|
+
cls: 'card-panel',
|
190
|
+
items: [{
|
191
|
+
module: Button,
|
192
|
+
text: 'Go to Card 2',
|
193
|
+
handler: function() {
|
194
|
+
this.up('container').layout.activeIndex = 1;
|
195
|
+
}
|
196
|
+
}],
|
197
|
+
style: {
|
198
|
+
backgroundColor: '#e0f7fa',
|
199
|
+
padding: '20px',
|
200
|
+
textAlign: 'center'
|
201
|
+
}
|
202
|
+
}, {
|
203
|
+
module: Container,
|
204
|
+
cls: 'card-panel',
|
205
|
+
items: [{
|
206
|
+
module: Button,
|
207
|
+
text: 'Go to Card 1',
|
208
|
+
handler: function() {
|
209
|
+
this.up('container').layout.activeIndex = 0;
|
210
|
+
}
|
211
|
+
}],
|
212
|
+
style: {
|
213
|
+
backgroundColor: '#fff3e0',
|
214
|
+
padding: '20px',
|
215
|
+
textAlign: 'center'
|
216
|
+
}
|
217
|
+
}]
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
Neo.setupClass(CardExample);
|
222
|
+
```
|
223
|
+
|
224
|
+
#### Lazy Loading with Card Layouts
|
225
|
+
|
226
|
+
One powerful feature of the Card layout is its ability to lazy load content. This means that the JavaScript module for a
|
227
|
+
card's content is only loaded when that card becomes active, significantly improving initial application load times.
|
228
|
+
|
229
|
+
This is achieved by defining the `module` property of an item within the `items` array as a function that returns a
|
230
|
+
dynamic `import()` statement. For example, in the Portal app's `Viewport.mjs`,
|
231
|
+
modules are lazy-loaded like this:
|
232
|
+
|
233
|
+
```javascript
|
234
|
+
items: [
|
235
|
+
{module: () => import('./home/MainContainer.mjs')},
|
236
|
+
{module: () => import('./learn/MainContainer.mjs')},
|
237
|
+
// ... other lazy-loaded modules
|
238
|
+
]
|
239
|
+
```
|
240
|
+
|
241
|
+
When `activeIndex` changes to a card configured this way, Neo.mjs automatically executes the import function, loads the
|
242
|
+
module, and then creates the component instance. This ensures that resources are only consumed when they are actually
|
243
|
+
needed.
|
244
|
+
|
245
|
+
This is just the beginning of understanding layouts in Neo.mjs. In subsequent sections, we will explore more advanced
|
246
|
+
layout types and concepts like nesting layouts for complex UI structures.
|