Showing posts with label HTML5. Show all posts
Showing posts with label HTML5. Show all posts
Friday, December 4, 2015
Some answer for Front-end-developer-interview-questions
Start to reading Front-end-developer-interview-questions and make some answer, you can take it as reference.
Will add more over time.
Thursday, April 3, 2014
ZK, AngularJS and Pure HTML Template
Introduction
As we already know ZK used to generate all html content within 'redraw' function of a ZK Widget, this is a little bit (?) hard to maintain and customize dom structure and style of component.
This article describe how to create ZK Component with AngularJS Directive so you can use a pure HTML template to generate dom elements of ZK Component and has some advantages:
1. Easier to maintain.
2. Easier to customize.
And also some drawbacks (part of them has workaround):
1. Need to load html template via several ajax call.
Workaround: You can load all templates at once,
for more information please refer to
AngularJS: load all templates in one file or AngularJS Merge Template
2. Will blink while rerender, you can use a fake dom to avoid blinking but this may affect the client side performance.
3. Has issue of dynamically insert and compile complex dom elements so need to rerender if dynamically add children.
Note:
1. This is just a POC done in a short time, probably not well organized and has some unknown issues.
2. I am not familiar with AngularJS so this POC may contains bad practice.
Pre-request
AngularJS Create Components
ZK Component Development Tutorial: Getting Started
ZK CDT: Fire Event to Server to Create a SelectableTextNote
Result
Online demo
http://screencast.com/t/nlLsNdbV8A
Description:
The concept is simple.
In AngularJS, a directive which support pure html template is a function that returns an Object Literal, and registered with a specific name (see Creating Custom Directives).
Both of them -- the name and the function -- can be declared at any places.
e.g. inside the Javascript of ZK Widget.
So we can specify Angular Tag (ngTag) and Angular Template Url (ngTemplateUrl) to each ZK Component and do below:
In ZK Component:
* Server Side:
** Make sure that each templateUrl maps to different tag.
* Client Side:
** Output ngTag in redraw function.
** Store component to zk.ngcomponents in redraw function.
** Register Angular Directive with ngTag.
In Angular Directive
* Use ngTemplateUrl as templateUrl in directive.
* Store $scope, $compile and controller to $element in controller function of directive.
* Bind ZK Widget with Angular Objects above in link function of directive.
Then we can use AngularJS Directive with pure HTML Template then control ZK Widget from AngularJS Directive and vice versa.
See
NgComp.java#fixTemplateMapping
NgComp.js#redraw
tabs.js#registerTemplate
zacomponents.js#addComponent
zacomponents.js#storeNgObjects
zacomponents.js#directive
zacomponents.js#bindng_
for more information.
Program Introduction
Testing zul page
index.zul
Testing Composer
TestComposer.java
AngularJS Library
angular.min.js
Bootstrap3 CSS Library
bootstrap.min.css
Utils for AngularJS ZK Widget
zacomponents.js
Basic APIs of AngularJS ZK Widget
NgComp.js
The Folder Contains All Default Templates
templates
Basic Java Class for AngularJS ZK Component
NgComp.java
Others:
Please refer to the full project below
Download
Full project at github
Demo Flash (click View Raw to download)
Saturday, February 22, 2014
AngularJS Merge Template
Simple Note
Describe how to merge multiple template into one .html file
Note: You will need to put testing files under a web server to run.
Pre-request
AngularJS Create Components
Testing Files
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/Advanced/template/management/001__merge_template
JS/CSS Libs
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/lib
Reference
http://angularjs.org/
https://gist.github.com/vojtajina/3354046
Saturday, February 8, 2014
AngularJS Create Components
Simple Note
Modified official sample with personal comments.
Pre-request
AngularJS Controller
Testing Files
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/Basic/Basic_004__Create_Components
JS/CSS Libs
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/lib
Reference
http://angularjs.org/
Monday, February 3, 2014
AngularJS Wireup Backend
Simple Note
Official sample with personal comments.
To run this sample you will need need to setup a server since this sample rely on ajax that need to be run under http protocol.
Personally I do not like this sample, mixing too many things and not described well.
Pre-request
AngularJS Controller
Testing Files
Run test.html to test
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/Basic/Basic_003__Wireup_Backend
JS Libs
https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/Frameworks/Angularjs/lib
Reference
http://angularjs.org/
Sunday, October 6, 2013
WebSocketServerPush vs ZKComet Performance Testing
Simple Note
Testing Environment
Server:
Tomcat 7.0.42
ZK Version:
6.5.2
WebSocket ServerPush Implementation:
ZK ServerPush with WebSocket - Rework
http://ben-bai.blogspot.tw/2013/10/zk-serverpush-with-websocket-rework.html
Testing Steps
1. Start Tomcat
2. Open visualVM
3. Open 5 testing pages
There are 2 cases for WebSocket ServerPush, push to desktop separately (index.zul) and push to all desktop at once (index_per_desktop.zul), 1 case for ZK Comet (push to desktop separately - index.zul)
4. Wait 2 minutes
5. Take snapshot
Testing Result
(Click snapshot to view large image)
Executing Result:
ZK Comet - index.zul
WebSocket ServerPush - index.zul
WebSocket ServerPush - index_per_desktop.zul
JVisualVM
ZK Comet - index.zul
WebSocket ServerPush - index.zul
WebSocket ServerPush - index_per_desktop.zul
Observation
Execution Speed
WebSocket ServerPush is two times faster than ZK Comet
Memory Consumption
Highest: WebSocket ServerPush (215/187 MB for per-desktop/push-to-all) is two times higher than ZK Comet (100MB), resonable since WebSocket is faster, do more work and use more memory at the same time.
Lowest: Almost the same (20 ~ 23 MB), most of the memory can be GCed.
CPU Usage
WebSocket AU (2%~8%/1%~6% for per-desktop/push-to-all after page loaded) is better than ZK Comet (1%~24% after page loaded).
Conclution
WebSocket do double work with 1/4 ~ 1/3 CPU Usage and double Memory Consumption.
Download
Testing projects at github
https://github.com/benbai123/ZK_Practice/tree/master/General/Performance_Testing/WebSocketServerPush_vs_ZKComet
Saturday, October 5, 2013
ZK ServerPush with WebSocket - Rework
Introduction
This article describe how to use WebSocket to do ServerPush in ZK MVVM, modified from previous article ZK: Server Push with WebSocket (http://ben-bai.blogspot.tw/2013/07/zk-server-push-with-websocket.html) with the desktop based implementation from ZK AURequest with WebSocket (http://ben-bai.blogspot.tw/2013/09/zk-aurequest-with-websocket.html).
It integrates WebSocket in ZK for ServerPush in more general way and has better performance (compared with previous component based version).
Environment: Tomcat 7.0.42, ZK 6.5.2
NOTE: This is just a POC with some customized components.
Pre-request
ZK AURequest with WebSocket
http://ben-bai.blogspot.tw/2013/09/zk-aurequest-with-websocket.html
ZK Create Helper Tag to Make Programmer Happier
http://ben-bai.blogspot.tw/2013/09/zkcreatehelpertag-to.html
ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html
Result
View demo on line
http://screencast.com/t/zp1rcVhYa
Program
There are lots of source files, will only post code for some of them and post link for others.
index.zul
Entry page
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/WebContent/index.zul
push_to_all.zul
Push value to all desktop (i.e., Application scope) with button click.
<zk>
<!-- Tested with ZK 6.5.2 -->
<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('test.TestPushToAllVM')">
Counter:
<!-- intbox, listen to positive integer in the begining -->
<intbox readonly="true" />
<contextBinding field="value" context="@load(vm.counter)" />
listen to: <label value="@load(vm.counter)" />
<div />
inverse Counter:
<!-- intbox, listen to negative integer in the begining -->
<intbox id="inverseCounter" readonly="true" />
<contextBinding field="value" context="@load(vm.inverseCounter)" />
listen to: <label value="@load(vm.inverseCounter)" />
<div />
<!-- update value of positive/negative integer -->
<button id="updateCounterBtn" label="updaet counter and negative counter"
onClick="@command('updateCounter')" />
<!-- switch listening context of the two intboxess above -->
<button label="switch context" id="switchBtn"
onClick="@command('switchCounter')" />
</window>
</zk>
test.TestPushToAllVM.java
VM for push_to_all.zul
package test;
import impl.serverpush.ServerPushUtil;
import java.util.concurrent.atomic.AtomicInteger;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
/** Tested with ZK 6.5.2
*
* @author benbai123
*
*/
public class TestPushToAllVM {
/** context listened by intboxes */
private String _counter = "positive";
private String _inverseCounter = "negative";
/** desktop of this VM */
private Desktop targetDesktop;
/** counter used to update positive/negative integer */
private static AtomicInteger _cntCounter = new AtomicInteger(0);
// getters
public String getCounter () {
return _counter;
}
public String getInverseCounter () {
return _inverseCounter;
}
/**
* update value to context 'positive' and 'negative' via
* WebSocket
*
* All components that listen to these context will be updated
*/
@Command
public void updateCounter () {
int val = _cntCounter.incrementAndGet();
push(val, "positive", false);
push(-1*val, "negative", false);
}
/** switch listening components of 'positive' and 'negative' context
* current desktop only
*
*/
@Command
@NotifyChange({"counter", "inverseCounter"})
public void switchCounter () {
int val = _cntCounter.get();
// push to opposite (for self desktop) before switch context
push(val, "negative", true);
push(-1*val, "positive", true);
String tmp = _counter;
_counter = _inverseCounter;
_inverseCounter = tmp;
}
/** push value
*
* @param val
* @param context
*/
private void push (Object val, String context, boolean desktopOnly) {
try {
if (targetDesktop == null) {
targetDesktop = Executions.getCurrent().getDesktop();
}
if (desktopOnly) {
// push to current desktop only
ServerPushUtil.pushVlaue(val, context, targetDesktop);
} else {
// push to all desktop
ServerPushUtil.pushVlaue(val, context);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
automatic_push.zul
Update value with java thread, Desktop scope.
<zk>
<!-- Tested with ZK 6.5.2 -->
<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('test.TestAutoPushVM')">
Self:
<!-- intbox that will be updated -->
<intbox readonly="true" />
<contextBinding field="value" context="@load(vm.task)" />
<!-- start update -->
<button label="start" onClick="@command('start')" />
<!-- stop update -->
<button label="stop" onClick="@command('stop')" />
<!-- textbox that bind rows/cols to integer -->
<textbox />
<contextBinding id="cbd" field="@load(vm.prop)" context="@load(vm.task)" />
<button label="switch field (rows/cols)" onClick="@command('switchRowCol')" />
</window>
</zk>
test.TestAutoPushVM.java
VM for automatic_push.zul
package test;
import impl.serverpush.ServerPushUtil;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
/** Tested with ZK 6.5.2
*
* @author benbai123
*
*/
public class TestAutoPushVM {
/** Counter used to update integer */
private AtomicInteger _cnt = new AtomicInteger(0);
/** ServerPush task timer */
private Timer timer;
/** Desktop of this VM */
private Desktop targetDesktop;
/** Binded field of textbox */
private String _prop = "rows";
// getters
public String getTask () {
return "timerTask";
}
public String getProp () {
return _prop;
}
/**
* start server push with WebSocket for
* specific context "timerTask"
*
* current desktop only
*/
@Command
public void start () {
if (timer == null) {
// update once immediately when first time start
if (_cnt.get() == 0) {
push(_cnt.incrementAndGet(), "timerTask");
}
timer = new Timer();
timer.schedule(getTimerTask(), 1000, 1000);
}
}
/**
* stop server push with WebSocket for
* specific context "timerTask"
*
* current desktop only
*/
@Command
public void stop () {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Command
@NotifyChange("prop")
public void switchRowCol () {
if ("rows".equals(_prop)) {
_prop = "cols";
} else {
_prop = "rows";
}
}
// task to be scheduled to update context "timerTask" every second
private TimerTask getTimerTask () {
return new TimerTask() {
public void run () {
push(_cnt.incrementAndGet(), "timerTask");
}
};
}
/** push value to specific context
*
* @param val value to push
* @param context context to push
*/
private void push (Object val, String context) {
try {
if (targetDesktop == null) {
targetDesktop = Executions.getCurrent().getDesktop();
}
// push to current desktop only
ServerPushUtil.pushVlaue(val, context, targetDesktop);
} catch (Exception e) {
e.printStackTrace();
}
}
}
components.IWebSocketEnhancedComponent.java
Copied from AURequestWithWebSocket, completely the same.
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/IWebSocketEnhancedComponent.java
components.Intbox.java
Copied from AURequestWithWebSocket, change _useWebSocketAU default to true and override smartUpdate instead of setter.
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/Intbox.java
components.Textbox.java
Created for this POC, override smartUpdate instead of override setters separately.
Almost the same with Intbox (actually completely the same after serialVersionUID
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/Textbox.java
components.serverpush.IWebSocketServerPushEnhancedComponent.java
Define a "WebSocket Enhanced Component -- ServerPush type" for ServerPush pattern
package components.serverpush;
import impl.serverpush.Binding;
import components.IWebSocketEnhancedComponent;
/** Tested with ZK 6.5.2<br>
*
* Define a "WebSocket Enhanced Component -- ServerPush type"
* for ServerPush pattern
*
* @author benbai123
*
*/
public interface IWebSocketServerPushEnhancedComponent extends IWebSocketEnhancedComponent {
/** Add Binding for specific field/context pair
*
* The added Binding will be stored to a Binding List of a Component
*
* @param field field to bind
* @param context context to bind with field
* @return the Added Binding
*/
public Binding addSocketContextBinding (String field, String context);
/** Remove Binding for specific field/context pair
*
* Remove it from Binding List of a Component
*
* @param field field of field/context pair to remove
* @param context context of field/context pair to remove
*/
public void removeSocketContextBinding (String field, String context);
}
components.serverpush.Intbox.java
Created for this POC, implements IWebSocketServerPushEnhancedComponent to support WebSocket ServerPush Almost the same with Textbox (completely the same after serialVersionUID)
package components.serverpush;
import impl.serverpush.Binding;
import impl.serverpush.ServerPushUtil;
/** Tested with ZK 6.5.2<br>
*
* Created for this POC, implements IWebSocketServerPushEnhancedComponent to
* support WebSocket ServerPush
*
* Almost the same with Textbox (completely the same after serialVersionUID)
*
* @author benbai123
*
*/
public class Intbox extends components.Intbox implements IWebSocketServerPushEnhancedComponent {
private static final long serialVersionUID = -3277498174057967067L;
@Override
public Binding addSocketContextBinding(String field, String context) {
return ServerPushUtil.addContextBinding(this, field, context);
}
@Override
public void removeSocketContextBinding(String field, String context) {
ServerPushUtil.removeContextBinding(this, field, context);
}
}
components.serverpush.Textbox.java
Created for this POC, implements IWebSocketServerPushEnhancedComponent to support WebSocket ServerPush Almost the same with Intbox (completely the same after serialVersionUID)
package components.serverpush;
import impl.serverpush.Binding;
import impl.serverpush.ServerPushUtil;
/** Tested with ZK 6.5.2<br>
*
* Created for this POC, implements IWebSocketServerPushEnhancedComponent to
* support WebSocket ServerPush
*
* Almost the same with Intbox (completely the same after serialVersionUID)
*
* @author benbai123
*
*/
public class Textbox extends components.Textbox implements IWebSocketServerPushEnhancedComponent {
private static final long serialVersionUID = 2354643632056197764L;
@Override
public Binding addSocketContextBinding(String field, String context) {
return ServerPushUtil.addContextBinding(this, field, context);
}
@Override
public void removeSocketContextBinding(String field, String context) {
ServerPushUtil.removeContextBinding(this, field, context);
}
}
components.helper.ContextBinding.java
Helper component to help a component to bind field with ServerPush context
package components.helper;
import impl.serverpush.Binding;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Div;
import components.serverpush.IWebSocketServerPushEnhancedComponent;
/** Tested with ZK 6.5.2
*
* Helper component to help a component to bind field with ServerPush context
*
* @author benbai123
*
*/
public class ContextBinding extends Div {
private static final long serialVersionUID = -7156149643515776677L;
/** field to bind with context (required) */
private String _field = "";
/** context to bind with field (required) */
private String _context = "";
/** specified id of target component (optional) */
private String _target;
/** used to store current binding (relatively old) for changing field/context */
private Binding _oldBinding = null;
/** found target */
private IWebSocketServerPushEnhancedComponent _foundTarget;
@SuppressWarnings({ "unchecked", "rawtypes" })
public ContextBinding () {
// update target url while created
addEventListener(Events.ON_CREATE, new EventListener () {
public void onEvent (Event event) {
updateTargetBinding();
}
});
// do not output any html
setWidgetOverride ("redraw", "function (out) {}");
}
// setters
public void setTarget (String target) {
_target = target;
}
public void setField (String field) {
if (field == null) {
field = "";
}
if (!field.equals(_field)) {
_field = field;
updateTargetBinding();
}
}
public void setContext (String context) {
if (context == null) {
context = "";
}
if (!context.equals(_context)) {
_context = context;
updateTargetBinding();
}
}
/** Update Binding of target Component when field/context is changed
*
*/
private void updateTargetBinding () {
if (!_field.isEmpty() && !_context.isEmpty()) {
_foundTarget = findTarget();
if (_foundTarget != null) {
// remove old binding if exists
if (_oldBinding != null) {
if (_field.equals(_oldBinding.getField())
&& _context.equals(_oldBinding.getContext())) {
// already binded, do nothing
return;
}
_foundTarget.removeSocketContextBinding(_oldBinding.getField(), _oldBinding.getContext());
}
// add new binding and store it to _oldBinding
_oldBinding = _foundTarget.addSocketContextBinding(_field, _context);
}
}
}
/* package */ IWebSocketServerPushEnhancedComponent getFoundTarget () {
return _foundTarget;
}
/** Try to find target to update
* The order to try: <br>
* 1. Try to find fellow under the same space owner with specified "target" attribute.<br>
* 2. Try to find whether previous sibling is IWebSocketServerPushEnhancedComponent.<br>
* 3. Try to find whether previous sibling is ContextBinding and already found a target.<br>
* 4. Try to find whether parent is IWebSocketServerPushEnhancedComponent.<br>
*
* This way it can work without target attribute in most cases.
* @return IWebSocketServerPushEnhancedComponent if any
*/
/* package */ IWebSocketServerPushEnhancedComponent findTarget () {
Component comp;
Component previous = getPreviousSibling();
// Try to find fellow under the same space owner with specified "target" attribute.
if (_target != null && !_target.isEmpty()) {
comp = getSpaceOwner().getFellowIfAny(_target);
if (comp != null
&& (comp instanceof IWebSocketServerPushEnhancedComponent)) {
return (IWebSocketServerPushEnhancedComponent) comp;
}
}
// Try to find whether previous sibling is IWebSocketServerPushEnhancedComponent.
if (previous instanceof IWebSocketServerPushEnhancedComponent) {
return (IWebSocketServerPushEnhancedComponent) previous;
}
// Try to find whether previous sibling is ContextBinding and already found a target.
if (previous instanceof ContextBinding) {
IWebSocketServerPushEnhancedComponent previousTarget = ((ContextBinding) previous).getFoundTarget();
if (previousTarget != null) {
return previousTarget;
}
}
// Try to find whether parent is IWebSocketServerPushEnhancedComponent.
if (getParent() instanceof IWebSocketServerPushEnhancedComponent) {
return (IWebSocketServerPushEnhancedComponent)getParent();
}
return null;
}
}
impl.DesktopUtils.java
Modified from AURequestWithWebSocket, almost the same, add synchronized for ServerPush.
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/DesktopUtils.java
impl.TestWebSocketServlet.java
Modified from AURequestWithWebSocket, almost the same, store channel at desktop so can send response with desktop itself.
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/TestWebSocketServlet.java
impl.EventListener.java
Define an Event listener used to process event from Not used in this POC.
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/EventListener.java
impl.RequestFromWebSocket.java
Copied from AURequestWithWebSocket, completely the same excepts this fragment. Not used in this POC
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/RequestFromWebSocket.java
impl.serverpush.Binding.java
Binding that bind a field of a component to specific context for WebSocket ServerPush.
package impl.serverpush;
import java.io.Serializable;
/** Binding that bind a field of a component to specific context for WebSocket ServerPush
*
* @author benbai123
*
*/
public class Binding implements Serializable {
private static final long serialVersionUID = -1591783705902661276L;
/** field to bind of component */
private String _field;
/** context to bind with field */
private String _context;
// Constructor
public Binding (String field, String context) {
if (field == null) {
field = "";
}
if (context == null) {
context = "";
}
_field = field;
_context = context;
}
// getters, setters
public void setField (String field) {
if (field == null) {
field = "";
}
_field = field;
}
public String getField () {
return _field;
}
public void setContext (String context) {
if (context == null) {
context = "";
}
_context = context;
}
public String getContext () {
return _context;
}
// super
public int hashCode () {
return (_field + _context).hashCode() * 31;
}
public boolean equals (Object obj) {
if (obj != null
&& (obj instanceof Binding)) {
Binding b2 = (Binding) obj;
return (_field.equals(b2.getField())
&& _context.equals(b2.getContext()));
}
return false;
}
}
impl.serverpush.ServerPushUtil.java
Utilities for ServerPush with WebSocket, concept is similar to ServerPushWithWebSocket -- Component Oriented Version, rewritten it to Desktop Oriented Version.
package impl.serverpush;
import impl.TestWebSocketServlet;
import java.beans.Statement;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
/** Tested with ZK 6.5.2<br>
*
* Utilities for ServerPush with WebSocket, concept is similar to
* ServerPushWithWebSocket -- Component Oriented Version, rewritten it to
* Desktop Oriented Version.
*
* @author benbai123
*
*/
@SuppressWarnings("unchecked")
public class ServerPushUtil {
/** keep all binded Desktop */
private static List<Desktop> bindedDesktops = new ArrayList<Desktop>();
/** Lock for push to all desktops */
private static Integer LOCK_FOR_PUSH_TO_ALL = 0;
/** Lock for access bindedDesktops */
private static Integer LOCK_FOR_ACCESS_BINDED_DESKTOPS = 0;
/** attribute kay for binding map */
private static final String BINDING_MAP = "BINDING_MAP_FOR_WEBSOCKET_SERVERPUSH";
/** Add a Binding to a Component
*
* @param comp Component to bind member field with update context
* @param field field of component to bind
* @param context context to bind with field
* @return Binding the added Binding object
*/
public static Binding addContextBinding (Component comp, String field, String context) {
Binding binding = null;
synchronized (comp.getDesktop()) {
// get binding list of specified component
List<Binding> bindingList = getBindingList(comp);
// create a binding object with specified field/context
binding = new Binding(field, context);
// add binding into binding list if not exists
if (!bindingList.contains(binding)) {
bindingList.add(binding);
}
}
// return created binding
return binding;
}
/** Remove a Binding from a Component
*
* @param comp Component to remove Binding
* @param field field of the field-context pair to remove
* @param context context of the field-context pair to remove
*/
public static void removeContextBinding (Component comp, String field, String context) {
synchronized (comp.getDesktop()) {
Map<Component, List<Binding>> bindingMap = getBindingMap(comp.getDesktop());
// get binding list of specified component
List<Binding> bindingList = bindingMap.get(comp);
if (bindingList != null) {
// create a binding object with specified field/context
Binding target = new Binding (field, context);
// remove binding if exists
for (Binding b : bindingList) {
if (b.equals(target)) {
bindingList.remove(b);
break;
}
}
}
}
}
/** Push a value to a context for all desktops
*
* @param value value to push
* @param context context to push to
* @throws Exception whatever
*/
public static void pushVlaue (Object value, String context) throws Exception {
synchronized (LOCK_FOR_PUSH_TO_ALL) {
// used to store dead desktop
List<Desktop> deadDesktops = new ArrayList<Desktop>();
// make a copy to reduce lock
List<Desktop> desktopToPush = new ArrayList<Desktop>();
synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
desktopToPush.addAll(bindedDesktops);
}
// for each binded desktop
for (Desktop bindedDesktop : desktopToPush) {
if (bindedDesktop.isAlive()) {
// push value to context if alive
execPush(value, context, bindedDesktop);
} else {
// store it to deadDesktops otherwise
deadDesktops.add(bindedDesktop);
}
}
// remove all dead desktops
synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
bindedDesktops.removeAll(deadDesktops);
}
desktopToPush.removeAll(deadDesktops);
for (Desktop dt : desktopToPush) {
// send response via WebSocket
TestWebSocketServlet.sendResponse(dt);
}
}
}
/** Push a value to a context for specified desktop
*
* @param value value to push
* @param context context to push to
* @param desktop desktop to apply this push
* @throws Exception whatever
*/
public static void pushVlaue (Object value, String context, Desktop desktop) throws Exception {
if (desktop.isAlive()) {
// push value to context for specified desktop if alive
execPush(value, context, desktop);
} else {
synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
// remove specified desktop from bindedDesktops otherwise
bindedDesktops.remove(desktop);
}
}
// send response via WebSocket
TestWebSocketServlet.sendResponse(desktop);
}
/** Apply "push value to context" for specified desktop
*
* @param value value to push
* @param context context to push to
* @param desktop desktop to apply
* @throws Exception whatever
*/
public static void execPush (Object value, String context, Desktop desktop) throws Exception {
// execute push if WebSocket of desktop is ready
if (TestWebSocketServlet.isWebSocketReady(desktop)) {
synchronized (desktop) {
// get binding map
// (Map<String, List> where String is component ID and List is Binding objects)
Map<Component, List<Binding>> bindingMap = (Map<Component, List<Binding>>)desktop.getAttribute(BINDING_MAP);
if (bindingMap != null) {
for (Map.Entry<Component, List<Binding>> e : bindingMap.entrySet()) {
// get component by ID
Component comp = e.getKey();
// get binding list
List<Binding> bindings = e.getValue();
// for each binding
for (Binding binding : bindings) {
// push value to component if
// context of binding is equal to specified context
if (binding.getContext().equals(context)) {
// get field from binding
String field = binding.getField();
// build setter name
String method = "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
// call setter to set value to component
Statement stat = new Statement(comp, method, new Object[]{value});
stat.execute();
}
}
}
}
}
}
}
/** Get Binding list of a Component
*
* @param comp Component to get Binding list
* @return Binding list for specified Component
*/
private static List<Binding> getBindingList (Component comp) {
// get binding map
Map<Component, List<Binding>> bindingMap = getBindingMap(comp.getDesktop());
// try to get binding list
List<Binding> bindingList = bindingMap.get(comp);
// create and add binding list if not exists
if (bindingList == null) {
bindingList = new ArrayList<Binding>();
bindingMap.put(comp, bindingList);
}
// return (created) binding list
return bindingList;
}
/** Get Binding map of a Desktop
*
* @param dt specified Desktop
* @return Map<Component, List<Binding>> Binding map of specified Desktop
*/
private static Map<Component, List<Binding>> getBindingMap (Desktop dt) {
// try to get binding map
Map<Component, List<Binding>> bindingMap = (Map<Component, List<Binding>>)dt.getAttribute(BINDING_MAP);
// create and add binding map if not exists
if (bindingMap == null) {
bindingMap = new Hashtable<Component, List<Binding>>();
dt.setAttribute(BINDING_MAP, bindingMap);
synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
bindedDesktops.add(dt);
}
}
// return (created) binding map
return bindingMap;
}
}
zk.xml
Modified from AURequestWithWebSocket, add config for lang-addon.xml, also change the way to get path for WebSocket (line 61~)
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/WebContent/WEB-INF/zk.xml
lang-addon.xml
Define components used in this POC.
<!-- Tested with ZK 6.5.2
Define components
-->
<language-addon>
<addon-name>param</addon-name>
<language-name>xul/html</language-name>
<!-- It specifies what language addons this addon
depends on. If specified, this addon will be
parsed after all the specified addons are parsed -->
<depends>zul</depends>
<!-- define param -->
<component>
<component-name>contextBinding</component-name>
<extends>div</extends>
<component-class>components.helper.ContextBinding</component-class>
</component>
<component>
<component-name>intbox</component-name>
<extends>intbox</extends>
<component-class>components.serverpush.Intbox</component-class>
</component>
<component>
<component-name>textbox</component-name>
<extends>textbox</extends>
<component-class>components.serverpush.Textbox</component-class>
</component>
</language-addon>
Download
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Integrate/WebSocket/ServerPushWithWebSocket
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/ServerPushWithWebSocket.swf
Sunday, September 8, 2013
WebSocketAU vs ZKAU Performance Testing
Simple Note
Testing Steps
1. Start Tomcat
2. Open visualVM
3. Open 5 testing pages (index.zul)
4. Wait 2 minutes
5. Take snapshot
Testing Result
Result of WebSocket AU
Result of ZKAU
Conclutions
Memory Consumption: Almost the same
CPU Usage: WebSocket AU (~15% then 1% ~ 3%) is better than ZK AU (~33% than 2% ~ 13%)
Download
Testing project at github
https://github.com/benbai123/ZK_Practice/tree/master/General/Performance_Testing/WebSocketAU_vs_ZKAU
Saturday, September 7, 2013
ZK AURequest with WebSocket
Introduction
This article describe how to use WebSocket to process Request/Response in ZK.
NOTE: This is just a POC with customized ZK timer and intbox
Environment: Tomcat 7.0.42, ZK 6.5.2
Result
View demo on line
http://screencast.com/t/J6czUwBia0CI
Pre-request
Simple WebSocket Test with Tomcat
http://ben-bai.blogspot.tw/2013/07/simple-websocket-test-with-tomcat.html
ZK: Override Widget in zk.xml
http://ben-bai.blogspot.tw/2013/07/zk-override-widget-in-zkxml.html
Group Connections with WebSocket
http://ben-bai.blogspot.tw/2013/07/group-connections-with-websocket.html
Program
index.zul
Use customized timer and intbox in it.
<zk>
<!-- Tested with ZK 6.5.2 -->
<div apply="test.TestComposer">
<!-- use custom timer and intbox -->
<timer id="timer"
repeats="true" running="true" delay="1000"
use="components.Timer"
useWebSocketAU="true" />
<timer id="timer2"
repeats="true" running="true" delay="500"
use="components.Timer"
useWebSocketAU="true" />
<intbox id="ibx" value="0"
use="components.Intbox"
useWebSocketAU="true" />
<intbox id="ibx2" value="0"
use="components.Intbox"
useWebSocketAU="true" />
</div>
</zk>
TestComposer.java
Composer for test page, register EventListener for WebSocket to component in it.
package test;
import impl.EventListener;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Wire;
import components.IWebSocketEnhancedComponent;
import components.Intbox;
import components.Timer;
/** Tested with ZK 6.5.2
*
* @author benbai123
*
*/
public class TestComposer extends SelectorComposer<Component> {
private static final long serialVersionUID = -5014610291543614202L;
// custom timer and intbox
@Wire
Timer timer;
@Wire
Timer timer2;
@Wire
Intbox ibx;
@Wire
Intbox ibx2;
public void doAfterCompose (Component comp) throws Exception {
super.doAfterCompose(comp);
registerEventListenerForWebSocketEnhancedTimer();
}
/** Register EventListener for WebSocket,
* Events.postEvent cannot work with WebSocket (without HttpRequest),
* so cannot use @Listen in Composer
*
*/
protected void registerEventListenerForWebSocketEnhancedTimer () {
// cast to IWebSocketEnhancedComponent
IWebSocketEnhancedComponent enhancedTimer = (IWebSocketEnhancedComponent)timer;
// register event listener for onTimer event of timer
enhancedTimer.registerListenerForWebSocketEvent("onTimer",
new EventListener () {
private static final long serialVersionUID = -8920291597084200994L;
public void onEvent (Event event) {
// increase value of intbox
ibx.setValue(ibx.getValue() + 1);
}
}
);
enhancedTimer = (IWebSocketEnhancedComponent)timer2;
// register event listener for onTimer event of timer2
enhancedTimer.registerListenerForWebSocketEvent("onTimer",
new EventListener () {
private static final long serialVersionUID = -5721055473084949736L;
public void onEvent (Event event) {
// increase value of intbox
ibx2.setValue(ibx2.getValue() - 1);
}
}
);
}
}
IWebSocketEnhancedComponent.java
Define a "WebSocket Enhanced Component"
package components;
import impl.EventListener;
import impl.RequestFromWebSocket;
/**
* Define a "WebSocket Enhanced Component"
* for request-response pattern
*
* @author benbai123
*
*/
public interface IWebSocketEnhancedComponent {
/**
* Whether use WebSocket to process AU request
*/
public void setUseWebSocketAU (boolean useWebSocketAU) ;
/**
* Help desktop to register itself into session
* since we need it in WebSocket and we will not
* use custom desktop for this POC
*/
public void helpDesktopToRegister () ;
/**
* Process event sent from client
* @see TestWebSocketServlet.TestMessageInbound#onTextMessage(java.nio.CharBuffer)
*/
public void serviceWebSocket (RequestFromWebSocket request) ;
/**
* Update status to client
*/
public void addUpdateProp (String prop, String value) ;
/**
* Register Event Listener at Component itself since
* Events.postEvent cannot work with WebSocket without
* an active Execution, need to register listener manually in
* Composer
* @param listener EventListener used to process event from WebSocket request
*/
public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) ;
}
Timer.java
Implement IWebSocketEnhancedComponent as needed, process custom EventListener with onTimer Event.
package components;
import impl.DesktopUtils;
import impl.EventListener;
import impl.RequestFromWebSocket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.zkoss.zk.ui.event.Event;
public class Timer extends org.zkoss.zul.Timer implements IWebSocketEnhancedComponent {
private static final long serialVersionUID = 1006786038089103071L;
private boolean _useWebSocketAU;
// hold event listener, put it here since
// we will not use customized AbstractComponent in this POC
private Map<String, List<EventListener>> _listenerForWebSocket = new HashMap<String, List<EventListener>>();;
@Override
public void setUseWebSocketAU (boolean useWebSocketAU) {
if (_useWebSocketAU != useWebSocketAU) {
_useWebSocketAU = useWebSocketAU;
smartUpdate("useWebSocketAU", useWebSocketAU);
}
}
@Override
public void helpDesktopToRegister () {
if (getDesktop() != null) {
// register desktop
DesktopUtils.register(getDesktop());
}
}
// called by TestWebSocketServlet.TestMessageInbound#onTextMessage(java.nio.CharBuffer)
@Override
public void serviceWebSocket(RequestFromWebSocket request) {
String command = request.getCommand();
if ("onTimer".equals(command)) {
for (EventListener l : _listenerForWebSocket.get("onTimer")) {
l.onEvent(new Event("onTimer", this, null));
}
}
}
@Override
public void addUpdateProp (String prop, String value) {
// ignore, not used in this POC
}
// called by TestComposer
/**
* register event listener with specific event name
*/
@Override
public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) {
// try to get listener list with respect to specified event name
List<EventListener> listeners = _listenerForWebSocket.get(evtnm);
if (listeners == null) {
// create new if list is not exists
listeners = new ArrayList<EventListener>();
_listenerForWebSocket.put(evtnm, listeners);
}
// add event listener into listener list
listeners.add(listener);
}
// render useWebSocketAU as needed
protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
throws java.io.IOException {
super.renderProperties(renderer);
if (_useWebSocketAU) {
helpDesktopToRegister();
// render useWebSocketAU to client side
render(renderer, "useWebSocketAU", _useWebSocketAU);
}
}
}
Intbox.java
Implement IWebSocketEnhancedComponent as needed, override setValue to update status to client by WebSocket if needed.
package components;
import impl.DesktopUtils;
import impl.EventListener;
import impl.RequestFromWebSocket;
public class Intbox extends org.zkoss.zul.Intbox implements IWebSocketEnhancedComponent {
private static final long serialVersionUID = -6488494817604420277L;
private boolean _useWebSocketAU;
@Override
public void setUseWebSocketAU (boolean useWebSocketAU) {
if (_useWebSocketAU != useWebSocketAU) {
_useWebSocketAU = useWebSocketAU;
smartUpdate("useWebSocketAU", useWebSocketAU);
}
}
@Override
public void helpDesktopToRegister () {
if (getDesktop() != null) {
// register desktop
DesktopUtils.register(getDesktop());
}
}
@Override
public void serviceWebSocket(RequestFromWebSocket request) {
/* ignore, not used in this POC */
}
@Override
public void addUpdateProp (String prop, String value) {
// add prop/value to update
DesktopUtils.updateComponentProp(this, prop, value);
}
@Override
public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) {
/* ignore, not used in this POC */
}
// render useWebSocketAU as needed
protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
throws java.io.IOException {
super.renderProperties(renderer);
if (_useWebSocketAU) {
helpDesktopToRegister();
// render useWebSocketAU to client side
render(renderer, "useWebSocketAU", _useWebSocketAU);
}
}
public void setValue (int value) {
if (_useWebSocketAU) { // use WebSocket to process AU request?
// set value without smartUpdate
super.setValueDirectly(value);
// update client status via WebSocket
addUpdateProp("value", value+"");
} else {
// original function
super.setValue(value);
}
}
}
EventListener.java
Used to process event in this POC.
package impl;
import java.io.Serializable;
import org.zkoss.zk.ui.event.Event;
/**
* Define an Event listener used to process event from
* WebSocket request
*
* @author benbai123
*
*/
public interface EventListener extends Serializable {
/** Called when the registered event is triggered
*
* @param event
* @see TestComposer#registerEventListenerForWebSocketEnhancedTimer()
*/
public void onEvent (Event event) ;
}
RequestFromWebSocket.java
Used to create a request object for WebSocket request message.
package impl;
import java.util.Map;
import org.zkoss.zk.ui.Component;
public class RequestFromWebSocket {
/** Event name */
private String _command;
/** Target component */
private Component _comp;
/** Data map */
private Map<Object, Object> _data;
// Constructor
public RequestFromWebSocket (String command, Component comp, Map<Object, Object> data) {
_command = command;
_comp = comp;
_data = data;
}
// getters
public String getCommand () {
return _command;
}
public Component getComponent () {
return _comp;
}
public Map<Object, Object> getData () {
return _data;
}
}
DesktopUtils.java
Utility of desktop, used to register desktop, store status to update, build response.
package impl;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpSession;
import org.zkoss.json.JSONObject;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Sessions;
public class DesktopUtils {
public static final String ATTRIBUTES_TO_UPDATE = "ATTRIBUTES_TO_UPDATE";
public static final String REGISTERED_DESKTOPS = "REGISTERED_DESKTOPS";
public static final String ALREADY_REGISTERED = "ALREADY_REGISTERED";
public static void updateComponentProp (Component comp, String prop, String val) {
String id = comp.getUuid();
getPropMap(comp.getDesktop(), id).put(prop, val);
}
/** Register desktop so we can try to find it when
* WebSocket receive AU request from client side
*/
public static void register (Desktop desktop) {
if (desktop.getAttribute(ALREADY_REGISTERED) == null) {
getRegisteredDesktops(null).put(desktop.getId(), desktop);
desktop.setAttribute(ALREADY_REGISTERED, ALREADY_REGISTERED);
}
}
/** Get registered desktop
*
* @param sess HttpSession
* @param id desktop ID
* @return Desktop
*/
public static Desktop getRegisteredDesktop (HttpSession sess, String id) {
return getRegisteredDesktops(sess).get(id);
}
/** Remove registered desktop
*
* @param sess HttpSession
* @param id desktop ID
*/
public static void removeRegisteredDesktop (HttpSession sess, String id) {
getRegisteredDesktops(sess).remove(id);
}
/** Get Map for registered desktops
*
* @param sess HttpSession
* @return Map<String, Desktop>
*/
@SuppressWarnings("unchecked")
private static Map<String, Desktop> getRegisteredDesktops (HttpSession sess) {
Map<String, Desktop> registeredDesktops = null;
if (sess == null) {
// try to find session if not specified
sess = (HttpSession)Sessions.getCurrent().getNativeSession();
}
synchronized (sess) {
// try to find Map for registered desktops
registeredDesktops = (Map<String, Desktop>)sess.getAttribute(REGISTERED_DESKTOPS);
if (registeredDesktops == null) {
// create Map for registered desktops and store it
// into session if not exists
registeredDesktops = new Hashtable<String, Desktop>();
sess.setAttribute(REGISTERED_DESKTOPS, registeredDesktops);
}
}
return registeredDesktops;
}
/** Build AU response of a desktop
*
* @param desktop the desktop to build AU response
* @return String response content
*/
public static String buildResponse (Desktop desktop) {
Map<String, Map<String, String>> compMap = getAttributesToUpdate(desktop);
// response JSON object
JSONObject resp = new JSONObject();
// for each component data
for (Entry<String, Map<String, String>> entry : compMap.entrySet()) {
Map<String, String> propMap = entry.getValue();
// prop/value JSON object
JSONObject propAndVal = new JSONObject();
// for each prop/value pair
for (Entry<String, String> propEntry : propMap.entrySet()) {
propAndVal.put(propEntry.getKey(), propEntry.getValue());
}
resp.put(entry.getKey(), propAndVal);
}
// clear data
getAttributesToUpdate(desktop).clear();
return resp.toJSONString();
}
/** Get Map that contains properties to update of a component
*
* @param desktop specific desktop
* @param id component id
* @return
*/
private static Map<String, String> getPropMap (Desktop desktop, String id) {
// try to get propMap
Map<String, Map<String, String>> compMap = getAttributesToUpdate(desktop);
Map<String, String> propMap;
propMap = compMap.get(id);
if (propMap == null) {
// create propMap if not exists
propMap = new Hashtable<String, String>();
compMap.put(id, propMap);
}
return propMap;
}
/** Get Map that contains components Map to update of a desktop
*
* structure: componentMap<key, propertyValueMap<prop, value>>
*
* @param desktop
* @return
*/
@SuppressWarnings("unchecked")
private static Map<String, Map<String, String>> getAttributesToUpdate (Desktop desktop) {
// try to get map
Map<String, Map<String, String>> attributesToUpdate =
(Map<String, Map<String, String>>)desktop.getAttribute(ATTRIBUTES_TO_UPDATE);
if (attributesToUpdate == null) {
// create if not exists
attributesToUpdate = new Hashtable<String, Map<String, String>>();
desktop.setAttribute(ATTRIBUTES_TO_UPDATE, attributesToUpdate);
}
return attributesToUpdate;
}
}
TestWebSocketServlet.java
Servlet for WebSocket, process request (msg) response in it.
package impl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import org.zkoss.json.JSONArray;
import org.zkoss.json.JSONObject;
import org.zkoss.json.JSONValue;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import components.IWebSocketEnhancedComponent;
/**
* Tested with Tomcat 7.0.42 and ZK 6.5.2
* @author benbai123
*
*/
public class TestWebSocketServlet extends WebSocketServlet {
private static final long serialVersionUID = -7663708549630020769L;
/**
* For create connection only, each connection will
* handle it self as needed
*/
@Override
protected StreamInbound createWebSocketInbound(String subProtocol,
HttpServletRequest request) {
// request uri, format is desktopId.wsreq
String uri = request.getRequestURI();
// get desktopId from uri
String desktopId = uri.substring(uri.lastIndexOf("/")+1, uri.length()).replace(".wsreq", "");
// create MessageInbound with desktop ID and session
return new TestMessageInbound(desktopId, request.getSession());
}
private final class TestMessageInbound extends MessageInbound {
// hold desktop id and session
private String _desktopId;
private HttpSession _session;
// constructor
public TestMessageInbound (String desktopId, HttpSession session) {
_desktopId = desktopId;
_session = session;
}
@Override
protected void onOpen(WsOutbound outbound) {
/* ignore */
}
// remove registered desktop
@Override
protected void onClose(int status) {
DesktopUtils.removeRegisteredDesktop(_session, _desktopId);
}
// ignore binary message
@Override
protected void onBinaryMessage(ByteBuffer message) throws IOException {
/* ignore */
}
/** Entry point, you can think it is similar to
* service method (doGet/doPost) in common Servlet
*
*/
@SuppressWarnings("unchecked")
@Override
protected void onTextMessage(CharBuffer message) throws IOException {
// pass request to components
// request from client: {
// dtid: DESKTOP_ID,
// [
// {"uuid": COMPONENT_ID_1, "evtnm": EVENT_NAME_1, "data": DATA_1},
// {"uuid": COMPONENT_ID_2, "evtnm": EVENT_NAME_2, "data": DATA_2},
// ...
// ]
// }
// parse to JSON object
JSONObject jsObj = (JSONObject)JSONValue.parse(message.toString());
// System.out.println("desktop: " + jsObj.get("dtid"));
// enable the line below to see (merged) request from Client
// System.out.println("requests: " + jsObj.get("requests"));
// get desktop
Desktop desktop = DesktopUtils.getRegisteredDesktop(_session, (String)jsObj.get("dtid"));
JSONArray requests = (JSONArray)JSONValue.parse((String)jsObj.get("requests"));
// for each request in requests
for (int i = 0; i < requests.size(); i++) {
// get request
JSONObject reqObj = (JSONObject)requests.get(i);
// find component
Component target = desktop.getComponentByUuidIfAny((String)reqObj.get("uuid"));
// get data
Map<Object, Object> data = (Map<Object, Object>)reqObj.get("data");
// create request object
RequestFromWebSocket req = new RequestFromWebSocket((String)reqObj.get("evtnm"), target, data);
// pass request object to service method of component
((IWebSocketEnhancedComponent)target).serviceWebSocket(req);
}
// build and send response after service
sendResponse(this, desktop);
}
}
/** Send message via WebSocket to specific desktop
*
* @param connection connection to use
* @param desktop used to build response
*/
public static void sendResponse (TestMessageInbound connection, Desktop desktop) {
// build response
String response = DesktopUtils.buildResponse(desktop);
try {
// send response
connection.getWsOutbound().writeTextMessage(CharBuffer.wrap(response));
} catch (IOException ignore) {
/* ignore */
}
}
}
zk.xml
Override javascript functions to support WebSocket.
<zk>
<device-config>
<device-type>ajax</device-type>
<embed><![CDATA[
<script type="text/javascript">
zk.afterLoad("zul", function () {
var _wgt = {};
zk.override(zk.Widget.prototype, _wgt, {
// setter for set context of WebSocket
setUseWebSocketAU: function (v) {
if (v != this._useWebSocketAU)
this._useWebSocketAU = v;
},
bind_: function (dt, skipper, after) {
// call original function
_wgt.bind_.apply(this, arguments);
// initiate WebSocket after bind_
if (this._useWebSocketAU)
this.helpDesktopToInitWebSocket();
},
// init WebSocket
helpDesktopToInitWebSocket: function () {
var desktop = this.desktop;
// if didn't init
if (desktop && !desktop.TestWebSocket) {
// override desktop to support WebSocket
overrideDesktop(desktop);
desktop.TestWebSocket.connect();
}
}
});
});
// Override Timer widget and desktop since
// we do not want to override zAu / jq.xhr for this POC
zk.afterLoad("zul.utl", function () {
var _tmWgt = {};
zk.override(zul.utl.Timer.prototype, _tmWgt, {
// onTimer
_tmfn: function () {
if (!this._repeats) this._running = false;
// whether _useWebSocketAU?
if (this._useWebSocketAU
&& this.desktop.webSocketReady) {
// build and send request via WebSocket
var req = {uuid: this.uuid,
evtnm: 'onTimer',
data: {}
};
this.desktop.sendRequestToWebSocket(req);
} else // call original function
this.fire('onTimer', null, {ignorable: true});
}
});
});
function overrideDesktop (desktop) {
desktop.TestWebSocket = {
socket: null,
connect: (function() {
// .wsreq for servlet mapping defined in web.xml
var path = window.location.host + window.location.pathname,
host = 'ws://' + path + desktop.id + '.wsreq';
if ('WebSocket' in window) {
this.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
this.socket = new MozWebSocket(host);
} else {
alert('Error: WebSocket is not supported by this browser.');
return;
}
// process message from server
this.socket.onmessage = function (msg) {
desktop.doWebSocketMessage_(msg);
};
// store ready state for components to check
this.socket.onopen = function () {
desktop.webSocketReady = true;
};
}),
disconnect: function () {
// close and clear
this.socket.close();
this.socket = null;
desktop.TestWebSocket = null;
},
// send message to server
sendRequestToWebSocket: (function(msg) {
this.socket.send(msg);
})
};
desktop.doWebSocketMessage_ = function (msg) {
// parse response (msg.data)
// pattern: {
// componentId: {prop: val, prop2: val2, ...},
// componentId2: {prop: val, prop2: val2, ...}, ...
// }
// Enable the line below to see (merged) response from server
// zk.log(msg.data);
var resp = jq.evalJSON(msg.data),
props, // properties ({key: value, ...}) to update
val,
setter,
wgt;
// for each component (ID)
for (var key in resp) {
// get widget by ID
wgt = zk.Widget.$('#'+key);
// get properties to update by ID
props = resp[key];
// for each property
for (var prop in props) {
// get value by property name
val = props[prop];
// build setter by property name (xyz -> setXyz)
setter = 'set' + prop.charAt(0).toUpperCase() + prop.slice(1);
// call setter to set value to widget
wgt[setter](val);
}
}
};
// API for widget to send request to server
desktop.sendRequestToWebSocket = function (req) {
if (!desktop.eventArrayForWebSocket)
desktop.eventArrayForWebSocket = [];
// push req into an array
desktop.eventArrayForWebSocket.push(req);
// send multiple req with single request
if (!desktop.sendRequestTimerForWebSocket) {
desktop.sendRequestTimerForWebSocket = setTimeout(function () {
var jsonToSend = jq.toJSON({
dtid: desktop.id,
requests: jq.toJSON(desktop.eventArrayForWebSocket)
});
desktop.TestWebSocket.sendRequestToWebSocket(jsonToSend);
desktop.eventArrayForWebSocket = desktop.sendRequestTimerForWebSocket = null;
}, 50);
}
};
}
</script>
]]></embed>
</device-config>
</zk>
web.xml
Define servlet for WebSocket.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>ReplaceAuRequestWithWebSocket</display-name>
<servlet>
<servlet-name>testWebSocketServlet</servlet-name>
<servlet-class>impl.TestWebSocketServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testWebSocketServlet</servlet-name>
<url-pattern>*.wsreq</url-pattern>
</servlet-mapping>
</web-app>
Reference
ZK Source
http://github.com/zkoss/zk/tree/master/zul/src/
Download
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Integrate/WebSocket/AURequestWithWebSocket
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Integrate/WebSocket/AURequestWithWebSocket.swf
Labels:
Custom Component,
HTML5,
Java,
Javascript,
Servlet,
Tomcat,
Web Server,
ZK
Subscribe to:
Posts (Atom)







