Container
A Widget that can contain other widgets. Uses a CSS Grid layout by default, either use CSS or see the layout config to apply another layout.
//<code-header>
fiddle.title = 'Container';
//</code-header>
// A container containing three widgets
const container = new Container({
appendTo : targetElement,
labelPosition : 'align-before',
items : {
name : { type : 'text', value : 'Clark Kent', label : 'Name' },
score : { type : 'number', label : 'Score', value : 100 },
save : {
type : 'button',
text : 'Save',
rendition : 'filled',
column : 2,
onClick : () => {
const
name = container.widgetMap.name.value,
score = container.widgetMap.score.value;
if (score > 1000) {
Toast.show('New highscore!');
}
else {
Toast.show(`Saving ${name}s score, which was ${score}`);
}
}
}
}
});// Create a container with two widgets
const container = new Container({
items : {
name : { type : 'textfield', label : 'Name' },
score : { type : 'numberfield', label : 'Score' }
}
});
Label position & input field alignment
Fields support placing labels either before or above them. This can be set for all fields in a container at once by specifying the labelPosition config. It supports 3 settings:
'above'- Labels are placed above the field. For text fields in the Material3 theme, this means ~ on the fields top border.'before'- Labels are placed before the field.'align-before'- Labels are placed before the field and the fields are arranged in a two column layout (used in the live demo above).
align-before setting cannot be used in conjunction with specifying a Container layout.When using 'align-before', the container uses display: grid to lay out the fields. You can override this by
specifying a different grid-template-columns value in your CSS. To get two equal width columns, use:
.b-container.b-columns {
grid-template-columns: 1fr 1fr;
}
If you want input elements aligned to the right / end side of your container, enable inputFieldAlign as seen in the demo below.
//<code-header>
fiddle.title = 'Container alignment';
//</code-header>
new SlideToggle({
insertFirst : targetElement,
label : 'Align input elements to the right side',
style : 'margin-bottom: 3em',
checked : true,
onChange({ value }) {
container.inputFieldAlign = value ? 'end' : 'start';
}
});
const container = new Container({
appendTo : targetElement,
labelPosition : 'align-before',
inputFieldAlign : 'end',
style : 'grid-template-columns: 1fr 1fr;',
items : {
name : { type : 'text', value : 'Clark Kent', label : 'Name' },
score : { type : 'number', label : 'Score', value : 100 },
canFly : { type : 'slidetoggle', label : 'Can fly', value : true },
ownsBlueSuit : { type : 'slidetoggle', label : 'Owns a blue suit', value : true },
kryptonite : { type : 'slidetoggle', label : 'Kryptonite allergy', value : true }
}
});Adding and removing child widgets
Containers can have child widgets added, or removed during their lifecycle to accommodate business needs. For example:
// Insert a child widget before another field
myContainer.insert(textField, someField)
// Remove a child widget
myContainer.remove(checkboxField);
//<code-header>
fiddle.title = 'Container add and remove';
CSSHelper.insertRule([
`.b-country-widget {
display : flex;
flex-direction : column;
gap : .2em;
}`,
`.b-country-amount {
font-weight : 600;
}`
], targetElement);
//</code-header>
const max = 5;
class CountryWidget extends Widget {
static $name = 'CountryWidget';
static type = 'countrywidget';
static configurable = {
name : null,
amount : null
};
compose() {
const { name, amount } = this;
return {
children : {
nameEl : {
class : 'b-country-title',
text : name
},
amountEl : {
class : 'b-country-amount',
text : `$${amount}`
},
barEl : {
style : {
width : `${100 * amount / max}%`,
height : '.5em',
background : '#0276f8'
},
class : 'b-country-bar',
dataset : {
btip : `${name} $${amount}`
}
}
}
};
}
}
CountryWidget.initClass();
const container = new Container({
appendTo : targetElement,
width : '26em',
layout : 'vbox',
style : {
display : 'flex',
background : 'var(--b-neutral-96)',
'box-shadow' : '2px 0px 5px var(--b-neutral-99)',
'border-radius' : '1em',
padding : '2em',
gap : '.8em'
},
items : [
{ type : 'widget', tag : 'h2', html : 'Countries', style : 'font-size:1.8em;font-weight:500;margin:0 0 1em 0' },
{ type : 'countrywidget', name : 'United States', amount : 4.92 },
{ type : 'countrywidget', name : 'Sweden', amount : 4.02 },
{ type : 'countrywidget', name : 'Belgium', amount : 3.52 },
{ type : 'countrywidget', name : 'Denmark', amount : 2.2 },
{ type : 'countrywidget', name : 'Canada', amount : 1.74 }
]
});
new Button({
appendTo : targetElement,
text : 'Add country',
rendition : 'filled',
style : 'margin-top:1em',
onClick : () => {
const
names = ['South Africa', 'Mexico', 'Germany', 'France', 'Italy', 'Australia', 'Singapore', 'China', 'Malaysia', 'Netherlands', 'Malta'];
// Add a new country widget to the container
container.add({
type : 'countrywidget',
name : names[Math.round(Math.random() * 10)],
amount : (container.lastItem.amount * 0.8).toFixed(2)
});
}
});Configs
92
Configs
92Common
Content
A config object containing default settings to apply to all child widgets.
An object containing typed child widget config objects or Widgets. May also be specified as an array.
If configured as an Object, the property names are used as the child component's ref name, and the value is the child component's config object.
class MyContainer extends Container {
static configurable = {
items : {
details : {
type : 'panel',
....
},
button : {
type : 'button',
text : 'Save'
}
}
}
}
new MyContainer({
title : 'Test Container',
floating : true,
centered : true,
width : 600,
height : 400,
layout : 'fit',
items : {
button : {
disabled : true
},
details : {
title : 'More coolness',
html : 'Details content'
}
}
}).show();
The order of the child widgets is determined by the order they are defined in items, but can also be
affected by configuring a weight on one or more widgets.
To remove existing items, set corresponding keys to null.
If you want to customize child items of an existing class, you can do this using the child widget 'ref' identifier (useful for reconfiguring Event Editor in Scheduler / Gantt):
new MyCustomTabPanel({
items : {
// Reconfigure tabs
firstTab : {
title : 'My custom title'
},
secretTab : null // hide this tab
}
}).show();
An array of child item config objects which is to be converted into instances only when this Container is rendered, rather than eagerly at construct time.
This is mutually exclusive with the items config.
An object containing default config objects which may be referenced by name in the items
config. For example, a specialized Menu subclass may have a namedItems default
value defined like this:
namedItems : {
removeRow : {
text : 'Remove row',
onItem() {
this.ownerGrid.remove(this.ownerGrid.selectedRecord);
}
}
}
Then whenever that subclass is instantiated and configured with an items object, the
items may use the above defaults like this:
items : {
removeRow : true, // The referenced namedItem will be applied to this
otherItemRef : {
text : 'Option 2',
onItem() { }
}
}
If a menu instance (or its class) does not include removeRow in its items, no menu item will be
created. See also defaults.
Specify true for a container used to show text markup. It will apply the CSS class b-text-content
which specifies a default max-width that makes long text more readable.
This CSS class is automatically removed if the container adds/defines child Widgets.
CSS
Set true to add a border to this container's element.
An optional CSS class to add to child items of this container.
Layout
Specify true to make this container hide when it has no visible children (Either empty
or all children hidden).
Container will show itself when there are visible children, ie: hidden children are shown, or new visible children are added.
The short name of a helper class which manages rendering and styling of child items.
Or a config object which includes a type property which specifies which type
of layout to use, and how to configure that layout.
By default, the only special processing that is applied is that the Container class's itemCls is added to child items.
Containers use CSS flexbox in its default configuration to arrange child items. You may either use the layoutStyle configuration to tune how child items are layed out, or use one of the built in helper classes which include:
fitA single child item is displayed fitting exactly into the contentElement.cardChild items are displayed one at a time, size to fit the contentElement and are slid in from the side when activated.boxChild items are layed out using flexbox.
For example:
{
id : 'myContainer',
// Our child items flow downwards and are stretched to fill our width
layout : {
type : 'box',
direction : 'column'
align : 'stretch'
}
}
The CSS style properties to apply to the contentElement.
By default, a Container's contentElement uses flexbox layout, so this config may contain the following properties:
- flexDirection default '
row' - flexWrap
- flexFlow
- justifyContent
- alignItems
- alignContent
- placeContent
misc
When this container is used as a tab in a TabPanel, these items are added to the TabBar when this container is the active tab.
new TabPanel({
appendTo : targetElement,
height : '25em',
items: {
firstTab : {
title: 'First',
items: {
name : { type: 'textfield', label: 'Name' }
},
tabBarItems : [{
type : 'button',
text : 'Add tab',
onClick : 'up.onAddTabClick'
}]
}
},
onAddTabClick({ value }) {
this.add({
type : 'container',
title : 'New tab',
items : {
button : {
type : 'button',
text : 'Click me',
onClick() {
Toast.show('Awesome!');
}
}
}
});
}
});
Other
A query selector function which can identify the descendant widget to which focus should be directed by default.
Or a CSS selector string which identifies a descendant element to focus.
By default, the first focusable descendant widget is chosen. This may direct focus to a different widget:
new Popup({
title : 'Details',
width : '25em',
centered : true,
modal : true,
// Focus goes straight to OK button in the bottom toolbar on show
defaultFocus : w => w.ref === 'okButton',
items : {
nameField : {
type : 'textfield',
label : 'Name'
},
ageField : {
type : 'numberfield',
label : 'Name'
}
},
bbar : {
items : {
okButton : {
text : 'OK',
handler : okFunction
},
cancelButton : {
text : 'Cancel',
handler : cancelFunction
}
}
}
}).show();
| Parameter | Type | Description |
|---|---|---|
widget | Widget | Widget passed to method |
truthy value if widget is the default one
Convenience setting to align input fields of child widgets. By default, the Field input element is
placed immediately following the label. If you prefer to have all input fields aligned to the
right, set this config to 'end'.
//<code-header>
fiddle.title = 'Container alignment';
//</code-header>
new SlideToggle({
insertFirst : targetElement,
label : 'Align input elements to the right side',
style : 'margin-bottom: 3em',
checked : true,
onChange({ value }) {
container.inputFieldAlign = value ? 'end' : 'start';
}
});
const container = new Container({
appendTo : targetElement,
labelPosition : 'align-before',
inputFieldAlign : 'end',
style : 'grid-template-columns: 1fr 1fr;',
items : {
name : { type : 'text', value : 'Clark Kent', label : 'Name' },
score : { type : 'number', label : 'Score', value : 100 },
canFly : { type : 'slidetoggle', label : 'Can fly', value : true },
ownsBlueSuit : { type : 'slidetoggle', label : 'Owns a blue suit', value : true },
kryptonite : { type : 'slidetoggle', label : 'Kryptonite allergy', value : true }
}
});Convenience setting to use same label placement on all child widgets.
Set it to 'auto' to let the theme decide on label placement.
Specifying 'align-before' will position labels before the field and also apply a two column layout (in
combination with display: contents on the fields) to align all labels in the first column and the
fields in the second.
Either a default rendition to apply to all child widgets, or a map of renditions keyed by child widget
type.
// Mixed types, use object form:
new Container({
rendition : {
text : 'outlined',
button : 'raised'
},
items : [
{ type : 'text', label : 'Name' },
{ type : 'button', text : 'Save' }
]
});
// Single type, can use string form:
new Container({
rendition : 'outlined',
items : [
{ type : 'button', text : 'Save' },
{ type : 'button', text : 'Cancel' }
]
});
Record
Update assigned record automatically on field changes
Record whose values will be used to populate fields in the container.
Any descendant widgets of this Container with a name property (or a ref if no name is configured)
will have its value set to the value of that named property of the record.
If no record is passed, the widget has its value set to null.
To strictly match by the name property, configure strictRecordMapping as true.
When fields loaded from this record are changed by user input, the hasChanges property will be set.
Specify true to match fields by their name property only when assigning a record,
without falling back to ref.
DOM
Float & align
Misc
Scrolling
Properties
81
Properties
81Class hierarchy
Layout
The layout as an instance of Layout. This is a helper class which adds and removes child widgets to this Container's DOM and applies CSS classes based upon its requirements.
The card layout provides for showing one child widget at a time, and provides a switching API to change which child widget is currently active.
The CSS style properties to apply to the contentElement.
By default, a Container's contentElement uses flexbox layout, so this config may contain the following properties:
- flexDirection default '
row' - flexWrap
- flexFlow
- justifyContent
- alignItems
- alignContent
- placeContent
Other
Returns true if any of the fields in this container have been changed since the container was
loaded with a record.
Note that this value does not update during typing. The field's change event which is triggered when the
field is blurred causes this to be updated.
For example when processing a "close" button click, this value will be in its correct state.
Convenience setting to align input fields of child widgets. By default, the Field input element is
placed immediately following the label. If you prefer to have all input fields aligned to the
right, set this config to 'end'.
//<code-header>
fiddle.title = 'Container alignment';
//</code-header>
new SlideToggle({
insertFirst : targetElement,
label : 'Align input elements to the right side',
style : 'margin-bottom: 3em',
checked : true,
onChange({ value }) {
container.inputFieldAlign = value ? 'end' : 'start';
}
});
const container = new Container({
appendTo : targetElement,
labelPosition : 'align-before',
inputFieldAlign : 'end',
style : 'grid-template-columns: 1fr 1fr;',
items : {
name : { type : 'text', value : 'Clark Kent', label : 'Name' },
score : { type : 'number', label : 'Score', value : 100 },
canFly : { type : 'slidetoggle', label : 'Can fly', value : true },
ownsBlueSuit : { type : 'slidetoggle', label : 'Owns a blue suit', value : true },
kryptonite : { type : 'slidetoggle', label : 'Kryptonite allergy', value : true }
}
});Returns true if currently setting values. Allow fields to change highlighting to distinguishing between
initially setting values and later on changing values.
Returns true if all contained fields are valid, otherwise false
A property, which, when read, returns an array of the child items of this container in rendered order.
This property may also be set to change the child items of the container. Just as in the initial items configuration, the new value may either be an array of Widgets/Widget configs or an object.
If specified as an Object, the property names are used as the child Widget's ref name, and the value is the child Widget/Widget config.
When setting this, any items which are only in the outgoing child items which were created by this container from raw config objects are destroyed.
Usage patterns:
myContainer.items = {
name : {
type : 'textfield',
label : 'User name'
},
age : {
type : 'numberfield',
label : 'User age'
}
};
or
myContainer.items = [{
ref : 'name',
type : 'textfield',
label : 'User name'
},
ref : 'age',
type : 'numberfield',
label : 'User age'
}];
Convenience setting to use same label placement on all child widgets.
Set it to 'auto' to let the theme decide on label placement.
Specifying 'align-before' will position labels before the field and also apply a two column layout (in
combination with display: contents on the fields) to align all labels in the first column and the
fields in the second.
Either a default rendition to apply to all child widgets, or a map of renditions keyed by child widget
type.
// Mixed types, use object form:
new Container({
rendition : {
text : 'outlined',
button : 'raised'
},
items : [
{ type : 'text', label : 'Name' },
{ type : 'button', text : 'Save' }
]
});
// Single type, can use string form:
new Container({
rendition : 'outlined',
items : [
{ type : 'button', text : 'Save' },
{ type : 'button', text : 'Cancel' }
]
});
Retrieves or sets all values from/to contained widgets.
The property set or read from a contained widget is its defaultBindProperty.
This defaults to the value for fields.
You may add child widgets which may accept and yield a value to/from another property, such as a Button having
its href set.
Accepts and returns a map, using name, ref or id (in that order) as keys.
const container = new Container({
appendTo : document.body,
items : {
firstName : {
type : 'textfield
},
surName : {
type : 'textfield
}
saveButton : {
type : 'button',
text : 'Save',
defaultBindProperty : 'href'
href : '#'
}
}
});
container.values = {
firstName : 'Clark',
surname : 'Kent',
saveButton : '#save-route'
};
Record
Record whose values will be used to populate fields in the container.
Any descendant widgets of this Container with a name property (or a ref if no name is configured)
will have its value set to the value of that named property of the record.
If no record is passed, the widget has its value set to null.
To strictly match by the name property, configure strictRecordMapping as true.
When fields loaded from this record are changed by user input, the hasChanges property will be set.
Specify true to match fields by their name property only when assigning a record,
without falling back to ref.
Widget hierarchy
The number of visible child items shown in this Container.
An object which contains a map of descendant widgets keyed by their ref.
All descendant widgets will be available in the widgetMap.
CSS
DOM
Misc
Functions
67
Functions
67Other
Appends the passed widget / widgets or config(s) describing widgets to this Container.
If the widgets specify a weight, they are inserted at the correct index compared to the existing items weights.
| Parameter | Type | Description |
|---|---|---|
toAdd | ContainerItemConfig | ContainerItemConfig[] | Widget | Widget[] | The child or children instances (or config objects) to add. |
Returns the widget at the specified index in this Container.
| Parameter | Type | Description |
|---|---|---|
index | Number | The index of the widget to return. Negative numbers index for the last item. For example,
|
The requested widget.
Returns a directly contained widget by id
| Parameter | Type | Description |
|---|---|---|
id | String | The widget id |
Inserts the passed widget into this Container at the specified position.
| Parameter | Type | Description |
|---|---|---|
toAdd | Widget | The child to insert. |
index | Number | Widget | The index to insert at or the existing child to insert before. |
The added widget.
This function is called prior to creating widgets, override it in subclasses to allow containers to modify the configuration of each widget.
When adding a widget to a container hierarchy, each parent containers' processWidgetConfig will be called.
Returning false from the function prevents the widget from being added at all.
| Parameter | Type | Description |
|---|---|---|
widgetConfig | ContainerItemConfig | The widget configuration about to be created |
addingTo | Container | The container the widget is being added to |
Return false to prevent the widget from being added
Removes the passed child/children from this Container.
| Parameter | Type | Description |
|---|---|---|
toRemove | Widget | The child or children to remove. |
Resets field values and dirty tracking.
- Calling with no arguments restores the values captured at the last clean
setValuescall (i.e. the baseline established whensetValues(...)ran, typically via setting a record). - Calling with an object applies those values and establishes a new clean baseline.
| Parameter | Type | Description |
|---|---|---|
values | Object | Optional map of values to apply as the new clean state. |
Sets values into descendant fields/widgets.
Accepts either a data Model instance or a plain object whose keys map to field names/refs/ids. The second parameter is passed through to child widgets so they can customize how they resolve and assign their value (mirrors previous internal usage).
Change tracking (dirty state) is now managed here.
When called, existing dirty state is cleared and a new baseline of initialValues is captured.
| Parameter | Type | Description |
|---|---|---|
values | Object | Model | Value source (record or plain object). If omitted/ |
options | Object | Value assignment options object. Defaults to |
Configuration
Events
Misc
Widget hierarchy
Events
16
Events
16Fired before this container will load record values into its child fields. This is useful if you want to modify the UI before data is loaded (e.g. set some input field to be readonly)
// Adding a listener using the "on" method
container.on('beforeSetRecord', ({ source, record }) => {
});| Parameter | Type | Description |
|---|---|---|
source | Container | The container |
record | Model | The record |
Fires when a field is mutated and the state of the hasChanges property changes
// Adding a listener using the "on" method
container.on('dirtyStateChange', ({ source, dirty }) => {
});| Parameter | Type | Description |
|---|---|---|
source | Container | The container. |
dirty | Boolean | The dirty state of the Container - |
Event handlers
16
Event handlers
16Called before this container will load record values into its child fields. This is useful if you want to modify the UI before data is loaded (e.g. set some input field to be readonly)
new Container({
onBeforeSetRecord({ source, record }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | Container | The container |
record | Model | The record |
Called when a field is mutated and the state of the hasChanges property changes
new Container({
onDirtyStateChange({ source, dirty }) {
}
});| Parameter | Type | Description |
|---|---|---|
source | Container | The container. |
dirty | Boolean | The dirty state of the Container - |
Typedefs
6
Typedefs
6CSS variables
31
CSS variables
31| Name | Description |
|---|---|
--b-container-gap | Gap between container items |
--b-container-padding | Container padding |
--b-container-align-content | Containers align-content setting |
--b-container-color | Containers color setting |
--b-divider-font-size | Divider font size |
--b-divider-line-color | Divider line color |
--b-divider-text-color | Divider text color |
--b-divider-font-weight | Divider font weight |
--b-divider-margin-block | Divider block margin |
--b-container-border-width | Border width for bordered containers |
--b-container-border-color | Border color for bordered containers |