Introduction
This is the 6th article of ZK CDT (ZK Component Development Tutorial) walkthrough, this article describe why and how to wrap the request data by a custom event.
Create Custom Event
The goal of this walkthrough is to create a quicknote component that help you do some quick note on web page (similar to you crop some screenshot, highlight something and add some text in photo editor), and describe each part of a component through each article in this walkthrough.
This is the 6th part: Create custom event.
Result
View demo online:
http://screencast.com/t/w974QUMlI
What you changed at client side will be updated to server side directly now.
Pre-request
ZK CDT: Fire Event to Server to Create a SelectableTextNote
http://ben-bai.blogspot.tw/2013/06/zk-cdt-fire-event-to-server-to-create.html
Program
recordabletextnote.zul
Contains a recordabletextnote component, a control block that used to add/update/clear note blocks, a slider/colorbox that control the opacity/background-color of the recordabletextnote and an information block that display the changed note block.
<zk>
<!-- several new things
selectedTextNoteData="@save(vm.selectedTextNoteData, before='updateAttrs,addNoteBlock')"
denotes save attribute selectedTextNoteData to VM before the
command 'updateAttrs' in VM is triggered
we need to update the data of selectedTextNoteData when
the selected text note block is changed or when
the attributes of selected text note block is changed
but ZKBIND currently does not support specify multiple save event
so we use @save annotation in MVVM to specify extra event
however we still need to specify selectedTextNoteData in lang-addon.xml to
change its direction to save at first
without this line the data of selected text note block will be changed with
onSelectedTextNoteBlockChanged event only
also save it before add a new block with add button
ref: http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Syntax/Data_Binding/@save
onTextNoteBlockSelect="@command('updateAttrs')"
trigger updateAttrs while onTextNoteBlockSelect event so it will
trigger save action of selectedTextNoteData
bind value of selected note block
value="@bind(vm.selectedTextNoteData.xxx)"
this will load/save attributes from/to selected text note data
onChange="@command('updateModel')"
this will trigger 'updateModel' command in VM,
the command will refresh model with NotifyChange annotation
update model manually since we didn't implement update mechanism
of model
added 'changed data' block to show which block is changed
-->
<div apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('custom.zk.samples.quicknote.RecordableTextNoteVM')">
<hlayout>
<!-- The recordabletextnote component that will cover
its children by a mask
you can click on the mask to add a textarea
and type text in it
-->
<recordabletextnote width="700px" id="stn"
opacity="@load(vm.opacity)" maskColor="@load(vm.maskColor)"
model="@load(vm.model)"
selectedTextNoteData="@save(vm.selectedTextNoteData, before={'updateAttrs','addNoteBlock'})"
selectedTextNoteIndex="@save(vm.indexToUpdate)"
onTextNoteBlockSelect="@command('updateAttrs')"
onSelectedTextNoteBlockChanged="@command('updateAttrs')"
onTextNoteBlockChanged="@command('updateChangedTextNoteData')">
<button label="ZK Website" />
<iframe width="100%"
height="1000px"
src="http://www.zkoss.org/"></iframe>
</recordabletextnote>
<vlayout>
<!-- controll block for add/update/clear note blocks -->
<vlayout>
<label value="controll block for add/update/clear note blocks" />
<hlayout>
index: <intbox value="@load(vm.indexToUpdate)" readonly="true" />
</hlayout>
<hlayout>
x: <intbox value="@bind(vm.textNoteData.posX)"
onChange="@command('updateModel')" />
</hlayout>
<hlayout>
y: <intbox value="@bind(vm.textNoteData.posY)"
onChange="@command('updateModel')" />
</hlayout>
<hlayout>
width: <intbox value="@bind(vm.textNoteData.width)"
onChange="@command('updateModel')" />
</hlayout>
<hlayout>
height: <intbox value="@bind(vm.textNoteData.height)"
onChange="@command('updateModel')" />
</hlayout>
<hlayout>
text: <textbox value="@bind(vm.textNoteData.text)"
onChange="@command('updateModel')" />
</hlayout>
<hlayout>
<button label="add" onClick="@command('addNoteBlock')" />
</hlayout>
<hlayout>
<button label="clear" onClick="@command('clearAllBlocks')" />
</hlayout>
</vlayout>
<hlayout style="margin-top: 10px;">
<!-- slider used to control opacity of recordabletextnote -->
<slider curpos="@bind(vm.opacity)" maxpos="100"
onScroll="@command('updateOpacity')" />
<!-- colorbox used to control mask color of recordabletextnote -->
<colorbox color="@bind(vm.maskColor)"
onChange="@command('updateMaskColor')" />
</hlayout>
<!-- information of changed note block data -->
<vlayout style="margin-top: 10px;">
<label value="information of changed note block data" />
<hlayout>
changed idnex: <intbox readonly="true" value="@load(vm.changedIndex)" />
</hlayout>
<hlayout>
x: <intbox readonly="true" value="@load(vm.changedTextNoteData.posX)" />
</hlayout>
<hlayout>
y: <intbox readonly="true" value="@load(vm.changedTextNoteData.posY)" />
</hlayout>
<hlayout>
width: <intbox readonly="true" value="@load(vm.changedTextNoteData.width)" />
</hlayout>
<hlayout>
height: <intbox readonly="true" value="@load(vm.changedTextNoteData.height)" />
</hlayout>
<hlayout>
text: <textbox readonly="true" value="@load(vm.changedTextNoteData.text)" />
</hlayout>
</vlayout>
</vlayout>
</hlayout>
</div>
</zk>
RecordableTextNoteVM.java
VM used in recordabletextnote.zul, provide data, do command and update data.
package custom.zk.samples.quicknote;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.NotifyChange;
import custom.zk.components.quicknote.Data.TextNoteData;
import custom.zk.components.quicknote.event.TextNoteBlockUpdateEvent;
public class RecordableTextNoteVM extends SelectableTextNoteVM {
// data from recordabletextnote component
// there is a getter 'getSelectedTextNoteData' in
// recordabletextnote so we can get data from it
// via MVVM @save annotation
TextNoteData _selectedTextNoteData;
// data grabbed from TextNoteBlockUpdateEvent
// there is no getter of changed text note data
// so we need to grab it from event while
// onTextNoteBlockChanged
TextNoteData _changedTextNoteData;
// index of changed text note data
int _changedIndex;
public void setSelectedTextNoteData (TextNoteData data) {
if (data != null) {
_selectedTextNoteData = data;
// copy data for add function in super class (RenderableTextNoteVM.java)
TextNoteData dataToAdd = super.getTextNoteData();
dataToAdd.setPosX(data.getPosX());
dataToAdd.setPosY(data.getPosY());
dataToAdd.setWidth(data.getWidth());
dataToAdd.setHeight(data.getHeight());
dataToAdd.setText(data.getText());
}
}
public TextNoteData getSelectedTextNoteData () {
return _selectedTextNoteData;
}
public TextNoteData getChangedTextNoteData () {
return _changedTextNoteData;
}
public int getChangedIndex () {
return _changedIndex;
}
// Override
public TextNoteData getTextNoteData () {
return _selectedTextNoteData != null? _selectedTextNoteData : super.getTextNoteData();
}
@Command
@NotifyChange("textNoteData")
public void updateAttrs () {
// for trigger update
}
@Command
@NotifyChange("model")
public void updateModel () {
// for trigger update
}
@Command
@NotifyChange({"changedTextNoteData", "changedIndex"})
public void updateChangedTextNoteData (@ContextParam(ContextType.TRIGGER_EVENT) TextNoteBlockUpdateEvent event) {
// grab data from event then update
_changedTextNoteData = event.getTextNoteData();
_changedIndex = event.getIndex();
}
}
RecordableTextNote.java
Java class of RecordableTextNote component, extends SelectableTextNote and handle note block update.
package custom.zk.components.quicknote;
import java.util.List;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Events;
import custom.zk.components.quicknote.Data.TextNoteData;
import custom.zk.components.quicknote.event.TextNoteBlockUpdateEvent;
import custom.zk.components.quicknote.model.TextNoteModel;
/** RecordableTextNote, will receive and store which note block is changed from client side action
*
* Two new things:
*
* Convert request to an event by static method defined in TextNoteBlockUpdateEvent
* two benefits:
* 1. Do not need to write the code to retrieve data in service method
* 2. Can grab data from event easily in event listener since we can wrap data properly at first
*
* UiException: throw this exception to notify user something wrong,
* will alert at client side
*
* @author benbai123
*
*/
public class RecordableTextNote extends SelectableTextNote {
private static final long serialVersionUID = -4769807131469685854L;
static {
addClientEvent(SelectableTextNote.class, "onTextNoteBlockChanged", CE_IMPORTANT | CE_DUPLICATE_IGNORE | CE_NON_DEFERRABLE);
addClientEvent(SelectableTextNote.class, "onSelectedTextNoteBlockChanged", CE_IMPORTANT | CE_DUPLICATE_IGNORE | CE_NON_DEFERRABLE);
}
// getter, so can save data to vm
public TextNoteData getSelectedTextNoteData () {
int index = getSelectedTextNoteIndex();
if (index >= 0) {
return getTextNoteData(index);
}
return null;
}
public TextNoteData getTextNoteData (int index) {
List datas = getModel().getTextNoteData();
return (datas != null && datas.size() > index)?
(TextNoteData)datas.get(index) : null;
}
// process client event
public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
final String cmd = request.getCommand();
// any text note block changed
if (cmd.equals("onTextNoteBlockChanged")) {
TextNoteModel model = getModel();
if (model != null) {
TextNoteBlockUpdateEvent event = TextNoteBlockUpdateEvent.getTextNoteBlockUpdateEvent(cmd, this, request);
// created at client side
if (model.getTextNoteData().size() <= event.getIndex())
model.add(event.getTextNoteData());
else // already in model
model.update(event.getIndex(), event.getTextNoteData());
// post event to trigger listeners if any
Events.postEvent(event);
} else {
throw new UiException("Model is required !!");
}
// selected text note block changed
} else if (cmd.equals("onSelectedTextNoteBlockChanged")) {
TextNoteModel model = getModel();
if (model != null) {
TextNoteBlockUpdateEvent event = TextNoteBlockUpdateEvent.getTextNoteBlockUpdateEvent(cmd, this, request);
// simply post event to trigger listeners if any
Events.postEvent(event);
} else {
throw new UiException("Model is required !!");
}
} else
super.service(request, everError);
}
}
RecordableTextNote.js
Widget class of RecordableTextNote component, extends SelectableTextNote and handle onfocus/onblur events of textarea in text note block.
/**
* Widget class of RecordableTextNote component,
* extends custom.zk.components.quicknote.SelectableTextNote
*
* actually no new thing, just handle more events and do more
* works to make it recordable
*
*/
custom.zk.components.quicknote.RecordableTextNote = zk.$extends(custom.zk.components.quicknote.SelectableTextNote, {
_createNoteBlock: function (x, y) {
// call super
var noteBlock = this.$supers('_createNoteBlock', arguments),
textarea = noteBlock.firstChild,
wgt = this;
// add event listener for onfocus and onblur of textarea
textarea.onfocus = function () {
wgt.doTextNoteBlockFocus(textarea);
};
textarea.onblur = function () {
wgt.doTextNoteBlockBlur(textarea);
};
// define an object to hold attributes of text note block
// and store it at textarea
textarea.zkAttributes = {};
this.storeTextBlockAttributes(textarea);
return noteBlock;
},
// rewrite _renderNoteBlock to add _afterRenderNoteBlock
// at the tail
_renderNoteBlock: function (x, y, w, h, txt) {
var noteBlock = this._createNoteBlock(x, y),
textarea = noteBlock.firstChild;
jq(textarea).css({'width': w+'px',
'height': h+'px'});
textarea.innerHTML = txt;
this.$n().appendChild(noteBlock);
// add _afterRenderNoteBlock
this._afterRenderNoteBlock(noteBlock);
},
// called after _renderNoteBlock
_afterRenderNoteBlock: function (noteBlock) {
var idx = this._selectedTextNoteIndex;
if (idx >= 0
&& idx == this.getTextBlockIndex(noteBlock.firstChild))
jq(noteBlock).addClass(this.getZclass() + '-noteblock-selected');
// simply update textarea.zkAttributes
this.storeTextBlockAttributes(noteBlock.firstChild);
},
// store/update attributes of text note block
// to textarea.zkAttributes
storeTextBlockAttributes: function (textarea) {
var $textarea = jq(textarea),
$div = jq(textarea.parentNode),
zattr = textarea.zkAttributes;
// store current value
zattr.left = $div.css('left');
zattr.top = $div.css('top');
zattr.width = $textarea.css('width');
zattr.height = $textarea.css('height');
zattr.text = $textarea.val();
},
// called while onfocus of textarea in text note block
doTextNoteBlockFocus: function (textarea) {
// store changed attributes
// and fire event as needed
this.recordTextBlock(textarea);
},
// called while onblur of textarea in text note block
doTextNoteBlockBlur: function (textarea) {
this.recordTextBlock(textarea);
},
// update changed attributes and fire event as needed
recordTextBlock: function (textarea) {
// update attributes and fire event
// if attributes are changed
if (this.isTextNoteBlockChanged(textarea)) {
this.storeTextBlockAttributes(textarea);
this.fireOnTextNoteBlockChanged(textarea);
}
},
// check whether attributes of a text note block
// are changed
isTextNoteBlockChanged: function (textarea) {
var $textarea = jq(textarea),
$div = jq(textarea.parentNode),
zattr = textarea.zkAttributes;
// store current value
return zattr.left != $div.css('left')
|| zattr.top != $div.css('top')
|| zattr.width != $textarea.css('width')
|| zattr.height != $textarea.css('height')
|| zattr.text != $textarea.val();
},
// fire text note block changed event
fireOnTextNoteBlockChanged: function (textarea) {
var zattr = textarea.zkAttributes,
idx = this.getTextBlockIndex(textarea),
selected = jq('.' + this.getZclass() + '-noteblock-selected')[0];
// has attributes object and index exists
if (zattr
&& idx >= 0) {
// create data
var data = {index: idx,
left: parseInt(zattr.left),
top: parseInt(zattr.top),
width: parseInt(zattr.width),
height: parseInt(zattr.height),
text: zattr.text
};
// always fire onTextNoteBlockChanged
this.fire('onTextNoteBlockChanged', data);
// also fire onSelectedTextNoteBlockChanged if
// changed text note block is selected one
if (selected
&& this.getTextBlockIndex(selected.firstChild) == idx) {
this.fire('onSelectedTextNoteBlockChanged', data);
}
}
}
});
TextNoteBlockUpdateEvent.java
Event class that wrap data of text note block update event.
package custom.zk.components.quicknote.event;
import java.util.Map;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import custom.zk.components.quicknote.Data.TextNoteData;
/** TextNoteBlockUpdateEvent
*
* Wrap the data of au request from text note block changed
*
* One new thing: Extends org.zkoss.zk.ui.event.Event and call
* 'super(name, target);' constructor to make it
* working with ZK Event processing flow
*
* @author benbai123
*
*/
public class TextNoteBlockUpdateEvent extends Event {
private static final long serialVersionUID = 8137381810564543333L;
private int _index;
private TextNoteData _textNoteData;
public static TextNoteBlockUpdateEvent getTextNoteBlockUpdateEvent (String name, Component target, AuRequest request) {
// create and return event
return new TextNoteBlockUpdateEvent(name, target, request);
}
// construct event with given name, target and au request
@SuppressWarnings("rawtypes")
public TextNoteBlockUpdateEvent (String name, Component target, AuRequest request) {
super(name, target);
Map data = request.getData(); // get data map
_index = (Integer)data.get("index");
_textNoteData = new TextNoteData((Integer)data.get("left"),
(Integer)data.get("top"),
(Integer)data.get("width"),
(Integer)data.get("height"),
(String)data.get("text"));
}
public int getIndex () {
return _index;
}
public TextNoteData getTextNoteData () {
return _textNoteData;
}
}
zk.wpd
Define components under "custom.zk.components.quicknote"
* only the added part, not the full code
NOTE: more components will be added with other articles later
...
<widget name="RecordableTextNote" />
...
lang-addon.xml
Define all components in the project
* only the added part, not the full code
NOTE: more components will be added with other articles later
...
<!-- 6th, recordabletextnote component
extends selectabletextnote and fire event to server
to update server side data
-->
<component>
<component-name>recordabletextnote</component-name>
<extends>selectabletextnote</extends>
<component-class>custom.zk.components.quicknote.RecordableTextNote</component-class>
<widget-class>custom.zk.components.quicknote.RecordableTextNote</widget-class>
<annotation>
<annotation-name>ZKBIND</annotation-name>
<property-name>selectedTextNoteData</property-name>
<attribute>
<attribute-name>ACCESS</attribute-name>
<attribute-value>save</attribute-value>
</attribute>
<attribute>
<attribute-name>SAVE_EVENT</attribute-name>
<attribute-value>onSelectedTextNoteBlockChanged</attribute-value>
</attribute>
<attribute>
<attribute-name>LOAD_TYPE</attribute-name>
<attribute-value>custom.zk.components.quicknote.Data.TextNoteData</attribute-value>
</attribute>
</annotation>
</component>
...
References
MVVM Data Binding @save
http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Syntax/Data_Binding/@save
Download
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Development__Series/001_walkthrough/ZKQuickNote
recordabletextnote_component.swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/component_development_series/001_walkthrough/recordabletextnote_component.swf
Hi Benbai,
ReplyDeleteI need your help to create custom event onScroll to combo box in
http://forum.zkoss.org/question/105030/zk-create-custom-event-for-combo-box/
Hi, please refer to this sample
ReplyDeletehttp://zkfiddle.org/sample/29oah2u/3-Another-new-ZK-fiddle