From Documentation

(Difference between revisions)
Jump to: navigation, search
m (Use Client Binding API in React)
m (Filtering on the server side)
Line 156: Line 156:
export default {
export default {
   // omitted
   // omitted
-
   init(name, command, event) {
+
   init(name, command, commandArguments, event) {
     this.init.emitters = this.init.emitters || {};
     this.init.emitters = this.init.emitters || {};
     let binder = getBinder(name);
     let binder = getBinder(name);
Line 167: Line 167:
       return new Promise(resolve => {
       return new Promise(resolve => {
         emitter.once('data', resolve);
         emitter.once('data', resolve);
-
         binder.command(command);
+
         binder.command(command, commandArguments);
       });
       });
     }
     }
Line 182: Line 182:
// omitted
// omitted
export const fetchProducts = (filters, sortBy, callback) => dispatch => {
export const fetchProducts = (filters, sortBy, callback) => dispatch => {
-
   return zkBinder.init('$main', 'loadProducts', 'loadProductsDone')
+
   return zkBinder.init('$main', 'loadProducts', {filterSizes: filters}, 'loadProductsDone')
     .then(products => {
     .then(products => {
// omitted
// omitted

Revision as of 10:50, 10 February 2020





  • Author
    Rudy Huang, Engineer, Potix Corporation
  • Date
    January, 2020
  • Version
    ZK 9.0.0

Contents

Overview

React is a JavaScript library for building user interfaces. We can use React as a front-end UI and use ZK as a pure backend. In this small talk, we will show you how to integrate React with ZK using the client binding API step by step.

By using this approach, the front-end can use anything written in JavaScript. That makes a React project integrate with an existing ZK project easily.

Here are some reasons why you would like to use this approach:

  • Use many React components (for example, Material-UI) on your ZK applications.
  • Use React while you can still use existing View Models.
  • Lower memory consumption since ZK doesn't need to maintain the state of components in the server.

For convenience, we use an MIT licensed React demo project react-shopping-cart to demonstrate.

React-Shopping-mall.png

We created an example project containing both client-side and server-side resources. For client-side resources, we put them into the frontend folder.

Client-binding-demo-react-project-structore.png

Render React in a Zul File

We have created an index.zul, an entry page for the whole application.

index.zul

<?script src="/bundle.js"?>
<zk xmlns:n="native">
    <div id="main" viewModel="@id('vm') @init('react.vm.IndexVM')">
        <n:div id="root"/>
    </div>
</zk>
  • Line 1: Load bundle.js packed by Webpack. You must configure Webpack to build a single bundle.js. See the demo source for details.
  • Line 3: The id property is needed for client binding to get the correct VM.
  • Line 4: We have created a native DIV element for React to render.

Wait for ZK to Be Mounted

Since we want to use ZK client binding API, it's safe to wait for ZK to finish mounting in React. We need to modify index.js a bit.

frontend/src/index.js

import 'babel-polyfill'
import React from 'react';
import ReactDOM from 'react-dom';

import App from './components/App';
import Root from './Root';

import './index.scss';

let zk = window.zk;
if (zk) {
  zk.afterMount(function () {
    ReactDOM.render(
      <Root>
        <App />
      </Root>,
      document.getElementById('root')
    );
  });
}
  • Line 12: We use the zk.afterMount API to execute ReactDOM.render, ensuring ZK is ready.

Load Data from ZK

We need to create a View Model to use the client binding feature in React.

Create a ViewModel (VM) in ZK

We created an IndexVM.java and defined a property "products". The products property can be loaded from a database.

IndexVM.java

// omitted
public class IndexVM {
	// omitted
	private List<ProductDto> products;
	// omitted
	public List<ProductDto> getProducts() {
		return products;
	}
	// omitted
  • Line 4: Declare a property
  • Line 6: Declare a getter only here so ZK knows products is a readonly property of a VM.

Add Client Binding Annotations

To get the vm.products from the client, we need to add some annotation to the IndexVM.

IndexVM.java

@NotifyCommands({
	@NotifyCommand(value = "loadProductsDone", onChange = "_vm_.products")
})
@ToServerCommand({"loadProducts", "placeOrder"})
@ToClientCommand({"loadProductsDone"})
@VariableResolver(DelegatingVariableResolver.class)
public class IndexVM {
	// omitted
	@Command
	@NotifyChange("products")
	public void loadProducts() {
		products = productService.getProducts();
	}
	// omitted
  • LIne 2: Add a @NotifyCommand and listen to _vm_.products changes (_vm_ means the VM object)
  • Line 4 and 11: We added a simple command to trigger products changes for later use. Since it's called from the client, we need to register it by @ToServerCommand.
  • Line 5: Expose the 'loadProductDone'-command to the client side. ZK's server side binding uses a white list so you must declare it explicitly (or expose all commands to the client side using the wildcard "*").

Once we set, we can get the property from the client. Let's see how we get the products from ZK.

Use Client Binding API in React

We can use binder.after to get properties of ViewModel from ZK.

zkbind.$('$main').after('loadProductsDone', function (data) {
    // the data is vm.products
    // and this function will be called each time vm.products is changed
    // one-way binding: vm.products -> this function
});

The react-shopping-cart loads JSON data from a server. A method fetchProducts initiates axios.get to make an Ajax get request.

frontend/src/services/shelf/actions.js

export const fetchProducts = (filters, sortBy, callback) => dispatch => {
  return axios
    .get(productsAPI)
    .then(res => {
      let { products } = res.data;
// omitted

We can use client binding API to request with ZK instead. We wrote a utility class to handle it for convenience (the full source is available in the demo project)

frontend/src/util/zkBinder.js

import { EventEmitter } from 'events';

function getBinder(name) {
  return window.zkbind && window.zkbind.$(name);
}

export default {
  // omitted
  init(name, command, commandArguments, event) {
    this.init.emitters = this.init.emitters || {};
    let binder = getBinder(name);
    if (binder) {
      let emitter = this.init.emitters[event];
      if (!emitter) {
        this.init.emitters[event] = emitter = new EventEmitter();
        binder.after(event, data => { if (data) emitter.emit('data', data) });
      }
      return new Promise(resolve => {
        emitter.once('data', resolve);
        binder.command(command, commandArguments);
      });
    }
    return Promise.reject(new Error('Binder not found'));
  }
};
  • Line 16: Listen to loadProductsDone, and only process the data if it is not null or undefined.
  • Line 20: To simulate a request, we need to trigger command from the client first. We can call loadProducts and wait for loadProductsDone to be triggered.

frontend/src/services/shelf/actions.js

import zkBinder from "../../util/zkBinder";
// omitted
export const fetchProducts = (filters, sortBy, callback) => dispatch => {
  return zkBinder.init('$main', 'loadProducts', {filterSizes: filters}, 'loadProductsDone')
    .then(products => {
// omitted
  • Line 4: Replace the axios call with ZK.

Send Data Back to ZK

The checkout feature of react-shopping-cart isn't complete. It just shows an alert dialog to display the subtotal. We can complete it by using client binding API to send the cart data back to ZK.

We can use binder.command to send data to the ViewModel.

zkbind.$('$main').command('toServerCommand', {key1: value1, key2: value2});

frontend/src/components/FloatCart/index.js

// omitted
class FloatCart extends Component {
// omitted
  proceedToCheckout = () => {
    const {
      totalPrice,
      productQuantity,
      currencyFormat,
      currencyId
    } = this.props.cartTotal;

    if (!productQuantity) {
      alert('Add some product in the cart!');
    } else {
      let products = this.props.cartProducts
        .map(p => ({[p.id]: p.quantity}))
        .reduce((acc, curr) => Object.assign(acc, curr), {});
      zkBinder.command('$main', 'placeOrder',
        {format: currencyFormat, price: totalPrice, id: currencyId, products: products});
    }
  };
  // omitted
  • Line 18: Calling zkbind.command with arguments.

Then we need to add placeOrder command in the ViewModel. Don't forget to add it to @ToServerCommand whitelist.

IndexVM.java

// omitted
@Command
public void placeOrder(@BindingParam("format") String format,
                       @BindingParam("price") double price,
                       @BindingParam("id") String id,
                       @BindingParam("products") Map<String, Integer> cartProducts) {
	Clients.showNotification(String.format("Checkout - Subtotal: %s %.2f", format, price));
	this.cartProducts = cartProducts;
	this.cartProducts.forEach((p, q) -> LOG.info("Order Product#{}, Quantity: {}", p, q));
	// Save to DB...
}
  • Line 3: Use @BindingParam to map each argument.
  • Line 7: We use ZK notification to show the result.

All done. Once the user clicks the checkout button, ZK will receive the cart result.

Conclusion

This article shows how to use ZK as a pure backend with React as frontend. With the help of MVVM Client-Binding API, ZK can integrate with React, which can get data from ZK and pass data to ZK, and run smoothly.

Download the Source

You can access the complete source at GitHub. Follow the instructions to start a server and open it in a browser.

Other Front-End Frameworks Integration

  • Angular
  • Vue.js


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.