Customizing React Menu and List Item Templates
Wijmo's React UI components, such as our Menu and list-like controls (ListBox, ComboBox, MultiSelect) allow us to customize their items' content imperatively, by generating an element tree for each item using JS code and DOM API in a formatItem event.
But when it comes to React, it sounds tempting to potentially define an item's content declaratively, in JSX markup, using React components with their property bindings.
In the Wijmo 2019 v2 release, we added two special components to the Wijmo React interop:
- Specially for Menu, we added MenuItem and MenuSeparator child components, which allows you to define menu items and separators declaratively in JSX.
- For all major list-like controls (ListBox, ComboBox, MultiSelect, Menu), we added special wjItemTemplate render prop, which allows you to specify a render function that draws items content.
React Menu Items
A pure JavaScript Menu control supposes that its menu items are defined in an array bound to the control's itemsSource property, that is, items are defined in the model part of the React component. This is not always very convenient, because actually the menu items are pertaining to the UI part of the component. And it would be more convenient to define them using React UI means, that is, a JSX markup.
Now it's possible by means of the new MenuItem component from the @grapecity/wijmo.react.input module. Instead of defining items in an array, you can now use MenuItem components nested to their Menu component right in the render function's JSX, where every MenuItem defines a separate menu item, with a content of an arbitrary complexity in it. In addition, a MenuSeparator component can be used to insert separators between menu items. For example, the following piece of JSX code from this sample defines File menu items with rich content and a separator before the Exit item:
<wjInput.Menu
header="File"
itemClicked={this.menuItemClicked}>
<wjInput.MenuItem>
<span className="glyphicon glyphicon-asterisk"></span>
<b>New</b>
<br />
<small><i>create a new file</i></small>
</wjInput.MenuItem>
<wjInput.MenuItem>
<span className="glyphicon glyphicon-folder-open"></span>
<b>Open</b>
<br />
<small><i>open an existing file or folder</i></small>
</wjInput.MenuItem>
<wjInput.MenuItem>
<span className="glyphicon glyphicon-floppy-disk"></span>
<b>Save</b>
<br />
<small><i>save the current file</i></small>
</wjInput.MenuItem>
<wjInput.MenuSeparator></wjInput.MenuSeparator>
<wjInput.MenuItem>
<span className="glyphicon glyphicon-remove"></span>
<b>Exit</b>
<br />
<small><i>exit the application</i></small>
</wjInput.MenuItem>
</wjInput.Menu>
You can also generate multiple MenuItems dynamically from a data source describing their data.
For example, the markup snippet below, taken from this sample, maps the palettes array to the array of MenuItem components representing its data:
<wjInput.Menu
header="Palette"
maxDropDownHeight={300}
value={this.state.thePalette}>
{this.palettes.map((value) => {
return <wjInput.MenuItem value={value.name}>
{value.name}
<span style=\{{float:'right'}}>
{value.colors.map((color) => {
return <div style=\{{
backgroundColor: color,
display:'inline',
padding:'2px',
height:'10px',
width:'3px'}}>
</div>
})}
</span>
</wjInput.MenuItem>
})}
</wjInput.Menu>
Note that the Menu component here is not bound to a data array using its itemsSource property. Instead, it generates child MenuItem components from the data array.
Commands
You can now declaratively define menu items bound to commands with parameters. The MenuItem component exposes cmd and cmdParam properties out of its interface, which can be bound to a command and its parameter respectively. The markup snippet below, taken from this sample, demonstrates the use of these properties to define menu items that increment or decrement a value. Each item represents a different increment value specified as the command parameter:
<wjInput.Menu
header="Tax Commands"
id="changeTax"
command={this.state.command}>
<wjInput.MenuItem cmd={this.state.command} cmdParam={0.50}>
Increment by 50%
</wjInput.MenuItem>
<wjInput.MenuItem cmd={this.state.command} cmdParam={0.25}>
Increment by 25%
</wjInput.MenuItem>
<wjInput.MenuItem cmd={this.state.command} cmdParam={0.05}>
Increment by 5%
</wjInput.MenuItem>
<wjInput.MenuSeparator></wjInput.MenuSeparator>
<wjInput.MenuItem cmd={this.state.command} cmdParam={-0.05}>
Decrement by 5%
</wjInput.MenuItem>
<wjInput.MenuItem cmd={this.state.command} cmdParam={-0.25}>
Decrement by 25%
</wjInput.MenuItem>
<wjInput.MenuItem cmd={this.state.command} cmdParam={-0.50}>
Decrement by 50%
</wjInput.MenuItem>
</wjInput.Menu>
Value Pickers
With the introduction of the MenuItem component, the Menu can now be used as a value picker. The MenuItem component has the value property containing a value associated with the item. And the Menu itself has the value property, whose value is displayed next to the menu header. This property can be bound to the parent component state, and the itemClicked event can be used to update the state with the value associated with the selected menu item. This is demonstrated in this example showing the menu that can be used to choose a browser name:
render() {
return <wjInput.Menu
header="Run"
value={this.state.browser}
itemClicked={this.splitButtonItemClicked}>
<wjInput.MenuItem value="IE">Internet Explorer</wjInput.MenuItem>
<wjInput.MenuItem value="Chrome">Chrome</wjInput.MenuItem>
<wjInput.MenuItem value="FF">FireFox</wjInput.MenuItem>
<wjInput.MenuItem value="Safari">Safari</wjInput.MenuItem>
<wjInput.MenuItem value="Opera">Opera</wjInput.MenuItem>
</wjInput.Menu>
}
splitButtonItemClicked = (menu) => {
this.setState({
browser: menu.selectedItem.value
});
};
Item Templates
You use formatItem event to create a customized item content for pure JS list-like controls, like ListBox, ComboBox, MultiSelect and Menu. The corresponding React components now allow you to describe items content declaratively, using JSX markup. Each of the mentioned components now exposes a wjItemTemplate render prop, which allows you to specify a render function that draws items content. For example, the following markup from this sample draws a glyph and its name taken from the source array:
<wjInput.ListBox
displayMemberPath="country"
itemsSource={this.state.glyphs}
selectedIndexChanged={this.onSelectedIndexChanged}
initialized={this.onInitialized}
wjItemTemplate={(context: wjInput.ItemTemplateContext)=>(
<div>
<div className="wj-glyph">
<span className={`wj-glyph-${context.item}`}></span>
</div>
{context.item}
</div>
)}>
</wjInput.ListBox>
Note that the wjItemTemplate render function provides a context parameter, which is an object that brings information about the currently drawing item. It exposes the following properties:
- item - the data item whose content is being rendered
- itemIndex - the index of the item being rendered
- control - the underlying pure JS control represented by the React component
The MultiSelect Component
The MultiSelect component has one peculiarity comparing to other list-like controls. Every its item should contain a checkbox which changes the checked state of the item. When it comes to items customization using item template, you will most probably want to keep this functionality. By convention, the first input element with the type="checkbox" in the item template will be used as a checked state switch. The following markup snippet from this sample defines an item template containing such a checkbox:
<wjInput.MultiSelect
displayMemberPath="name"
headerPath="name"
placeholder="Countries"
itemsSource={this.state.data}
showSelectAllCheckbox={this.state.showSelectAllCheckbox}
checkedItemsChanged={this._onCheckedItemsChanged.bind(this)}
wjItemTemplate={(context: wjInput.ItemTemplateContext)=>(
<div className="item">
<label><input type="checkbox"/>{context.item.name}</label>
<br/>
<b>{context.item.city}</b> ({context.item.state})<br/>
{context.item.address}<br/>
Phone: <b>{context.item.phone}</b><br/>
Fax: <b>{context.item.fax}</b><br/>
Website: <a href="{context.item.site}" target="_blank">{context.item.site}</a><br/>
</div>
)}>
</wjInput.MultiSelect>
Menu
In the topic above we already demonstrated how MenuItem components can be dynamically created from an array of data items. The wjItemTemplate render prop is an alternative way to achieve a similar functionality. In this case a Menu component should be bound to an array of items, and wjItemTemplate render prop defines a template that renders each item. For example, the markup snippet from this sample shows customized items for the menu bound to an array of musicians:
header="Artists"
itemClicked={this.menuItemClicked}
itemsSource={this.musicians}
maxDropDownHeight={300}
wjItemTemplate={(context: wjInput.ItemTemplateContext)=>(
<div style=\{{minWidth: '160px'}}>
{context.itemIndex + 1}. <b>{context.item.name}</b>
{context.item.photo ?
<div>
<img src={context.item.photo} height="50" />
</div>
: null}
</div>
)}>
</wjInput.Menu>
Conclusion
New MenuItem component and wjItemTemplate render prop raises the convenience of Wijmo's React Input components customization to a new level. It's now possible to define their content using conventional JSX markup, with a rich content that includes other application components with bindings.