23 February 2015

Introduction

logo

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)
  • 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”)}

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}

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


blog comments powered by Disqus