Difference between revisions of "Zscript, java, EL"

From Documentation
(Created page with ' {{ZKDevelopersGuidePageHeader}} ==Overview== By default, code in <tt>zscript</tt> is Java language. Therefore, any code written in <tt>zscript</tt> can move to java file by slig…')
 
m (correct highlight (via JWB))
 
(2 intermediate revisions by 2 users not shown)
Line 2: Line 2:
 
{{ZKDevelopersGuidePageHeader}}
 
{{ZKDevelopersGuidePageHeader}}
 
==Overview==
 
==Overview==
By default, code in <tt>zscript</tt> is Java language. Therefore, any code written in <tt>zscript</tt> can move to java file by slightly modification. In retrospect, any code written in java file can move to <tt>zscript</tt> by slightly modification. After all, they are all java.
+
By default, code in <code>zscript</code> is Java language. Therefore, any code written in <code>zscript</code> can move to java file by slightly modification. In retrospect, any code written in java file can move to <code>zscript</code> by slightly modification. After all, they are all java.
  
It is convenient to use <tt>zscript</tt> in ZUML. The advantage is that no compilation is required and you can modify its content dynamically (without re-deploying the Web application). And the syntax is more intuitive, EL can access variables in <tt>zscript</tt> easily.
+
It is convenient to use <code>zscript</code> in ZUML. The advantage is that no compilation is required and you can modify its content dynamically (without re-deploying the Web application). And the syntax is more intuitive, EL can access variables in <code>zscript</code> easily.
  
But it comes with a price: slower performance. The degradation varies from one application from another. For large website, it is suggested not to use <tt>zscript</tt> if possible. Also, you '''CAN NOT''' use Java debug tool to debug <tt>zscript</tt>, you can't set breakpoints in zscript. Not like Java, sometimes obvious bug like typo can't be easily found in <tt>zscript</tt>. ZK has implemented <tt>GenericAutowireComposer</tt>, it eases the burden of moving code from <tt>zscript</tt> to java a lot. Please refer to the smalltalk [http://www.zkoss.org/smalltalks/mvc3 ZK MVC Made Easy] for detail.
+
But it comes with a price: slower performance. The degradation varies from one application from another. For large website, it is suggested not to use <code>zscript</code> if possible. Also, you '''CAN NOT''' use Java debug tool to debug <code>zscript</code>, you can't set breakpoints in zscript. Not like Java, sometimes obvious bug like typo can't be easily found in <code>zscript</code>. ZK has implemented <code>GenericAutowireComposer</code>, it eases the burden of moving code from <code>zscript</code> to java a lot. Please refer to the smalltalk [http://books.zkoss.org/wiki/Small%20Talks/2008/August/ZK%20MVC%20Made%20Easy] for detail.
  
<tt>zscript</tt> is useful for prototyping, but it's more hard to debug and maintain. In the following paragraph, we'll show examples of equivalent <tt>zscript</tt> and java code.
+
<code>zscript</code> is useful for prototyping, but it's more hard to debug and maintain. In the following paragraph, we'll show examples of equivalent <code>zscript</code> and java code.
  
 +
==Use <code>zscript</code> to initialize==
 +
In Page Initial Phase, ZK processes the processing instructions, called <code>init</code>.
  
 
+
Use <code>zscript</code> to init the page,simply specify a file containing the scripting codes with the zscript attribute, as follows. Then, the file will be interpreted at the Page Initial phase.
 
 
 
 
==Use <tt>zscript</tt> to initialize==
 
In Page Initial Phase, ZK processes the processing instructions, called <tt>init</tt>.
 
 
 
Use <tt>zscript</tt> to init the page,simply specify a file containing the scripting codes with the zscript attribute, as follows. Then, the file will be interpreted at the Page Initial phase.
 
  
 
For example:
 
For example:
Line 26: Line 22:
 
Notice that the page is not yet attached to the desktop when the Page Initial phase executes.
 
Notice that the page is not yet attached to the desktop when the Page Initial phase executes.
  
Use java to init the page, simply specify a class with class attribute, an instance of the specified class is constructed, and then its <tt>doInit</tt> method is called.  
+
Use java to init the page, simply specify a class with class attribute, an instance of the specified class is constructed, and then its <code>doInit</code> method is called.  
  
 
For example:
 
For example:
Line 34: Line 30:
  
 
==access UI component by ID==
 
==access UI component by ID==
<tt>zscript</tt> can access UI component by ID, in the following example,
+
<code>zscript</code> can access UI component by ID, in the following example,
 
<source lang="xml" >
 
<source lang="xml" >
 
<window id="win_1">
 
<window id="win_1">
Line 49: Line 45:
 
</source>
 
</source>
  
To access ui component in java code, you can use <tt>Path.getComponent()</tt> to obtain the mapping java object.
+
To access ui component in java code, you can use <code>Path.getComponent()</code> to obtain the mapping java object.
  
 
<source lang="java" >
 
<source lang="java" >
Line 56: Line 52:
 
</source>
 
</source>
  
==define Variable in <tt>zscript</tt> and access by EL==
+
==define Variable in <code>zscript</code> and access by EL==
Variable defined in <tt>zscript</tt> can be easily accessed by EL.
+
Variable defined in <code>zscript</code> can be easily accessed by EL.
 
<source lang="xml" >
 
<source lang="xml" >
 
<window>
 
<window>
Line 68: Line 64:
 
</source>
 
</source>
  
The result shows <tt>1:abc</tt>
+
The result shows <code>1:abc</code>
  
 
To achieve the same result in java, you can rewrite it to
 
To achieve the same result in java, you can rewrite it to
Line 90: Line 86:
 
</source>
 
</source>
  
Note that <tt>${win_1.var}</tt> will be mapped to MyWindow.getVar(). It's case sensitive. The mapping getter function will map first character of variable to upper case and append <tt>get</tt> in front of it.
+
Note that <code>${win_1.var}</code> will be mapped to MyWindow.getVar(). It's case sensitive. The mapping getter function will map first character of variable to upper case and append <code>get</code> in front of it.
  
 
==Define class and methods in zscript==
 
==Define class and methods in zscript==
Line 103: Line 99:
 
</source>
 
</source>
  
And you can define methods in <tt>zscript</tt>
+
And you can define methods in <code>zscript</code>
 
<source lang="xml" >
 
<source lang="xml" >
 
<window>
 
<window>
Line 117: Line 113:
  
 
==access implicit object==
 
==access implicit object==
In <tt>zscript</tt>, you can access implicit object like they are declared global variable.
+
In <code>zscript</code>, you can access implicit object like they are declared global variable.
 
   
 
   
 
<source lang="xml" >
 
<source lang="xml" >
Line 127: Line 123:
 
</source>
 
</source>
  
the result shows <tt>window</tt>'s title becomes <tt>given by zscript</tt>
+
the result shows <code>window</code>'s title becomes <code>given by zscript</code>
  
 
Not every implicit object has its own mapping java object. But there is always a workaround. If you want to access attribute in desktopScope, the following two statements are equivalent, assuming comp is a component.
 
Not every implicit object has its own mapping java object. But there is always a workaround. If you want to access attribute in desktopScope, the following two statements are equivalent, assuming comp is a component.
Line 136: Line 132:
 
</source>
 
</source>
  
<tt>Desktop.java</tt> has api such as <tt>getExecution</tt>, <tt>getPage</tt>, <tt>getSession</tt>, <tt>getWebApp</tt>. And <tt>AbstractComponent.java</tt> has <tt>getSpaceOwner</tt>, <tt>getDesktop</tt>. And <tt>Execution</tt> has <tt>getArg</tt>. And <tt>ForEachImpl.java</tt> has <tt>getEach</tt>
+
<code>Desktop.java</code> has api such as <code>getExecution</code>, <code>getPage</code>, <code>getSession</code>, <code>getWebApp</code>. And <code>AbstractComponent.java</code> has <code>getSpaceOwner</code>, <code>getDesktop</code>. And <code>Execution</code> has <code>getArg</code>. And <code>ForEachImpl.java</code> has <code>getEach</code>
  
For example, you can get the implicit object <tt>execution</tt> from any component
+
For example, you can get the implicit object <code>execution</code> from any component
 
<source lang="java" >
 
<source lang="java" >
 
comp.getDesktop().getExecution()
 
comp.getDesktop().getExecution()
Line 144: Line 140:
  
 
== getVariable VS. getZScriptVariable ==
 
== getVariable VS. getZScriptVariable ==
Variables defined in the namespace can be retrieved by use of the <tt>getVariable</tt> method.
+
Variables defined in the namespace can be retrieved by use of the <code>getVariable</code> method.
  
On the other hand, variables defined in <tt>zscript</tt> is part of the interpret that interprets it. They are ''not'' a part of any namespace. In other words, you can ''not'' retrieve them by use of the <tt>getVariable</tt> method.
+
On the other hand, variables defined in <code>zscript</code> is part of the interpret that interprets it. They are ''not'' a part of any namespace. In other words, you can ''not'' retrieve them by use of the <code>getVariable</code> method.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 155: Line 151:
 
</source>
 
</source>
  
Instead, you have to use <tt>getZScriptVariable</tt> to retrieve variables defined in <tt>zscript</tt>. Similarly, you can use <tt>getZScriptClass</tt> to retrieve classes and <tt>getZScriptFunction</tt> to retrieve methods defined in <tt>zscript</tt>. These methods will iterate through all loaded interpreters until the specified one is found.
+
Instead, you have to use <code>getZScriptVariable</code> to retrieve variables defined in <code>zscript</code>. Similarly, you can use <code>getZScriptClass</code> to retrieve classes and <code>getZScriptFunction</code> to retrieve methods defined in <code>zscript</code>. These methods will iterate through all loaded interpreters until the specified one is found.
  
If you want to search a particular interpreter, you can use the <tt>getInterpreter</tt> method to retrieve the interpreter first, as follows.
+
If you want to search a particular interpreter, you can use the <code>getInterpreter</code> method to retrieve the interpreter first, as follows.
  
 
<source lang="java" >
 
<source lang="java" >
Line 184: Line 180:
  
 
==foreach==
 
==foreach==
It's a common usage of <tt>zscript</tt> to define array of object for <tt>forEach</tt>
+
It's a common usage of <code>zscript</code> to define array of object for <code>forEach</code>
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 198: Line 194:
 
</source>
 
</source>
  
You can move the definition of array of object to java file as the following example. But remember to add <tt>id</tt> inside <tt>${}</tt>, then EL knows which component has the variable.
+
You can move the definition of array of object to java file as the following example. But remember to add <code>id</code> inside <code>${}</code>, then EL knows which component has the variable.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 219: Line 215:
 
</source>
 
</source>
  
Or you can use <tt>appendChild</tt>. Manually generate all children in java, then you don't have to mix with <tt>forEach</tt> in ZUML. In the following example, the <tt>listitem</tt>s will be appended to <tt>listbox</tt> after the <tt>button</tt> is clicked.
+
Or you can use <code>appendChild</code>. Manually generate all children in java, then you don't have to mix with <code>forEach</code> in ZUML. In the following example, the <code>listitem</code>s will be appended to <code>listbox</code> after the <code>button</code> is clicked.
 
<source lang="xml" >
 
<source lang="xml" >
 
<window id="win_1" use="MyWindow" title="list">
 
<window id="win_1" use="MyWindow" title="list">
Line 248: Line 244:
  
 
==Macro Component==
 
==Macro Component==
ZK provides two way to provide additional methods to macro component. One is through java, and the other is through <tt>zscript</tt>. Please refer to section [[Provide Additional Methods]] in chapter [[Macro]] for more information.
+
ZK provides two way to provide additional methods to macro component. One is through java, and the other is through <code>zscript</code>. Please refer to section '''Provide Additional Methods''' in chapter [[Macro_Components|Macro]] for more information.
  
 
==event handler==
 
==event handler==
You can write event handler code in <tt>zscript</tt> or <tt>forward</tt> to event handler in java.
+
You can write event handler code in <code>zscript</code> or <code>forward</code> to event handler in java.
  
If the code is really simple, you can write it in <tt>zscript</tt> following event's name. Like following example:
+
If the code is really simple, you can write it in <code>zscript</code> following event's name. Like following example:
 
<source lang="xml" >
 
<source lang="xml" >
 
<window>
 
<window>
Line 260: Line 256:
 
</source>
 
</source>
  
If the code is more than one line, for readability, you can use <tt>attribute</tt>.
+
If the code is more than one line, for readability, you can use <code>attribute</code>.
 
<source lang="xml" >
 
<source lang="xml" >
 
<window>
 
<window>
Line 271: Line 267:
 
</source>
 
</source>
  
Or you can deliberately define a method in <tt>zscript</tt>, and call it from event's following <tt>zscript</tt>.
+
Or you can deliberately define a method in <code>zscript</code>, and call it from event's following <code>zscript</code>.
 
<source lang="xml" >
 
<source lang="xml" >
 
<window>
 
<window>
Line 284: Line 280:
 
</source>
 
</source>
  
To handle events in java, you define static java method, or use zk attributes: <tt>use</tt> or <tt>apply</tt>.
+
To handle events in java, you define static java method, or use zk attributes: <code>use</code> or <code>apply</code>.
  
 
The following example shows how to call static method in java,  
 
The following example shows how to call static method in java,  
Line 309: Line 305:
 
</source>
 
</source>
  
The following example uses zk attribute: <tt>use</tt> ,
+
The following example uses zk attribute: <code>use</code> ,
 
<source lang="xml" >
 
<source lang="xml" >
 
<window id="win_1" use="MyWindow">
 
<window id="win_1" use="MyWindow">
Line 331: Line 327:
 
</source>
 
</source>
  
The following example uses zk attribute: <tt>apply</tt>. Note that name of event handling method must be <tt>onXXX</tt>.
+
The following example uses zk attribute: <code>apply</code>. Note that name of event handling method must be <code>onXXX</code>.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 356: Line 352:
  
 
==GenericAutowireComposer==
 
==GenericAutowireComposer==
If you want to access UI object, you have to call <tt>getFellow</tt> to get its mapping java object. But why not just bind these components and data beans automatically? In the following example, you can omit the tedious <tt>getFellow</tt> calls, if your applied class extends <tt>GenericAutowireComposer</tt>.
+
If you want to access UI object, you have to call <code>getFellow</code> to get its mapping java object. But why not just bind these components and data beans automatically? In the following example, you can omit the tedious <code>getFellow</code> calls, if your applied class extends <code>GenericAutowireComposer</code>.
  
 
In the following example, Full Name is updated automatically while you change First Name or Last Name.
 
In the following example, Full Name is updated automatically while you change First Name or Last Name.

Latest revision as of 13:25, 19 January 2022


Documentationscript, java, EL
script, java, EL


Stop.png This documentation is for an older version of ZK. For the latest one, please click here.


Overview

By default, code in zscript is Java language. Therefore, any code written in zscript can move to java file by slightly modification. In retrospect, any code written in java file can move to zscript by slightly modification. After all, they are all java.

It is convenient to use zscript in ZUML. The advantage is that no compilation is required and you can modify its content dynamically (without re-deploying the Web application). And the syntax is more intuitive, EL can access variables in zscript easily.

But it comes with a price: slower performance. The degradation varies from one application from another. For large website, it is suggested not to use zscript if possible. Also, you CAN NOT use Java debug tool to debug zscript, you can't set breakpoints in zscript. Not like Java, sometimes obvious bug like typo can't be easily found in zscript. ZK has implemented GenericAutowireComposer, it eases the burden of moving code from zscript to java a lot. Please refer to the smalltalk [1] for detail.

zscript is useful for prototyping, but it's more hard to debug and maintain. In the following paragraph, we'll show examples of equivalent zscript and java code.

Use zscript to initialize

In Page Initial Phase, ZK processes the processing instructions, called init.

Use zscript to init the page,simply specify a file containing the scripting codes with the zscript attribute, as follows. Then, the file will be interpreted at the Page Initial phase.

For example:

<?init zscript="/my/init.zs"?>

Notice that the page is not yet attached to the desktop when the Page Initial phase executes.

Use java to init the page, simply specify a class with class attribute, an instance of the specified class is constructed, and then its doInit method is called.

For example:

<?init class="MyInit"?>

access UI component by ID

zscript can access UI component by ID, in the following example,

<window id="win_1">	
	<zscript><![CDATA[  
		win_1.title="given by zscript";				
	]]>
	</zscript>	
</window>

the result will be:

given by zscript

To access ui component in java code, you can use Path.getComponent() to obtain the mapping java object.

Window win = (Window)Path.getComponent("/win_1");
win.setTitle("given by java");

define Variable in zscript and access by EL

Variable defined in zscript can be easily accessed by EL.

<window>
	<zscript><![CDATA[
		var="abc";	
	]]>
	</zscript>
	1:${var}
</window>

The result shows 1:abc

To achieve the same result in java, you can rewrite it to

<window id="win_1" use="MyWindow">
	1:${win_1.var}
</window>

And the java file:

import org.zkoss.zul.Window;

public class MyWindow extends Window {
	String var = "abc";
	public String getVar(){
		return var;
	}
}

Note that ${win_1.var} will be mapped to MyWindow.getVar(). It's case sensitive. The mapping getter function will map first character of variable to upper case and append get in front of it.

Define class and methods in zscript

Thanks to the power of BeanShell, the implementation of Java classes can be done in zscript as follows.

<zscript>
    public class MyWindow extends Window {    
    }    
</zscript>
<window use="MyWindow"/>

And you can define methods in zscript

<window>
	<zscript><![CDATA[  
		public void sayHello(){
			alert("hello");
		}
	]]>
	</zscript>
	<button label="Say Hello!" onClick="sayHello()" />
</window>

access implicit object

In zscript, you can access implicit object like they are declared global variable.

<window>
	<zscript>
		self.title="given by zscript";
	</zscript>	
</window>

the result shows window's title becomes given by zscript

Not every implicit object has its own mapping java object. But there is always a workaround. If you want to access attribute in desktopScope, the following two statements are equivalent, assuming comp is a component.

comp.getAttribute("some", comp.DESKTOP_SCOPE);
comp.getDesktop().getAttribute("some");

Desktop.java has api such as getExecution, getPage, getSession, getWebApp. And AbstractComponent.java has getSpaceOwner, getDesktop. And Execution has getArg. And ForEachImpl.java has getEach

For example, you can get the implicit object execution from any component

comp.getDesktop().getExecution()

getVariable VS. getZScriptVariable

Variables defined in the namespace can be retrieved by use of the getVariable method.

On the other hand, variables defined in zscript is part of the interpret that interprets it. They are not a part of any namespace. In other words, you can not retrieve them by use of the getVariable method.

<zscript>
     var1 = 123; //var1 belongs to the interpreter, not any namespace
     page.getVariable("var1"); //returns null
</zscript>

Instead, you have to use getZScriptVariable to retrieve variables defined in zscript. Similarly, you can use getZScriptClass to retrieve classes and getZScriptFunction to retrieve methods defined in zscript. These methods will iterate through all loaded interpreters until the specified one is found.

If you want to search a particular interpreter, you can use the getInterpreter method to retrieve the interpreter first, as follows.

page.getInterpreter("JavaScript").getVariable("some"); //interpreter for JavaScript
page.getInterpreter(null).getVariable("some"); //interpreter for default language

The Scripting Codes in a Separate File

To separate codes and views, developers could put the scripting codes in a separated file, say sayHello.zs, and then use the src attribute to reference it.

<window>
    <button label="Say Hello" onClick="sayHello()"/>    
    <zscript src="sayHello.zs"/>    
</window>

which assumes the content of sayHello.zs is as follows.

int count = 0;
void sayHello() { //declare a global function
    alert("Hello World! "+ ++count);    
}

foreach

It's a common usage of zscript to define array of object for forEach

<window>
	<zscript><![CDATA[
	contacts = new String[] {"Monday", "Tuesday", "Wednesday"};
	]]>
	</zscript>
	<listbox>
		<listitem label="${each}" forEach="${contacts}"/>
	</listbox>
</window>

You can move the definition of array of object to java file as the following example. But remember to add id inside ${}, then EL knows which component has the variable.

<window id="win_1" use="MyWindow">
	<listbox>
		<listitem label="${each}" forEach="${win_1.contacts}"/>
	</listbox>
</window>
import org.zkoss.zul.Window;
 
public class MyWindow extends Window {
	String[] contacts = new String[] {"Monday", "Tuesday", "Wednesday"};
	public String[] getContacts(){
		return contacts;
	}
}

Or you can use appendChild. Manually generate all children in java, then you don't have to mix with forEach in ZUML. In the following example, the listitems will be appended to listbox after the button is clicked.

<window id="win_1" use="MyWindow" title="list">
	<listbox id="lb_1">
	</listbox>
	<button label="Hello" onClick="win_1.onTest()"/>
</window>
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Window;

public class MyWindow extends Window {
	String[] contacts = new String[] { "Monday", "Tuesday", "Wednesday" };

	public void onTest() {
		Listbox lb = (Listbox) getFellow("lb_1");
		for (int i = 0; i < contacts.length; i++) {
			Listitem li = new Listitem();
			li.setLabel(contacts[i]);
			lb.appendChild(li);
		}
	}
}

Macro Component

ZK provides two way to provide additional methods to macro component. One is through java, and the other is through zscript. Please refer to section Provide Additional Methods in chapter Macro for more information.

event handler

You can write event handler code in zscript or forward to event handler in java.

If the code is really simple, you can write it in zscript following event's name. Like following example:

<window>
	<button onClick='alert("here is a zcript")'/>
</window>

If the code is more than one line, for readability, you can use attribute.

<window>
	<button>
		<attribute name="onClick">
			alert("here is a zscript");
		</attribute>
	</button>
</window>

Or you can deliberately define a method in zscript, and call it from event's following zscript.

<window>
	<zscript><![CDATA[
		public void showAlert(){
			alert("here is a zcript too");
		}
	]]>
	</zscript>	
	<button onClick="showAlert()"/>
</window>

To handle events in java, you define static java method, or use zk attributes: use or apply.

The following example shows how to call static method in java,

<window>
	<button onClick="MyManager.showAlert()"/>
</window>

And MyManager.java

import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

public class MyManager {
	public static void showAlert(){
		try {
			Messagebox.show("handle event in java");
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

The following example uses zk attribute: use ,

<window id="win_1" use="MyWindow">
	<button onClick="win_1.showAlert()"/>
</window>
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

public class MyWindow extends Window {
	public void showAlert(){
		try {
			Messagebox.show("handle event in java");
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

The following example uses zk attribute: apply. Note that name of event handling method must be onXXX.

<window apply="MyComposer">
	<button forward="onShowAlert()"/>
</window>
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.util.GenericComposer;
import org.zkoss.zul.Messagebox;

public class MyComposer extends GenericComposer {
	public void onShowAlert(Event evt) {
		try {
			Messagebox.show("handle event in java");
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

GenericAutowireComposer

If you want to access UI object, you have to call getFellow to get its mapping java object. But why not just bind these components and data beans automatically? In the following example, you can omit the tedious getFellow calls, if your applied class extends GenericAutowireComposer.

In the following example, Full Name is updated automatically while you change First Name or Last Name.

<window apply="Autowired">
    <grid>
        <rows>
            <row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
            <row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
            <row>Full Name: <label id="fullName"/></row>
        </rows>
    </grid>
</window>

And the source code of Autowired.java:

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.util.GenericAutowireComposer;
import org.zkoss.zul.Label;
import org.zkoss.zul.Textbox;


public class Autowired extends GenericAutowireComposer {
    private Textbox firstName; //auto-wired
    private Textbox lastName; //auto-wired
    private Label fullName; //auto-wired

    //all getFellow() codes are removed

    public void onFirstName(Event event) { 
        fullName.setValue(firstName.getValue()+" "+lastName.getValue());
    }
    
    public void onLastName(Event event) {
        fullName.setValue(firstName.getValue()+" "+lastName.getValue());
    }
}

For detail information, please refer to ZK MVC Made Easy.



Last Update : 2022/01/19

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