Use VueJS With ZK9 EmbeddedZK"

From Documentation
m
 
(16 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=James Chu, Potix Corporation
 
|author=James Chu, Potix Corporation
|date=January 7, 2020
+
|date=January 31, 2020
 
|version='''ZK''' 9.0.0
 
|version='''ZK''' 9.0.0
 
}}
 
}}
  
 
= Overview =
 
= Overview =
In the recently released ZK9, we provide a new way to integrate applications with ZK and front-end frameworks - [https://blog.zkoss.org/2019/12/17/zk-9-highlight-embedding-your-zk-app-into-a-3rd-party-app/ Embedded ZK]. For instance, [https://vuejs.org/ VueJS] is a progressive framework for building user interfaces. With the embedded ZK in ZK9, we could easily combine VueJS and ZK together. For more detail, we will demo how it works with modern front-end frameworks. We wil use ZK client side binding, embedded ZK, [https://getbootstrap.com/ Bootstrap] and [https://vuejs.org/ VueJS] in this demo project.
+
In the recently released ZK9, we provide a new way to integrate applications with ZK and front-end frameworks - Embedded ZK. For instance, we can integrate Vue. [https://vuejs.org/ VueJS] is a progressive framework for building user interfaces. With the Embedded ZK feature in ZK9, we could easily combine VueJS and ZK together. In the following demo project, we will use [http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/client_binding_api.html ZK client-side binding], [https://www.zkoss.org/wiki/ZK_Developer%27s_Reference/Integration/ZK_Embedded/Embedded_ZK_Application Embedded ZK], [https://getbootstrap.com/ Bootstrap] and [https://vuejs.org/ VueJS] to demonstrate how this can be done.
  
= Demo =
+
[[File:ZK9_Embedded_With_VueJS01.png | 800px | center]]
[[File:ZK9_Embedded_With_VueJS011.png | center]]
 
The new client side binding provides 4 methods - 2 at the client side, and 2 at the server side.
 
Their relationships can be illustrated by the following diagram:
 
  
== At client ==
+
= Prepare data and ZK settings (Java) =
We have to get the client binder first in order to use the client side methods. To get the binder, simply use
+
As we can see, this demo is a forum project. This demo contains several parts on the server-side:
<source lang="js">var binder = zkbind.$('$id');</source>
 
in the scripts. After we have our client binder, we could use the ''command'' and ''after'' method to interact with the view model back to our server.
 
  
=== binder.command(commandName, data) ===
+
1. Data object
This method is used to trigger a command we have at server by giving the command name as the first parameter. The second parameter is a JavaScript object, which is used to pass any information you want with the command.
 
  
'''Note:''' You could also pass ZK widgets in the data object and use ''@BindingParam'' to get the corresponding ZK component at the server.
+
2. View Models
  
=== binder.after(commandName, callback) ===
+
3. Define ZK Binder in ZUL files
This method is used to place a callback at the client after a command gets executed at the server.
 
  
== At server ==
+
4. Enable Embedded ZK
Here, we are going to introduce two new annotations at the server side for the new client side binding. They should be placed at the beginning of the class declaration of our View Model.
 
  
=== @NotifyCommand(value="commandName", onChange="_vm_.expression") ===
+
== Data Object ==
The notify command annotation allows us to trigger a command whenever the given expression changes at the server. Notice the command which gets triggered is a command in our view model. The ''_vm_'' here means the current view model.
+
There are two main entity classes - User and Article.
  
=== @ToClientCommand(commandNames) ===
+
''org.zkoss.demo.forum.entity.User''
The client command annotation allows us to put which commands we want to notify the client after execution has been done. Notice only the commands we put inside this annotation will trigger the callback we put in ''binder.after'' at client.
+
<source lang="java">
 +
public class User {
 +
  private int uid;
 +
  private String account;
 +
  private String password;
 +
  private String name;
 +
  //... getters/setters are omitted
 +
}
 +
</source>
 +
 
 +
''org.zkoss.demo.forum.entity.Article''
 +
<source lang="java">
 +
public class Article {
 +
  private int uid;
 +
  private User user;
 +
  private String subject;
 +
  private String thumbnail;
 +
  private String content;
 +
  private Date lastEditedTime;
 +
  //... getters/setters are omitted
 +
}
 +
</source>
 +
 
 +
When using ZK client-binding, the two data objects would be converted into '''JSON object'''. Therefore we could inject the data into Vue components later.
 +
 
 +
== ZK View Models and ZUL Files==
 +
In the view model, we need to prepare the binder and define the commands that would be called on the client-side.
  
= Demo =
+
=== Client-side Binding in View Model (Server to Client) ===
In this demo, we will use full calendar, which is a JavaScript calendar library under MIT license. We are going to wire up the full calendar with some ZK components back to the server with a View Model and a Data Model. The goal here is to bind the events coming out from full calendar to the commands we have in our View Model so that we can sync whatever changes in the calendar directly to our data model.
+
''org.zkoss.demo.forum.viewmodel.ArticleListVM''
 +
<source lang="java" highlight="1,2,3">
 +
@NotifyCommand(value = "toC_Articles", onChange = "_vm_.articles")
 +
@ToClientCommand({ "toC_Articles"})
 +
@ToServerCommand({ "loadArticles", "loadArticleById"})
 +
public class ArticleListVM {
 +
  @Command
 +
  @NotifyChange("articles")
 +
  public void loadArticles() {
 +
    articles = articleService.getRecentArticles();
 +
  }
 +
  //omitted
 +
}
 +
</source>
 +
Line 1 & 2: When the property "articles" is changed, ZK would fire a client command "toC_Articles" to the client-side.
  
<gflash width="900" height="730">Fullcalendar_demo.swf</gflash>
+
Line 3: Allow client-side binders to call specific commands.
  
== Implementation ==
+
=== Define ZK Binder in ZUL files ===
We will have four main parts in this demo.
 
* zul page, which is the view of our demo
 
* js file, which will contain our event handlers for full calendar and our client side bindings.
 
* view model, where we handle all the commands that come from the client.
 
* data model, which act as a data accessing object to our data source.
 
  
=== zul page ===
+
''src/main/resources/web/zul/articles.zul''
Our zul page is relatively simple. It has three parts, a container for full calendar and two popups.
+
<source lang="xml">
The container will be just a native div element with an ID:
+
<div id="articles-binder" viewModel="@id('vm') @init('org.zkoss.demo.forum.viewmodel.ArticleListVM')" />
<source lang="xml"><n:div id="cal" /></source>
+
</source>
  
The popups will be ZK popups and they will be used to create and modify events. The modify event popup looks like:
+
The id "articles-binder" is defined for the ZK Client-binding Javascript API.
  
 +
== Enable Embedded ZK ==
 +
We use the [https://www.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_Library_Properties/org.zkoss.web.servlet.http.embedded.enabled library property] to enable Embedded ZK.
 +
 +
''zk.xml''
 
<source lang="xml">
 
<source lang="xml">
<popup id="modifyEventPop">
+
<library-property>
<window title="Modify Event: " width="500px">
+
<name>org.zkoss.web.servlet.http.embedded.enabled</name>
<grid form="@id('fx') @load(vm.tempEvent) @save(vm.tempEvent, before='modEvent')">
+
<value>true</value>
<rows>
+
</library-property>
<row>
 
<label value="Event ID: " />
 
<label id="modId" value="@load(fx.id)" />
 
</row>
 
<row>
 
<label value="Title:" />
 
<textbox id="modTitle" value="@bind(fx.title)" />
 
</row>
 
<row>
 
<label value="Start Date:" />
 
<datebox id="modStart" value="@bind(fx.start)" format="long+medium" />
 
</row>
 
<row>
 
<label value="End Date:" />
 
<datebox id="modEnd" value="@bind(fx.end)" format="long+medium" />
 
</row>
 
</rows>
 
</grid>
 
<button id="modify" label="Modify"
 
onClick="@command('modEvent', pop=modifyEventPop)" />
 
<button label="Cancel" onClick="modifyEventPop.close()" />
 
</window>
 
</popup>
 
 
</source>
 
</source>
  
The popup shows when an ''eventClicked'' event is triggered. The data of the event will be pre-loaded before the popup shows. When the modify button gets clicked, a ''modEvent'' command will be fired back to our View Model, and we will update the modified event to our data model from there.
+
= Integrate with VueJS (Javascript) =
The popup for creating event is very similar with the one above, we will omit its implementation here just to keep things simple.
+
Before integrating with VueJS, we need to load embedded ZK in the HTML file.
  
=== js file ===
+
== Sets Embedded ZK ==
Our script is where the new client side binding begins to play a role at. The complete js file looks like:
 
<source lang="js" high="3,10,11,37,40">
 
zk.afterMount(function() {
 
 
var binder = zkbind.$('$cal'),
 
calConfig = {};
 
 
// day click handler
 
calConfig.dayClick = function(data, jsEvent, view) {
 
var popOffset = [jsEvent.clientX, jsEvent.clientY];
 
 
binder.command('doDayClicked', {dateClicked: data.toDate().getTime()})
 
.after(function() {
 
var newPop = zk.$('$newEventPop');
 
newPop.open(newPop, popOffset);
 
});
 
};
 
 
// event click handler
 
calConfig.eventClick = function(event, jsEvent, view) {
 
var popOffset = [jsEvent.clientX, jsEvent.clientY];
 
binder.command('doEventClicked', {evtId: event.id})
 
.after(function() {
 
var modPop = zk.$('$modifyEventPop');
 
modPop.open(modPop, popOffset);
 
});
 
}
 
 
// event drop handler and event resize handler
 
calConfig.eventResize = calConfig.eventDrop =
 
function(event, delta, revertFunc, jsEvent, ui, view) {
 
var startTime = event.start ?
 
event.start.toDate().getTime() : 0,
 
endTime = event.end ? event.end.toDate().getTime() : 0;
 
 
binder.command('doEventChanged', {evtId: event.id, startTime: startTime, endTime: endTime});
 
}
 
 
$('#cal').fullCalendar(calConfig);
 
 
// the event handler of after 'doCommandChange' from server
 
binder.after('doEventsChange', function(events) {
 
$('#cal').fullCalendar('removeEvents');
 
$('#cal').fullCalendar('addEventSource', events);
 
$('#cal').fullCalendar('rerenderEvents');
 
});
 
  
});
+
''src/main/resources/static/index.html''
 +
 
 +
<source lang="xml">
 +
<body>
 +
  <!-- omitted -->
 +
  <div id="embeddedZK"></div>
 +
  <!-- omitted -->
 +
</body>
 
</source>
 
</source>
The first thing we do here at line 3 is to get our client binder. Then in our day clicked event handler, we use ''binder.command'' to trigger the ''doDayClicked'' command and pass the clicked date back to our view model at line 10. At line 11, we use ''binder.after'' to open our popup. Notice that when cascading ''binder.command'' and ''binder.after'', the first argument in ''binder.after'' can be omitted. ''eventClick'', ''eventDrop'', and ''eventResize'' handlers follow the similar concept as well. Line 37 is where we initialize our full calendar and finally, begins at line 40 is where our ''doEventsChange'' callback. We use this callback to ensure that every time when events change at the View Model, they will be updated in our view.
 
  
=== View Model ===
+
Use a div to embed ZK binder DOM ($articles-binder).
Our view model is where we put all of our commands at. The structure of our view model looks like:
+
 
 +
''src/main/resources/static/js/app.js''
  
<source lang="java" high="1,2">
+
<source lang="javascript" highlight="1">
@NotifyCommand(value="doEventsChange", onChange="_vm_.events")
+
zEmbedded.load('embeddedZK', '/articles');
@ToClientCommand({"doEventClicked", "doDayClicked", "doEventsChange"})
+
window.addEventListener('load', function(){
public class DemoViewModel {
+
  zk.afterMount(function () {
+
    // Initialize Vue Components after ZK is mounted
private EventsDataModel dataModel;
+
  });
private Collection<EventObject> events;
 
private EventObject tempEvent;
 
 
 
@Init
 
public void init() throws GeneralSecurityException, IOException {
 
// init event data model
 
dataModel = new DemoDataModel();
 
events = dataModel.getEvents();
 
}
 
 
@Command
 
@NotifyChange("tempEvent")
 
public void doDayClicked(@BindingParam("dateClicked") long dateClicked);
 
 
@Command
 
@NotifyChange("tempEvent")
 
public void doEventClicked(@BindingParam("evtId") String evtId);
 
 
@Command
 
@NotifyChange("events")
 
public void doEventChanged(@BindingParam("evtId") String evtId,  
 
@BindingParam("startTime") long startTime, @BindingParam("endTime") long endTime);
 
 
@Command
 
@NotifyChange("events")
 
public void modEvent(@BindingParam("pop") Popup pop);
 
 
@Command
 
@NotifyChange("events")
 
public void createEvent(@BindingParam("pop") Popup pop);
 
 
}
 
}
 
</source>
 
</source>
Notice we omit command implementations here just to focus on the new client side binding. First we put our ''@NotifyCommand'' and ''@ToClientCommand'' on top of our class declaration.
 
  
At line 1,
+
Line 1: '/articles' would redirect to the specific URL path (articles.zul)
<source lang="java">
+
Notice that all the data transfer from the ZK server to the client should wait for ZK being mounted.
@NotifyCommand(value="doEventsChange", onChange="_vm_.events")
+
 
 +
== Use ZK Client-side binding in Vue (Client to Server) ==
 +
After loading the embedded ZK, we used client-side binding in Vue instances.
 +
 
 +
1. To trigger commands in ZK Server, we used '''Binder.command(command, args, ...)'''.
 +
 
 +
2. To get the result from the ZK server, we used '''Binder.after(command, callback)''' to register the callback function.
 +
 
 +
''src/main/resources/static/js/app.js''
 +
 
 +
<source lang="javascript" highlight="2,4,9,14,16,18,19">
 +
// inside zk.afterMount
 +
var articlesBinder = zkbind.$('$articles-binder');
 +
new Vue({
 +
  el: '#articles',
 +
  data: {
 +
    articles: []
 +
  },
 +
  beforeMount() {
 +
    this.loadArticles();
 +
  },
 +
  methods: {
 +
    loadArticles () {
 +
      var self = this,
 +
        changeArticles = function (data) {
 +
          self.articles = data;
 +
          articlesBinder.unAfter('toC_Articles', changeArticles);
 +
        };
 +
      articlesBinder.after('toC_Articles', changeArticles);
 +
      articlesBinder.command('loadArticles');
 +
    }
 +
    //omitted
 +
  }
 +
});
 
</source>
 
</source>
We specify that our view model will trigger the ''doEventsChange'' command whenever events are changed. The ''_vm_'' here stands for the current view model.
 
  
At line 2,
+
Line 2: Looking for the ZK binder.
<source lang="java">
+
 
@ToClientCommand({"doEventClicked", "doDayClicked", "doEventsChange"})
+
Line 4: Vue dom selector.
 +
 
 +
Line 9: Trigger the loading method before Vue is mounted.
 +
 
 +
Line 14: Prepare a callback function to update articles.
 +
 
 +
Line 16: Unregister the callback.
 +
 
 +
Line 18: Register a callback function, it would be triggered when receiving the client command - "toC_Articles".
 +
 
 +
Line 19: Use ZK Client-binding API to trigger a command on the server-side.
 +
 
 +
Since the data "articles" is a list, we use [https://vuejs.org/v2/guide/list.html List Rendering] in VueJs.
 +
 
 +
''src/main/resources/static/index.html''
 +
 
 +
<source lang="xml" highlight="3,5">
 +
<div id="articles" class="...">
 +
  <!-- omitted -->
 +
  <article-block v-for="article in articles"
 +
    v-bind:key="article.uid" v-bind:subject="article.subject" v-bind:thumbnail="article.thumbnail"
 +
    v-bind:username="article.user.name" v-bind:time="article.publishedTime"
 +
    v-on:view="view(article.uid)">
 +
  </article-block>
 +
  <!-- omitted -->
 +
</div>
 
</source>
 
</source>
We specify that every time these commands execute, ZK will notify our client, and if there is a ''binder.after'' callback at client, it will be invoked. Notice that we do not have a ''doEventsChange'' command in our view model, we put ''@NotifyCommand'' here just because we want to trigger the callback function at the client.
 
 
=== data model ===
 
Data model is used to assess our data source. Here's the class diagram of our data model:
 
  
 +
Line 3: "article-block" is a Vue component, and we use the List rendering
  
[[File: Fullcalendar_datamodel.png]]
+
Line 5: the property "article.user.name" is fully referred to the data from Java Bean
  
Since we can have different data sources, our data model will have different implementations depending on the data source. As shown in the demo above, we use a mock data source, which is just a map object in memory. If you check out the [https://github.com/jumperchen/zk8-fullcalendar-demo source code] of this demo, we also have a implementation with Google Calendar's API.
+
= Summary =
 +
The core value of ZK is to allow you to build a Web UI quickly with pure Java. However, in some cases, you may need to integrate a specific 3rd-party front-end framework with ZK. With embedded ZK, using such front-end frameworks with ZK becomes easier. I hope this article will give you some hints on how you can get this done.
 +
Please feel free to leave comments below or create a discussion on [http://forum.zkoss.org/questions/ ZK forum].
  
== Download ==
+
= Download =
You can download the war file and all of the source code for this demo in [https://github.com/jumperchen/zk8-fullcalendar-demo/releases/tag/v0.8.0 Github]
+
You can download all of the source code for this demo in [https://github.com/zkoss-demo/client-binding-demo-vuejs Github]
  
 
{{Template:CommentedSmalltalk_Footer_new|
 
{{Template:CommentedSmalltalk_Footer_new|

Latest revision as of 14:41, 20 June 2023

DocumentationSmall Talks2020JanuaryUse VueJS With ZK9 EmbeddedZK
Use VueJS With ZK9 EmbeddedZK

Author
James Chu, Potix Corporation
Date
January 31, 2020
Version
ZK 9.0.0

Overview

In the recently released ZK9, we provide a new way to integrate applications with ZK and front-end frameworks - Embedded ZK. For instance, we can integrate Vue. VueJS is a progressive framework for building user interfaces. With the Embedded ZK feature in ZK9, we could easily combine VueJS and ZK together. In the following demo project, we will use ZK client-side binding, Embedded ZK, Bootstrap and VueJS to demonstrate how this can be done.

ZK9 Embedded With VueJS01.png

Prepare data and ZK settings (Java)

As we can see, this demo is a forum project. This demo contains several parts on the server-side:

1. Data object

2. View Models

3. Define ZK Binder in ZUL files

4. Enable Embedded ZK

Data Object

There are two main entity classes - User and Article.

org.zkoss.demo.forum.entity.User

public class User {
  private int uid;
  private String account;
  private String password;
  private String name;
  //... getters/setters are omitted
}

org.zkoss.demo.forum.entity.Article

public class Article {
  private int uid;
  private User user;
  private String subject;
  private String thumbnail;
  private String content;
  private Date lastEditedTime;
  //... getters/setters are omitted
}

When using ZK client-binding, the two data objects would be converted into JSON object. Therefore we could inject the data into Vue components later.

ZK View Models and ZUL Files

In the view model, we need to prepare the binder and define the commands that would be called on the client-side.

Client-side Binding in View Model (Server to Client)

org.zkoss.demo.forum.viewmodel.ArticleListVM

@NotifyCommand(value = "toC_Articles", onChange = "_vm_.articles")
@ToClientCommand({ "toC_Articles"})
@ToServerCommand({ "loadArticles", "loadArticleById"})
public class ArticleListVM {
  @Command
  @NotifyChange("articles")
  public void loadArticles() {
    articles = articleService.getRecentArticles();
  }
  //omitted
}

Line 1 & 2: When the property "articles" is changed, ZK would fire a client command "toC_Articles" to the client-side.

Line 3: Allow client-side binders to call specific commands.

Define ZK Binder in ZUL files

src/main/resources/web/zul/articles.zul

<div id="articles-binder" viewModel="@id('vm') @init('org.zkoss.demo.forum.viewmodel.ArticleListVM')" />

The id "articles-binder" is defined for the ZK Client-binding Javascript API.

Enable Embedded ZK

We use the library property to enable Embedded ZK.

zk.xml

<library-property>
	<name>org.zkoss.web.servlet.http.embedded.enabled</name>
	<value>true</value>
</library-property>

Integrate with VueJS (Javascript)

Before integrating with VueJS, we need to load embedded ZK in the HTML file.

Sets Embedded ZK

src/main/resources/static/index.html

<body>
  <!-- omitted -->
  <div id="embeddedZK"></div>
  <!-- omitted -->
</body>

Use a div to embed ZK binder DOM ($articles-binder).

src/main/resources/static/js/app.js

zEmbedded.load('embeddedZK', '/articles');
window.addEventListener('load', function(){
  zk.afterMount(function () {
    // Initialize Vue Components after ZK is mounted
  });
}

Line 1: '/articles' would redirect to the specific URL path (articles.zul) Notice that all the data transfer from the ZK server to the client should wait for ZK being mounted.

Use ZK Client-side binding in Vue (Client to Server)

After loading the embedded ZK, we used client-side binding in Vue instances.

1. To trigger commands in ZK Server, we used Binder.command(command, args, ...).

2. To get the result from the ZK server, we used Binder.after(command, callback) to register the callback function.

src/main/resources/static/js/app.js

// inside zk.afterMount
var articlesBinder = zkbind.$('$articles-binder');
new Vue({
  el: '#articles',
  data: {
    articles: []
  },
  beforeMount() {
    this.loadArticles();
  },
  methods: {
    loadArticles () {
      var self = this,
        changeArticles = function (data) {
          self.articles = data;
          articlesBinder.unAfter('toC_Articles', changeArticles);
        };
      articlesBinder.after('toC_Articles', changeArticles);
      articlesBinder.command('loadArticles');
    }
    //omitted
  }
});

Line 2: Looking for the ZK binder.

Line 4: Vue dom selector.

Line 9: Trigger the loading method before Vue is mounted.

Line 14: Prepare a callback function to update articles.

Line 16: Unregister the callback.

Line 18: Register a callback function, it would be triggered when receiving the client command - "toC_Articles".

Line 19: Use ZK Client-binding API to trigger a command on the server-side.

Since the data "articles" is a list, we use List Rendering in VueJs.

src/main/resources/static/index.html

<div id="articles" class="...">
  <!-- omitted -->
  <article-block v-for="article in articles"
    v-bind:key="article.uid" v-bind:subject="article.subject" v-bind:thumbnail="article.thumbnail"
    v-bind:username="article.user.name"  v-bind:time="article.publishedTime"
    v-on:view="view(article.uid)">
  </article-block>
  <!-- omitted -->
</div>

Line 3: "article-block" is a Vue component, and we use the List rendering

Line 5: the property "article.user.name" is fully referred to the data from Java Bean

Summary

The core value of ZK is to allow you to build a Web UI quickly with pure Java. However, in some cases, you may need to integrate a specific 3rd-party front-end framework with ZK. With embedded ZK, using such front-end frameworks with ZK becomes easier. I hope this article will give you some hints on how you can get this done. Please feel free to leave comments below or create a discussion on ZK forum.

Download

You can download all of the source code for this demo in Github


Comments



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