Introduction
In this tutorial, I’ll build a small interactive form in 3 different CSS frameworks.
I want to demonstrate how to use two-way data binding for complex objects in react using react-binding.
React-binding is lightweight mixin called BindToMixin in react.
Features
BindToMixin offers two-way data binding support for:
- object properties with path expression (dot notation)
- this.bindToState(“data”,”Employee.FirstName”);
- this.bindToState(“data”,”Employee.Contact.Email”);
- complex objects (json) with nested properties
- this.bindTo(employee,”FirstName”);
- this.bindTo(employee,”Contact.Email”);
- collection-based structures - arrays and lists
- model={this.bindTo(employee,”FirstName”)}
- this.props.model.items.map(function(item){ return (<Hobby model={hobby}/>);})
- this.props.model.add()
- this.props.model.remove(item)
- model={this.bindTo(employee,”FirstName”)}
- supports for “value/requestChange” interface also to enable to use ReactLink attribute
- valueLink={this.bindTo(employee,”FirstName”)}
- usable with any css frameworks
Basic principle
Each bindTo return and uses interface called “value/onChange”. Each bindTo component is passed a value (to render it to UI) as well as setter to a value that triggers a re-render (typically at the top location).
The re-render is done in the component where you bind to the state via (bindToState, bindArrayToState).
BindTo can be nested - composed to support components composition. Then path is concatenate according to parent-child relationship.
Example usage
If you design a typical business application, you can distinguish among 3 concerns - UI, logic, data.
- UI should be generally agnostic towards your data and your logic. It’s for rendering UI and dealing with UI events.
- logic should be generally agnostic towards your UI and your data. It’s for execution of business rules (validations) and business processes.
- data should be generally agnostic of UI and logic. It’s a persistent storage.
Libraries used for implementation
- UI - react - react is agnostic towards data and data -> you could think of React as the View in MVC.
- Logic - business-rules-engine is agnostic towards UI -> you could think of React as the Controller in MVC.
- Data - plain JSON -> you could think of React as the Model in MVC.
1. define structure for data
In all cases we want to capture data from user in this form
{
"Person": {
"LastName": "Smith",
"FirstName": "Adam",
"Contact": {
"Email": "smith@gmail.com"
}
},
"Hobbies": [
{
"HobbyName": "Bandbington",
"Frequency": "Daily",
"Paid": true,
"Recommendation": true
},
{
"HobbyName": "Cycling",
"Frequency": "Daily",
"Recommendation": false,
"Paid": false
}
]
}
2. describe logic - business rules
We use business-rules-engine for defining business rules.
I choose JSON schema declarative description. For other possible formats read declarative-vs-imperative-validation.
{
"Person": {
"type": "object",
"properties": {
"FirstName": {
"type": "string",
"title": "First name",
"required": "true",
"maxLength": "15"
},
"LastName": {
"type": "string",
"title": "Last name",
"required": "true",
"maxLength": "15"
},
"Contact": {
"type": "object",
"properties": {
"Email": {
"type": "string",
"title": "Email",
"required": "true",
"maxLength": 100,
"email": "true"
}
}
}
}
},
"Hobbies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"HobbyName": {
"type": "string",
"title": "HobbyName",
"required": "true",
"maxLength": 100
},
"Frequency": {
"type": "string",
"title": "Frequency",
"enum": ["Daily", "Weekly", "Monthly"]
},
"Paid": {
"type": "boolean",
"title": "Paid"
},
"Recommedation": {
"type": "boolean",
"title": "Recommedation"
}
}
},
"maxItems": 4,
"minItems": 2
}
}
It is a simple task to use business rules in our form.
- add rules to state -> create validation rules new FormSchema.JsonSchemaRuleFactory(BusinessRulesJson).CreateRule(“Main”)
- execute all validation rules - this.state.rules.Validate(this.state.data) to get results
- using Utils.CompositeDotObject.Transform as helper function that enables to access to validation result comfortably by dot notation (with path expressions) - see one-way binding to error messages below
var HobbyForm = React.createClass({
mixins: [BindToMixin],
getInitialState: function() {
return {
data: {},
rules:new FormSchema.JsonSchemaRuleFactory(BusinessRulesJson).CreateRule("Main")
};
},
result:function(){
var validationResult = this.state.rules.Validate(this.state.data);
return Utils.CompositeDotObject.Transform(validationResult).Main;
},
_handleDialogOpen: function() {
if (this.result().HasError){
//TODO: show error dialog;
//return;
}
//send to server to persist
//for demo -> show data dialog
this.refs.customDialog.show();
},
}
3. UI - using React
- using BindToMixin for two-way data binding.
- using react one-way binding to display error messages.
Summary for two-way data bindings for HobbyForm - using model and valueLink property
- PersonComponent - model={this.bindToState(“data”, “Person”)}
- TextBoxInput - valueLink={this.bindTo(this.props.model, “FirstName”)}
- TextBoxInput - valueLink={this.bindTo(this.props.model, “LastName”)}
- TextBoxInput - valueLink={this.bindTo(this.props.model, “Contact.Email”)}
- Button - onClick={this.addHobby}
- HobbyList component - model={this.bindArrayToState(“data”, “Hobbies”)}
- Hobby component
- TextBoxInput - valueLink={this.bindTo(this.props.model, “HobbyName”)
- Button - onClick={this.removeHobby}
- RadioGroup - valueLink={this.bindTo(this.props.model, “Frequency”)
- CheckBoxInput model={this.bindTo(this.props.model, “Paid”)}
- CheckBoxInput model={this.bindTo(this.props.model, “Recommendation”)}
- Hobby component
Summary for one-way binding to error messages - using error property for error messages
- PersonComponent - error={this.result().Person}
- TextBoxInput - error={this.props.error.FirstName.ErrorMessage}
- TextBoxInput - error={this.props.error.LastName.ErrorMessage}
- TextBoxInput - error={this.props.error.Contact.Email.ErrorMessage}
- Span - {this.result().Hobbies.PropRule.ErrorMessage}
- HobbyList component - errors={this.result().Hobbies.Children}
- Hobby component - error={this.props.errors[index]}
- TextBoxInput - error={this.props.error.HobbyName.ErrorMessage}
- Hobby component - error={this.props.errors[index]}
3.1. minimal - without any CSS framework
3.2. react-bootstrap
remarks
- simple html controls replaced with ReactBootstrap controls - (button -> ReactBootstrap.Button,input -> ReactBoostrap.Input)
- showing error messages
- using help property for error messages
- using bsStyle property to style input (bsStyle={this.validationState(this.props.error.HobbyName)})
3.3. react material UI
- You can see the end result at the top of this post.
- You can play with the demo.
- You can browse the sources.
remarks
- simple html controls replaced with material ui controls - (button -> RaisedButton,input -> TextField)
- showing error messages using errorText property for error messages