Transforming An Existing React Front End Using KendoReact Components
KendoReact components come in four different flavors covering all modern front-end frameworks – jQuery, Angular, React and Vue. They offer widgets that can be used right out of the box, while also offering endless customizability to fit all existing use-cases.
Here we will see one such example of migrating an existing React front end from a multitude of libraries to a unified system using KendoReact components. This allows the streamlining and unification of the codebase, easing future development tasks.
What are the benefits of KendoReact?
There are numerous benefits of migrating to KendoReact. The first one is replacing all the different libraries and external packages with a unified framework offering everything in one package. And KendoReact does that by bringing a selection of components that are easy to use in all use-cases – from creating modern layouts to building data-rich applications.
The components offered by KendoReact make application building easier, faster and more efficient. They are composable and precisely configurable to give developers the ability to work with them just as they would any other React component and – of course – they support both controlled and uncontrolled states.
Finally, using KendoReact offers a stable framework that gets regular updates and is always on top of new changes in both the React and JavaScript worlds, while bringing easy maintainability for future-proofing.
The migration in detail
We will focus on three specific cases – one involving the KendoReact Grid component. The Grid allows us to create a unified interface for displaying, sorting and filtering tabled data. The second case showcases several of the layout and dialog components. The Drawer, Window and AppBar components are ready-to-use to create a user interface, fulfilling all the expectations for a modern application. The final is an example of the rich Forms support included in KendoReact.
Users table
Here in the old interface, we were using a table along with a custom filter component. By introducing the KendoReact Grid component, we are able to do away with the custom filter and use the included functionality to replace it. Implementing sorting is also painless, as it is a built-in capability of the Grid.
Old interface
New interface
And here is the code for the above interface. It is a single Grid which has all the needed functionality of the original example, but it’s all packaged into one – including filters, sorting and other actions needed to replace the original interface.
Each data column is specified by the GridColumn component. The actions at the top of the grid are specified by the GridToolbar component.
Scroll to the end of this article to see what the code looks like.
Layout
The AppBar component will replace the existing navbar at the top of all pages. It allows for easy unification of the brand identity and navigation items that are present throughout the web app. The Drawer component is used as side navigation which gives a clean interface to navigation items that are used less often and thus do not need to be visible at all times.
Old interface
New interface
Scroll to the end of this article to see what the code looks like.
Form in Window
Finally, we will showcase the Form and Window components. Here they are used as a pop-up interface for adding and editing existing Users. The Form offers out-of-the-box support for creating complex fields for all types of inputs, along with custom ones.
Old interface
New interface
Scroll to the end of this article to see what the code looks like.
In summary
KendoReact is a great choice for all types of front-end use-cases – from simple presentation pages to extensive data-rich web applications. The library also comes in three other flavors, covering all modern front-end libraries – jQuery, Angular, and Vue. The use of KendoReact in our specific case allows us to transition from a scattering of front-end libraries and components to a unified component library, which eases development tasks as well as allowing a unified and fully functional user interface.
This article was written by Kalin Koychev, KendoReact guru and Web Developer.
Users table:
<Grid
data={orderBy(
filterBy(this.state.mappedUsers, this.state.filter),
this.state.sort
)}
scrollable="none"
filterable
sortable={true}
sort={this.state.sort}
onSortChange={(e) => {
this.setState({
sort: e.sort,
});
}}
filter={this.state.filter}
onFilterChange={(e) => {
this.setState({
filter: e.filter,
});
}}
>
<GridToolbar>
<ChipList
selection="single"
data={chipData}
onDataChange={this.handleChipListChange}
chip={(props) => <Chip onClick={this.handleChipListChange} {...props} />}
/>
<div>
<Button icon="close" look="flat" onClick={this.handleFilterReset}>
Clear Filters
</Button>
<ConfigurationPropertiesContext.Consumer>
{({ internalUserManagement }) =>
internalUserManagement && (
<Button
icon="plus-outline"
look="flat"
onClick={() => this.handleOpenModal(USER_CREATE_MODE)}
>
Add new user
</Button>
)
}
</ConfigurationPropertiesContext.Consumer>
</div>
</GridToolbar>
<GridColumn
title="Name"
field="fullName"
width="240px"
cell={(props) => (
<td>
<Avatar
shape="rounded"
style={{
marginRight: ".75rem",
width: "40px",
height: "40px",
}}
>
<img
src={UserService.getUserProfilePictureUrlByEmail(
props.dataItem.email
)}
/>
</Avatar>
{props.dataItem.fullName}
</td>
)}
/>
<GridColumn field="email" title="e-mail" />
<GridColumn
field="mappedRoles"
title="Role"
filterCell={dropdownFilterCell(
this.state.roles.map((role) => role.name),
"Any"
)}
cell={(props) => <td>{props.dataItem[props.field]}</td>}
/>
<GridColumn
field="managerName"
title="Manager"
filterCell={dropdownFilterCell(
this.state.managers.map(
(manager) => `${manager.firstName} ${manager.lastName}`
),
"Any manager"
)}
/>
<GridColumn
title="Actions"
width="120px"
filterable={false}
sortable={false}
cell={(props) => {
const user = props.dataItem;
const principal = this.props.user;
return (
<td>
<Tooltip openDelay={100} anchorElement="target" position="top">
<Icon
name="edit"
title="Edit User"
onClick={() => this.handleOpenModal(USER_EDIT_MODE, user)}
/>
<ConfigurationPropertiesContext.Consumer>
{({ internalUserManagement }) =>
internalUserManagement &&
principal.id !== user.id && (
<Icon
name="delete"
title="Delete User"
onClick={() => this.handleOpenModal(USER_DELETE_MODE, user)}
/>
)
}
</ConfigurationPropertiesContext.Consumer>
<RunAsBtn user={props.dataItem} principal={principal} />
</Tooltip>
</td>
);
}}
/>
</Grid>
dataproperty is used to pass all the data to the GridorderByandfilterByare used to manage the displaying of the datasortable,sortonSortChangeare properties needed for the sorting of the Gridfilterable,filterandonFilterChangeare properties needed for the filtering of the GridGridToolbarallows for adding a toolbar containing different actions to the top of the Grid. In this case, we have added aChipListused for additional filters outside of the existing columns. There are also two buttons – one to clear all filters – and another with a custom action – opening a window for adding new users.GridColumncomponents, which specify each column of the Grid. Some only need afieldandtitleproperty that tell the Grid which data field to use and what the column title would be. Others use thecellproperty to render a custom view in the table cell for the items in that columnfilterCellis used to pass a custom filtering component for certain columns.
Layout:
First, we will showcase the AppDrawer which replaces the top navigation bar.
<AppBar className="row">
<AppBarSection>
<Link to="/" className="navbar-brand">
<img src="/images/logo.png" alt="Proxiad" width="120" height="33" />
</Link>
</AppBarSection>
<AppBarSection>
<nav className="navbar-nav">
{AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
<NavLink exact to="/mygoals" activeClassName="selected">
My Goals
</NavLink>
)}
{AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
<NavLink exact to="/appraisal" activeClassName="selected">
Appraisal
</NavLink>
)}
{AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
<NavLink exact to="/appraisal/history" activeClassName="selected">
History
</NavLink>
)}
{AuthenticationService.hasPermission(
user,
PERMISSION_ASSESS_EMPLOYEES
) && (
<NavLink exact to="/manager" activeClassName="selected">
Manager
</NavLink>
)}
{AuthenticationService.hasPermission(
user,
PERMISSION_MANAGE_ORGANIZATIONAL_GOALS
) && (
<NavLink
exact
to="/goals/template/organizational"
activeClassName="selected"
>
Organizational Goals
</NavLink>
)}
{AuthenticationService.hasPermission(
user,
PERMISSION_MANAGE_GOAL_TEMPLATES
) && (
<NavLink exact to="/goals/template" activeClassName="selected">
Goals Template
</NavLink>
)}
{AuthenticationService.hasPermission(user, PERMISSION_MANAGE_PROCESS) && (
<NavLink exact to="/processes" activeClassName="selected">
Processes
</NavLink>
)}
</nav>
</AppBarSection>
<AppBarSpacer />
{(AuthenticationService.hasPermission(user, PERMISSION_MANAGE_USERS) ||
AuthenticationService.hasPermission(user, PERMISSION_MANAGE_ROLES) ||
AuthenticationService.hasPermission(user, PERMISSION_MANAGE_TEAMS)) && (
<AppBarSection className="actions">
<ButtonGroup>
<DropDownButton
ref={settingsDropdown}
icon="cog"
look="flat"
onItemClick={(event) => history.push(event.item.to)}
>
{AuthenticationService.hasPermission(
user,
PERMISSION_MANAGE_USERS
) && (
<DropDownButtonItem
text="Users"
iconClass="fa fa-users-cog fa-fw"
to="/admin/users"
/>
)}
{AuthenticationService.hasPermission(
user,
PERMISSION_MANAGE_ROLES
) && (
<DropDownButtonItem
text="Roles"
iconClass="fa fa-cogs fa-fw"
to="/admin/roles"
/>
)}
{AuthenticationService.hasPermission(
user,
PERMISSION_MANAGE_TEAMS
) && (
<DropDownButtonItem
text="Teams"
iconClass="fa fa-users fa-fw"
to="/admin/teams"
/>
)}
</DropDownButton>
<Button
icon="arrow-60-down"
look="flat"
onClick={() => {
if (!document.querySelector(".k-popup .k-list"))
settingsDropdown.current.mainButton.click();
}}
/>
</ButtonGroup>
</AppBarSection>
)}
{user && (
<AppBarSection>
<ButtonGroup>
<Button
imageUrl={userProfilePicture}
look="flat"
onClick={() => setShowUserProfile(!showUserProfile)}
/>
<Button
icon="arrow-60-down"
look="flat"
onClick={() => setShowUserProfile(!showUserProfile)}
/>
</ButtonGroup>
</AppBarSection>
)}
</AppBar>
- The
AppBaroffers an easy-to-use navigation component, which can contain any custom configurations. It is made up ofAppBarSectionandAppBarSpacercomponents. Each section separates different items inside theAppBar, while theAppBarSpaceradds extra separation between sections.
The second part of the above interface is the Drawer component, which replaces the side navigation.
<Drawer
expanded={showUserProfile}
position={"end"}
mode={"overlay"}
onOverlayClick={() => setShowUserProfile(false)}
onSelect={onSelect}
items={items}
item={(props) => {
return (
<DrawerItem {...props}>
{props.user && (
<div>
<Avatar type="image" size="medium" shape="circle">
<img src={userProfilePicture} alt="User avatar" />
</Avatar>
{user.firstName} {user.lastName} <br />
{user.title} <br />
{user.email} <br />
{`${user.roles.map((r) => getRoleName(r)).join(" / ")}`} <br />
</div>
)}
{!props.user && <div>{props.text}</div>}
{props.versiondata && (
<div>
{props.versiondata.git
? `Ver.: ${props.versiondata.build.version} / Rev.${props.versiondata.git.commit.id} / ${props.versiondata.git.commit.time}`
: `Ver.: ${props.versiondata.build.version}`}
</div>
)}
</DrawerItem>
);
}}
>
<DrawerNavigation></DrawerNavigation>
</Drawer>
Draweris used for side navigationexpandedproperty sets the display of the drawerpositionandmodecontrol the display styleonOverlayClickcontrols the behavior when clicking outside of the drawer navigationonSelectcontrols the functionality of clicking an item in the draweritemsis the total items to be displayed in the draweritemallows for custom rendering, which in this case is used to display different items, as in our use case we have a few differently displayed items from the others – it returns aDrawerItemcomponent for each item.
Form in window:
And here is the code – first for the form itself, which uses the Form (aliased as KendoForm) and FormElement components.
<KendoForm
onSubmit={handleSubmit}
render={(formRenderProps) => (
<FormElement>
<fieldset>
<Field name={"firstName"} component={Input} label={"First Name"} />
<Field name={"lastName"} component={Input} label={"Last Name"} />
</fieldset>
<Field name={"title"} component={Input} label={"Position Title"} />
<fieldset>
<Field name={"username"} component={Input} label={"Username"} />
<Field name={"email"} component={Input} label={"Email"} />
</fieldset>
<fieldset>
<Field name={"password"} component={Input} label={"Password"} />
<Field
name={"confirmPassword"}
component={Input}
label={"Confirm Password"}
disabled={true}
/>
</fieldset>
<DropDownList label="Manager"></DropDownList>
<DropDownList label="Teams"></DropDownList>
<KendoLabel>Roles</KendoLabel>
<fieldset>
{hcRoles.map((role, i) => (
<KendoLabel style={{ marginBottom: "5px" }} key={i}>
<Switch />
{role}
</KendoLabel>
))}
</fieldset>
<div>
<fieldset style={{ display: "flex", flexWrap: "wrap" }}>
<KendoLabel style={{ marginRight: "5px" }}>
<Switch defaultChecked={true} />
Active
</KendoLabel>
<KendoLabel>
<Switch />
Limited visibility
</KendoLabel>
</fieldset>
<fieldset style={{ display: "flex", flexWrap: "wrap" }}>
<KendoButton style={{ marginRight: "5px" }}>Cancel</KendoButton>
<KendoButton
type={"submit"}
primary={true}
onClick={formRenderProps.onSubmit}
>
Submit
</KendoButton>
</fieldset>
</div>
</FormElement>
)}
/>
- The form containing all the different form fields. The
Fieldcomponent is used and it’s passed the type of field as acomponentprop.
And the code for the pop-up, which employs the Window component:
<Window
title={"Add new user"}
onClose={this.handleCloseModal}
initialTop={0}
initialHeight={660}
initialWidth={
window.screen.width > 750
? window.screen.width / 2
: window.screen.width - 50
}
>
{(this.state.mode === USER_CREATE_MODE ||
this.state.mode === USER_EDIT_MODE) && (
<EditUserForm
user={this.state.selectedUser}
handleClose={this.handleCloseModal}
handleFormSubmit={this.handleSubmit}
users={this.state.users}
roles={this.state.roles}
teams={this.state.teams}
/>
)}
</Window>
Windowis a component that creates a pop-up window. The content of the component is rendered inside the displayed window, in this caseEditUserForm, which is the custom form.