Tuesday, April 30, 2013
ZK MVVM: Show Validation Message in Modal Window
Simple Note
Introduction
This article describe how to display validation message in a modal window, this is a custom way to simulate the EE only feature so just simple note.
NOTE: It is better to buy a ZK EE License and use the official feature for better stability.
The Result
View demo on line
http://screencast.com/t/zuHrho5u3
The Program
MVVM_Show_Validation_Message_in_Modal_Window.zul
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/WebContent/MVVM_Show_Validation_Message_in_Modal_Window.zul
ShowMessageInModalWinTestVM.java
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/src/blog/ben/test/mvvm/formbinding/ShowMessageInModalWinTestVM.java
Reference
Related thread on forum
http://forum.zkoss.org/question/86565/mvvm-form-binding-validator/
AbstractValidator.java
https://github.com/zkoss/zk/blob/v6.5.1.1/zkbind/src/org/zkoss/bind/validator/AbstractValidator.java#L67
Friday, April 26, 2013
ZK MVVM: Form Binding with Validator
Introduction
This article describe how to use Validator with Form Binding in ZK MVVM.
The Result
View demo on line
http://screencast.com/t/caPtmte73J
Pre-request
ZK MVVM: Form Binding
http://ben-bai.blogspot.tw/2013/04/zk-mvvm-form-binding.html
The Program
MVVM_FormBinding.zul
The inner div bind to vm.person, i.e., use vm.person as a form object. It will save form content before savePerson command and use vm.formValidator as the validator to do validation of form fields.
The validator will store error message in validationMessages map with the specified id 'vmsgs'
<zk>
<!--
tested with ZK 6.0.2
validationMessages="@id('vmsgs')"
means all validation message will be stored in a map
that mapped by the id 'vmsgs'
i.e., you can get messages from that map by
@bind(vmsgs['keyOfMessageContent'])
-->
<div apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('blog.ben.test.mvvm.formbinding.FormBindingWithValidatorTestVM')"
validationMessages="@id('vmsgs')">
<!--
@validator(vm.formValidator)
do validation before save form with this validator
-->
<div form="@id('fx') @load(vm.person) @save(vm.person, before='savePerson') @validator(vm.formValidator)">
<vlayout>
<hlayout>
<label value="First Name" />
<textbox value="@bind(fx.firstName)" />
<!--
@bind(vmsgs['firstNameContentError'])
show error message of the key 'firstNameContentError' in
vmsgs map after form validation as needed
-->
<label value="@bind(vmsgs['firstNameContentError'])" style="color: red;" />
<label value="@bind(vmsgs['firstNameError'])" style="color: red;" />
</hlayout>
<hlayout>
<label value="Last Name" />
<textbox value="@bind(fx.lastName)" />
<label value="@bind(vmsgs['lastNameContentError'])" style="color: red;" />
<label value="@bind(vmsgs['lastNameError'])" style="color: red;" />
</hlayout>
<hlayout>
<label value="Age" />
<intbox value="@bind(fx.age)" />
<label value="@bind(vmsgs['ageContentError'])" style="color: red;" />
<label value="@bind(vmsgs['ageTooSmallError'])" style="color: red;" />
<label value="@bind(vmsgs['ageTooLargeError'])" style="color: red;" />
</hlayout>
</vlayout>
</div>
<button label="save" onClick="@command('savePerson')" />
<label value="@load(vm.personContent)" />
</div>
</zk>
FormBindingTestVM.java
Simple vm, contains getter, command and a validator
package blog.ben.test.mvvm.formbinding;
import org.zkoss.bind.ValidationContext;
import org.zkoss.bind.Validator;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.bind.validator.AbstractValidator;
/**
* tested with ZK 6.0.2
* @author benbai123
*
*/
public class FormBindingWithValidatorTestVM {
private Person _person;
public Person getPerson () {
if (_person == null) {
_person = new Person("Ben", "Bai", 123); // fake
}
return _person;
}
public String getPersonContent () {
return _person.getFirstName() + " " + _person.getLastName() + ", age = " + _person.getAge();
}
/**
* empty function as a command for
* save in form binding and update personContent
*/
@Command
@NotifyChange("personContent")
public void savePerson () {
}
/**
* get the validator that will do validation while save
* @return
*/
public Validator getFormValidator(){
return new AbstractValidator() {
public void validate(ValidationContext ctx) {
// get value from form context,
// ctx.getProperties("firstName")[0].getValue() will
// get the value that bind with @bind(fx.firstName)
// in zul page
String firstName = (String)ctx.getProperties("firstName")[0].getValue();
String lastName = (String)ctx.getProperties("lastName")[0].getValue();
Integer age = (Integer)ctx.getProperties("age")[0].getValue();
if (firstName == null || firstName.isEmpty()) {
// put error message into validationMessages map
addInvalidMessage(ctx, "firstNameContentError", "firstName is required ");
} else if (firstName.length() < 3) {
addInvalidMessage(ctx, "firstNameError", "firstName at least 3 chars ");
}
if (lastName == null || lastName.isEmpty()) {
addInvalidMessage(ctx, "lastNameContentError", "lastName is required");
} else if (lastName.length() < 3) {
addInvalidMessage(ctx, "lastNameError", "lastName at least 3 chars ");
}
if (age == null) {
addInvalidMessage(ctx, "ageContentError", "age is required");
} else {
if (age < 0) {
addInvalidMessage(ctx, "ageTooSmallError", "age should not be negative");
}
if (age > 130) {
addInvalidMessage(ctx, "ageTooLargeError", "age should smaller than 130");
}
}
}
};
}
}
Person.java
Simple pojo contains data of a person
package blog.ben.test.mvvm.formbinding;
public class Person {
String _firstName;
String _lastName;
Integer _age;
public Person (String firstName, String lastName, Integer age) {
_firstName = firstName;
_lastName = lastName;
_age = age;
}
public void setFirstName (String firstName) {
_firstName = firstName;
}
public void setLastName (String lastName) {
_lastName = lastName;
}
public void setAge (int age) {
_age = age;
}
public String getFirstName () {
return _firstName;
}
public String getLastName () {
return _lastName;
}
public Integer getAge () {
return _age;
}
}
Reference
MVVM > Data Binding > Validator
http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Data_Binding/Validator
Download
MVVM_FormBinding_with_Validator.zul
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/WebContent/MVVM_FormBinding_with_Validator.zul
FormBindingWithValidatorTestVM.java
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/src/blog/ben/test/mvvm/formbinding/FormBindingWithValidatorTestVM.java
Person.java
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/src/blog/ben/test/mvvm/formbinding/Person.java
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Pattern/MVVM/MVVM_FormBinding_with_Validator.swf
ZK MVVM: Form Binding
Introduction
This article describe how to do Form Binding in ZK MVVM.
The Result
View demo on line
http://screencast.com/t/yAE1f4wTALNr
Pre-request
ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html
The Program
MVVM_FormBinding.zul
The inner div bind to vm.person, i.e., use vm.person as a form object. It will save form content before savePerson command.
<zk>
<!--
tested with ZK 6.0.2
-->
<div apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('blog.ben.test.mvvm.formbinding.FormBindingTestVM')">
<!-- form binding,
@id('fx') @load(vm.person)
here the id (fx) is mapping to vm.person
@save(vm.person, before='savePerson')"
save to vm.person before savePerson command
the before/after in @save is required,
you should always indicate when (before/after a command)
to save form content
save to vm.person means
call vm.getPerson().setFirstName() to save firstName
-->
<div form="@id('fx') @load(vm.person) @save(vm.person, before='savePerson')">
<vlayout>
<!-- fx.firstName, i.e., vm.person.firstName -->
<hlayout>
<label value="First Name" />
<textbox value="@bind(fx.firstName)" />
</hlayout>
<hlayout>
<label value="Last Name" />
<textbox value="@bind(fx.lastName)" />
</hlayout>
<hlayout>
<label value="Age" />
<intbox value="@bind(fx.age)" />
</hlayout>
</vlayout>
</div>
<!-- click this button will execute savePerson() method in vm
and trigger save action of the form above -->
<button label="save" onClick="@command('savePerson')" />
<!-- show content of person,
will be updated after savePerson command -->
<label value="@load(vm.personContent)" />
</div>
</zk>
FormBindingTestVM.java
Simple vm, contains getter and command
package blog.ben.test.mvvm.formbinding;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
/**
* tested with ZK 6.0.2
* @author benbai123
*
*/
public class FormBindingTestVM {
private Person _person;
public Person getPerson () {
if (_person == null) {
_person = new Person("Ben", "Bai", 123); // fake
}
return _person;
}
public String getPersonContent () {
return _person.getFirstName() + " " + _person.getLastName() + ", age = " + _person.getAge();
}
/**
* empty function as a command for
* save in form binding and update personContent
*/
@Command
@NotifyChange("personContent")
public void savePerson () {
}
}
Person.java
Simple pojo contains data of a person
package blog.ben.test.mvvm.formbinding;
public class Person {
String _firstName;
String _lastName;
Integer _age;
public Person (String firstName, String lastName, Integer age) {
_firstName = firstName;
_lastName = lastName;
_age = age;
}
public void setFirstName (String firstName) {
_firstName = firstName;
}
public void setLastName (String lastName) {
_lastName = lastName;
}
public void setAge (int age) {
_age = age;
}
public String getFirstName () {
return _firstName;
}
public String getLastName () {
return _lastName;
}
public Integer getAge () {
return _age;
}
}
Reference
MVVM > Data Binding > Form Binding
http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/MVVM/Data%20Binding/Form%20Binding
Download
MVVM_FormBinding.zul
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/WebContent/MVVM_FormBinding.zul
FormBindingTestVM.java
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/src/blog/ben/test/mvvm/formbinding/FormBindingTestVM.java
Person.java
https://github.com/benbai123/ZK_Practice/blob/master/Pattern/MVVM/AdvancedMVVM/src/blog/ben/test/mvvm/formbinding/Person.java
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Pattern/MVVM/MVVM_FormBinding.swf
Notify ZUL from HTML
Simpole Note
This article describe how to notify zul from a html button click via ajax request, this obviously not the suggest pattern so just simple note.
Program
notify_zul_from_html.html
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/notify_zul_from_html.html
notify_zul_from_html.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/notify_zul_from_html.zul
notify_zul_from_html__notify.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/notify_zul_from_html__notify.zul
Online demo
http://screencast.com/t/IIg4Eg1XB
Thursday, April 25, 2013
ZK Calendar as Week Picker
Introduction
This article describe how to customize the small calendar as a week picker.
Pre-request
Please check Client Side Programming and The use, apply Attribute at References section before reading this article
The Program
calendar_as_week_picker.zul
<zk xmlns:w="client">
<style>
.custom-selected-node {
background-color: #99FF99 !important;
}
</style>
<vlayout>
<label value="selected dates" />
<textbox rows="7" id="tbx" width="300px" />
</vlayout>
<calendar id="cal" use="test.custom.component.WeekPicker">
<attribute w:name="_markCal"><![CDATA[
function (opts) {
// clear old custom-selected-node
jq('.custom-selected-node').each(function () {
jq(this).removeClass('custom-selected-node');
});
this.$_markCal(opts);
if (this._view == 'day') {
// target: current focused date (td)
// parent: tr
var target = jq('.z-calendar-seld')[0],
parent = target.parentNode,
node = parent.firstChild,
beforeCnt = 0,
found;
// loop through each td
while (node) {
// add selected style
jq(node).addClass('custom-selected-node');
if (node == target) {
found = true;
} else if (!found) {
// count nodes before target
beforeCnt++;
}
node = node.nextSibling;
}
// fire event to server
this.fire('onCustomSelect', {bcnt: beforeCnt});
}
}
]]></attribute>
<attribute name="onCustomSelect"><![CDATA[
List dates = self.getSelectedDates();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd / MM / yyyy");
String value = "";
for (int i = 0; i < dates.size(); i++) {
value = value + sdf.format((Date)dates.get(i)) + "\n";
}
tbx.setValue(value);
]]></attribute>
</calendar>
</zk>
WeekPicker.java
package test.custom.component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Calendar;
public class WeekPicker extends Calendar {
private static final long serialVersionUID = 7513083343273393743L;
private List<Date> _selectedDates;
static {
addClientEvent(WeekPicker.class, "onCustomSelect", CE_IMPORTANT|CE_REPEAT_IGNORE|CE_NON_DEFERRABLE);
}
public List<Date> getSelectedDates () {
return _selectedDates;
}
private void updateSelectedDates (int beforeCnt) {
_selectedDates = new ArrayList<Date>();
java.util.Calendar cal = java.util.Calendar.getInstance();
// current selected date
cal.setTime(getValue());
// move to first day of the week
cal.add(java.util.Calendar.DATE, (-1*beforeCnt));
// add seven days to _selectedDates
for (int i = 0; i < 7; i++) {
_selectedDates.add(cal.getTime());
cal.add(java.util.Calendar.DATE, 1);
}
}
public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
final String cmd = request.getCommand();
if (cmd.equals("onCustomSelect")) {
final Map<String, Object> data = request.getData();
// get node count before selected date
final Integer beforeCnt = (Integer)data.get("bcnt");
// update selected dates
updateSelectedDates(beforeCnt);
// post event
Events.postEvent("onCustomSelect", this, null);
} else {
super.service(request, everError);
}
}
}
The Result
View demo on line
http://screencast.com/t/N35NM2T98yY
References
Calendar.js
https://github.com/zkoss/zk/blob/master/zul/src/archive/web/js/zul/db/Calendar.js
Calendar.java
https://github.com/zkoss/zk/blob/master/zul/src/org/zkoss/zul/Calendar.java
Client Side Programming
http://books.zkoss.org/wiki/Small_Talks/2010/April/Client_Side_Programming
The use, apply Attribute
http://books.zkoss.org/wiki/ZK_Developer%27s_Guide/Fundamental_ZK/ZK_User_Interface_Markup_Language/ZK_Attributes#The_use.2C_apply_Attribute
Download
calendar_as_week_picker.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/calendar_as_week_picker.zul
WeekPicker.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/custom/component/WeekPicker.java
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/calendar_as_week_picker.swf
Sunday, April 21, 2013
ZK Pivottable: Filter Row or Column Header Value
Introduction
This article describe how to use the value of row or column field as filter to filter raw data.
Pre-request
Pass Event to Other Component
http://ben-bai.blogspot.tw/2012/12/pass-event-to-other-component.html
ZK Pivottable: Display Data in ZK Pivottable
http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html
ZK Pivottable: Sync the Open Status of Pivotmodel
http://ben-bai.blogspot.tw/2013/02/zk-pivottable-sync-open-status-of.html
ZK Pivottable: Sync Structure of Pivot Model
http://ben-bai.blogspot.tw/2013/02/zk-pivottable-sync-structure-of-pivot.html
ZK Pivottable: Get Distinct Values of Field
http://ben-bai.blogspot.tw/2013/03/zk-pivottable-get-distinct-values-of.html
The Program
index.zul
A pivottable and a filter
<zk>
<!-- Tested with ZK 6.0.1 CE and ZK Pivottable 2.0.0 -->
<!-- window, apply a SelectorComposer -->
<window id="win" xmlns:w="client"
apply="test.TestComposer">
<!-- pivottable, get model from window's composer -->
<pivottable id="pivottable" model="${win$composer.pivotModel}" />
<div style="margin-top: 10px;">
Filter info:
<label id="lb" />
</div>
<!-- filter list -->
<div id="filter" />
</window>
</zk>
TestComposer.java
Provide model, listen to onPivotPopup to update filter list, listen to onFilterChanged to update pivottable with filtered data.
package test;
import java.util.List;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.event.PivotUIEvent;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Div;
import org.zkoss.zul.Label;
/**
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
@SuppressWarnings("rawtypes")
public class TestComposer extends SelectorComposer {
private static final long serialVersionUID = -8249566421884806620L;
@Wire
Pivottable pivottable;
@Wire
Div filter; // div that will contain filter list
@Wire
Label lb; // filter info
// pivot model with the 'whole' raw data
private TabularPivotModel _pivotModel;
// model provider, provide the columns, raw data and pivot model
private PivotModelProvider _modelProvider = new PivotModelProvider();
// handler to do the works of filter
// NOTE: Renew it if changed to a complete different model
private FilterHandler _filterHandler = new FilterHandler();
/**
* Get pivottable's model
* @return TabularPivotModel the pivottable's model
* @throws Exception
*/
public TabularPivotModel getPivotModel () throws Exception {
if (_pivotModel == null) {
_pivotModel = _modelProvider.getPivotModel();
}
return PVTUtils.cloneModelWithData(_pivotModel, getFilteredData());
}
/**
* update the selected field for filter
* @param e
*/
@Listen("onPivotPopup = #pivottable")
public void updateFilterIndex (PivotUIEvent e) {
if (e.getRowContext() != null
&& e.getColumnContext() == null) {
// clicked on row field
updateFilter(_pivotModel.getRowFields()[e.getRowContext().getNode().getDepth()-1]);
} else if (e.getRowContext() == null
&& e.getColumnContext() != null) {
// clicked on column field
updateFilter(_pivotModel.getColumnFields()[e.getColumnContext().getNode().getDepth()-1]);
}
}
/**
* called while filter is changed
* update filter value of selected field
* @param event
*/
@Listen("onFilterChanged = #pivottable")
public void updateLimit (FilterChangedEvent event) {
_filterHandler.updateLimit(event.getFieldName(), event.getValue(), event.isChecked());
updatePivottable();
}
/**
* update the filter list
* @param field the field to update filter list
*/
private void updateFilter (TabularPivotField field) {
_filterHandler.updateFilter(pivottable,
field,
_modelProvider.getColumns(),
getRawData(),
filter);
// update field info label
lb.setValue("field type: " + field.getType() + ", field name: " + field.getFieldName());
}
/**
* update pivottable with filtered pivot model
*/
private void updatePivottable () {
// store current structure at first
PVTUtils.syncModelStructure((TabularPivotModel)pivottable.getModel(), _pivotModel);
TabularPivotModel filteredModel = PVTUtils.cloneModelWithData(_pivotModel, getFilteredData());
pivottable.setModel(filteredModel);
}
/**
* get the filtered data
* @return
*/
private Iterable<List<Object>> getFilteredData () {
return _filterHandler.filterData(_pivotModel, _modelProvider.getColumns(), getRawData());
}
/**
* get complete raw data
* @return
*/
@SuppressWarnings("unchecked")
private Iterable<List<Object>> getRawData () {
return (Iterable<List<Object>>)_pivotModel.getRawData();
}
}
PivotModelProvider.java
Provide base model, raw data and columns.
package test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
public class PivotModelProvider {
// pivot model with the 'whole' raw data
private TabularPivotModel _pivotModel;
/**
* Get pivottable's model
* @return TabularPivotModel the pivottable's model
* @throws Exception
*/
public TabularPivotModel getPivotModel () {
if (_pivotModel == null) {
_pivotModel = new TabularPivotModel(getData(), getColumns());
// assign rows, the order matches to the level of row node field
_pivotModel.setFieldType("Row_01", PivotField.Type.ROW);
_pivotModel.setFieldType("Row_02", PivotField.Type.ROW);
_pivotModel.setFieldType("Row_03", PivotField.Type.ROW);
// assign columns, the order matches to the level of column node field
_pivotModel.setFieldType("Column_01", PivotField.Type.COLUMN);
_pivotModel.setFieldType("Column_02", PivotField.Type.COLUMN);
// assign datas, the order matches to the order of data field
_pivotModel.setFieldType("Data_01", PivotField.Type.DATA);
_pivotModel.setFieldType("Data_02", PivotField.Type.DATA);
_pivotModel.setFieldType("Data_03", PivotField.Type.DATA);
}
return _pivotModel;
}
/**
* prepare columns name for pivottable's model
* @return
*/
public List<String> getColumns() {
return Arrays.asList(new String[]{
"Row_01", "Row_02", "Row_03",
"Column_01", "Column_02",
"Data_01", "Data_02", "Data_03"
});
}
/**
* prepare the data for pivottable's model
* The order of object put into data list matches
* the order of column name's order
* @return
* @throws Exception
*/
private List<List<Object>> getData() {
List<List<Object>> result = new ArrayList<List<Object>>();
Random r = new Random();
for (int i = 0; i < 100; i++) {
List<Object> data = new ArrayList<Object>();
data.add("Row_01 - " + (r.nextInt(5) + 1));
data.add("Row_02 - " + (r.nextInt(5) + 1));
data.add("Row_03 - " + (r.nextInt(5) + 1));
data.add("Column_01 - " + (r.nextInt(5) + 1));
data.add("Column_02 - " + (r.nextInt(5) + 1));
data.add(r.nextInt(10000));
data.add(r.nextDouble() * 10000.0);
data.add(r.nextInt(100));
result.add(data);
}
return result;
}
}
FilterHandler.java
Handle filter task
package test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
import org.zkoss.pivot.util.PivotModels;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Div;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
/**
* Class to handle tasks with respect to filter
*
* NOTE: A FilterHandler is rely on a specific set of fields and
* should be renew after the fields of pivot model are changed
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
public class FilterHandler {
/**
* map that contains all Limit object
* use field name as the key
*
*/
private Map<String, Limit> _fieldsLimitsMap = new HashMap<String, Limit>();
/**
* map that contains all field index
* use field name as the key
*/
private Map<String, Integer> _indexMap = new HashMap<String, Integer>();;
@SuppressWarnings("rawtypes")
public void updateFilter (Pivottable pivottable, TabularPivotField field, List<String> columns, Iterable<List<Object>> rawData, Div filter) {
List distinctValues = PVTUtils.getDistinctValues(rawData, columns, getFieldIndex(columns, field.getFieldName()));
updateFilterList(pivottable, distinctValues, field, filter);
}
/**
* update the limited values of a field
* @param fieldName the name of the field to update
* @param value the value to update
* @param accept whether accept the value above
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void updateLimit (String fieldName, Object value, boolean accept) {
// try find Limit object
Limit limit = _fieldsLimitsMap.get(fieldName);
// create a new one if not found
if (limit == null) {
limit = new Limit(fieldName, new HashSet());
_fieldsLimitsMap.put(fieldName, limit);
}
// remove value from limited values if the value is accepted
// add value to limited values if the value is not accepted
if (accept) {
limit.getLimitedValues().remove(value);
} else {
limit.getLimitedValues().add(value);
}
}
/**
* filter data by limits
* @param model pivot model, get all row/column fields from it
* @param rawData raw data, to filter it
* @return the filtered raw data
*/
public Iterable<List<Object>> filterData (TabularPivotModel model, final List<String> columns, Iterable<List<Object>> rawData) {
// field name of row/column fields
final List<String> rcColumns = new ArrayList<String>();
// keep a final object of limit map so can be used in inner class
final Map<String, Limit> limits = _fieldsLimitsMap;
// add all row/column field names
for (TabularPivotField tpf : model.getRowFields()) {
rcColumns.add(tpf.getFieldName());
}
for (TabularPivotField tpf : model.getColumnFields()) {
rcColumns.add(tpf.getFieldName());
}
return PivotModels.filter(rawData, new PivotModels.Filter<List<Object>>() {
public boolean keep(List<Object> row) {
// for each row/column (field names)
for (String s : rcColumns) {
// find Limit object
Limit l = limits.get(s);
if (l != null) {
// find value index
int index = getFieldIndex(columns, s);
// get value
Object value = row.get(index);
// do not keep the value if the value is in limit
if (l.getLimitedValues().contains(value)) {
return false;
}
}
}
return true;
}
});
}
/**
* create the filter list
* @param pivottable pivottable, to fire onFilterChanged event as needed
* @param distinctValues all different values, used to construct the filter list
* @param field pivot field, used to get fieldName
* @param filter the div component specified in index.zul
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void updateFilterList (final Pivottable pivottable, List distinctValues, final TabularPivotField field, Div filter) {
// clear old children
filter.getChildren().clear();
Listbox lb = new Listbox();
lb.setWidth("200px");
final String fieldName = field.getFieldName();
Limit limit = _fieldsLimitsMap.get(fieldName);
// for each value of this field
for (final Object value : distinctValues) {
Listitem li = new Listitem();
Listcell lc = new Listcell();
Checkbox cb = new Checkbox(value.toString());
// update checked status of checkbox according to
// whether this value is a limited value
if (limit != null && limit.getLimitedValues().contains(value)) {
cb.setChecked(false);
} else {
cb.setChecked(true);
}
cb.setParent(lc);
lc.setParent(li);
li.setParent(lb);
// add onCheck event listener to checkbox
cb.addEventListener("onCheck", new EventListener() {
public void onEvent (Event event) {
// fire event to pivottable with
// the information of changed filter attributes
Events.postEvent(new FilterChangedEvent(pivottable, fieldName, value, ((CheckEvent)event).isChecked() ));
}
});
}
// add listbox to div
lb.setParent(filter);
}
/**
* get the index in raw data of a field
* @param columns columns used in pivot model
* @param fieldName name of the field to search index
* @return int the found index
*/
private int getFieldIndex (List<String> columns, String fieldName) {
// search it from index map at first
if (!_indexMap.containsKey(fieldName)) {
int index = PVTUtils.getFieldIndex(columns, fieldName);
// store it to index map
_indexMap.put(fieldName, index);
}
return _indexMap.get(fieldName);
}
}
PVTUtils.java
Utils of pivottable
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
/**
* utilities for pivottable
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
public class PVTUtils {
/**
* Create a new pivot model based on
* current pivot model and new data
* @param model
* @param newData
* @return
*/
public static TabularPivotModel cloneModelWithData (TabularPivotModel model, Iterable<List<Object>>newData) {
TabularPivotField[] fields = model.getFields();
// get columns from old model
List<String> columns = new ArrayList<String>();
// set field
for (TabularPivotField tpf : fields) {
columns.add(tpf.getFieldName());
}
TabularPivotModel newModel = new TabularPivotModel(newData, columns);
PVTUtils.syncModelStructure(model, newModel);
return newModel;
}
/**
* get the index in raw data of a field
* @param columns columns used in pivot model
* @param fieldName name of the field to search index
* @return int the found index
*/
public static int getFieldIndex (List<String> columns, String fieldName) {
int index = -1;
// search field name in columns
for (int i = 0; i < columns.size(); i++) {
if (columns.get(i).equals(fieldName)) {
index = i;
break;
}
}
return index;
}
/**
* get all different values of a field
* @param rawData the raw data to get different values from
* @param columns all columns in pivot model
* @param index the index to get value from list
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static List getDistinctValues (Iterable<List<Object>> rawData, List<String> columns, int index) {
// set used to hold distinct values
Set s = new HashSet();
// result list
List result = new ArrayList();
if (index == -1) return result;
// add all value to set directly
for (List<Object> data : rawData) {
s.add(data.get(index));
}
// copy to list then sort the list
for (Object o : s) {
result.add(o);
}
Collections.sort(result);
return result;
}
/**
* sync the structure of pivot model
*
* @param model the base pivot model
* @param modelTwo the pivot model to adjust its structure
*/
public static void syncModelStructure (TabularPivotModel model, TabularPivotModel modelTwo) {
syncFields(model.getRowFields(), modelTwo);
syncFields(model.getColumnFields(), modelTwo);
syncFields(model.getDataFields(), modelTwo);
syncFields(model.getFields(PivotField.Type.UNUSED), modelTwo);
syncOpenStatus(model.getRowHeaderTree().getRoot(), modelTwo.getRowHeaderTree().getRoot(), false);
syncOpenStatus(model.getColumnHeaderTree().getRoot(), modelTwo.getColumnHeaderTree().getRoot(), false);
}
/**
* sync pivot fields of pivot model
* @param fields the base fields
* @param model the pivot model to adjust its fields
*/
private static void syncFields (TabularPivotField[] fields, TabularPivotModel model) {
for (TabularPivotField f : fields) {
model.setFieldType(f.getFieldName(), f.getType());
PivotField field = model.getField(f.getFieldName());
model.setFieldSubtotals(field, f.getSubtotals());
model.setFieldKeyComparator(field, f.getComparator());
}
}
/**
* Synchronize the open status of two pivot header trees
*
* @param root the root of the base pivot header tree (or its sub trees)
* @param rootTwo the root of the pivot header tree (or its sub trees) to sync
* @param checkAll whether sync whole tree, <br>
* true: sync whole tree, put every node of base pivot header tree into open list to sync<br>
* false: sync only current view, only put the displayed node into open list to sync
*/
private static void syncOpenStatus (PivotHeaderNode root, PivotHeaderNode rootTwo, boolean checkAll) {
Map<Object, PivotHeaderNode> originalOpenMap = new HashMap<Object, PivotHeaderNode>();
// sync displayed node only if not checkAll
// so do not need to scan whole header tree
for (PivotHeaderNode node : root.getChildren()) {
// checkAll: sync each node
// !checkAll: sync displayed node
if (checkAll
|| (node.getDepth() == 1 || node.getParent().isOpen())) {
originalOpenMap.put(node.getKey(), node);
}
}
// for each node in children of rootTwo
for (PivotHeaderNode newNode : rootTwo.getChildren()) {
PivotHeaderNode node = originalOpenMap.get(newNode.getKey());
if (node != null) {
newNode.setOpen(node.isOpen());
// recursively sync sub trees
syncOpenStatus(node, newNode, checkAll);
}
}
}
}
Limit.java
Contains the restricted values of a pivot field
package test;
import java.util.HashSet;
import java.util.Set;
/**
* Class for hold limited values of a field
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*
*/
public class Limit {
// the name to represent a specific field
private String _fieldName;
// the limited values
private Set<Object> _limitedValues;
public Limit (String fieldName, Set<Object> limitedValues) {
_fieldName = fieldName;
_limitedValues = limitedValues;
if (_limitedValues == null) {
_limitedValues = new HashSet<Object>();
}
}
public String getFieldName () {
return _fieldName;
}
public Set<Object> getLimitedValues () {
return _limitedValues;
}
}
FilterChangedEvent.java
Event that will be fired when the filter is changed.
package test;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
/**
* Event used to pass the information of updated filter
*
* Event name is "onFilterChanged"
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*
*/
public class FilterChangedEvent extends Event {
private static final long serialVersionUID = 5055917746546499563L;
/**
* whether a value is checked in filter list,
* true: checked (denotes this value is allowed)
* false: not checked (denotes this value is not allowed)
*/
private boolean _checked = true;
/**
* the updated filter value
*/
private Object _value;
/**
* the field related to the updated filter
*/
private String _fieldName;
// constructor,
public FilterChangedEvent (Component target, String fieldName, Object value, boolean checked) {
super("onFilterChanged", target);
_checked = checked;
_fieldName = fieldName;
_value = value;
}
public String getFieldName () {
return _fieldName;
}
public Object getValue () {
return _value;
}
public boolean isChecked () {
return _checked;
}
}
Reference
Filtering input data
http://books.zkoss.org/wiki/ZK_Pivottable_Essentials/Working_With_Pivottable/Prepare_Data#Filtering_input_data
The Result
View demo on line
http://screencast.com/t/XO77z12jf
Download
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/FilterRowColumnHeader
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/addon/pvt_filter_row_column_header.swf
Friday, April 5, 2013
ZK Macro Component: Using Macro Component to Create a Keypad Component
Introduction
This article describe how to create a keypad component by ZK macro component.
Specification
A keypad, constructs buttons in a popup based on the given charSeq, and update the given input element while button clicked.
charSeq:
[break] denotes make a new line for remaining buttons
[Del] denotes a functional button that will perform delete action
[CapsLock] denotes a functional button works as the CapsLock on common keybord
Note: To display '[' button, you should wrap it by [] (i.e., [[]) since it is a special char in keypad component.
inp:
The input element to work with.
open:
Open/Close the keypad.
The Result
View demo on line
http://screencast.com/t/LrrCTySFv6q
The Program
keypad_test.zul
Test page to test keypad component, define keypad component at the top then using it as a component.
<!-- define macro component -->
<?component name="keypad" macroURI="/folders/macrocomponents/keypad/keypad.zul"
class="test.marcro.component.keypad.Keypad"?>
<zk>
<div apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('test.marcro.component.keypad.KeypadVM')"
style="margin: 50px;">
<hlayout>
<textbox onFocus="@command('focusInput')" value="@bind(vm.txtValue)" />
<label value="@load(vm.txtValue)" />
</hlayout>
<hlayout>
<intbox onFocus="@command('focusInput')" value="@bind(vm.intValue)" />
<label value="@load(vm.intValue)" />
</hlayout>
<hlayout>
<doublebox onFocus="@command('focusInput')" value="@bind(vm.doubleValue)" />
<label value="@load(vm.doubleValue)" />
</hlayout>
<!-- use macro component -->
<keypad charSeq="@load(vm.charSeq)" inp="@load(vm.inp)"
open="@bind(vm.open)" />
</div>
</zk>
KeypadVM.java
VM to test keypad component, contains some setter/getter and process focus event to update status/properties.
package test.marcro.component.keypad;
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 org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zul.Doublebox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.impl.InputElement;
public class KeypadVM {
// chars
private String _charSeq;
private InputElement _inp;
private boolean _open;
private String _txtValue;
private int _intValue;
private double _doubleValue;
public String getCharSeq () {
return _charSeq;
}
public InputElement getInp () {
return _inp;
}
public boolean isOpen () {
return _open;
}
public void setTxtValue (String txtValue) {
_txtValue = (txtValue == null? "" : txtValue);
}
public String getTxtValue () {
return _txtValue;
}
public void setIntValue (Integer intValue) {
_intValue = (intValue == null? 0 : intValue);
}
public Integer getIntValue () {
return _intValue;
}
public void setDoubleValue (Double doubleValue) {
_doubleValue = (doubleValue == null? 0.0 : doubleValue);
}
public Double getDoubleValue () {
return _doubleValue;
}
@Command
@NotifyChange ({"charSeq", "inp", "open"})
public void focusInput (@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
Component comp = event.getTarget();
if (comp instanceof InputElement) {
if (comp instanceof Intbox) {
_charSeq = "123[break]456[break]789[break]0[Del]";
} else if (comp instanceof Doublebox) {
_charSeq = "123[break]456[break]789[break].0[Del]";
} else { // textbox
// note: wrap '[' by '[' and ']'
_charSeq = "1234567890-=[break]QWERTYUIOP[[]]\\[break]ASDFGHJKL;'[CapsLock][break]ZXCVBNM,./[Del]";
}
_inp = (InputElement)comp;
_open = true;
}
}
}
keypad.zul
This is the zul page for keypad component, define several custom javascript function with a popup component.
<zk xmlns:w="client">
<script><![CDATA[
function updateCursorRange (iwgt) {
var inp = iwgt.getInputNode(), // input element
range = zk(inp).getSelectionRange(), // get selected range
start = range[0],
end = range[1];
// store start/end in input widget
iwgt.cursorStartPosition = start;
iwgt.cursorEndPosition = end;
}
function getCursorRange (iwgt) {
// get the stored start/end
var start = iwgt.cursorStartPosition, // selected range
end = iwgt.cursorEndPosition,
range = [];
// default 0 if undefined
range.push(start? start : 0);
range.push(end? end : 0);
return range;
}
]]></script>
<popup id="pp">
<attribute w:name="open"><![CDATA[
function (ref, offset, position, opts) {
// keep the related input widget
var iwgt = zk.Widget.$(ref);
this.currentInp = iwgt;
// override doKeyUp_ and doMouseUp_ to update selected range
// override fire to reduce ajax call
this.overrideInp(iwgt);
// update selected range at first
// or intbox/doublebox will put char at wrong position
// since the first mouseup might not the overridden one
updateCursorRange(iwgt);
if (this.capsLockEnabled)
this.switchCapsLock();
// call original function
this.$open(ref, offset, position, opts);
}
]]></attribute>
<attribute w:name="executeDelete"><![CDATA[
function () {
var iwgt = this.currentInp, // input widget
range = getCursorRange(iwgt),
start = range[0], // selected range, never undefined
end = range[1],
value = iwgt.getInputNode().value,
len = value? value.length : 0;
// nothing selected
if (start == end) {
if (len >= (end + 1)) {
// to delete one char after cursor
end++;
iwgt.cursorEndPosition = end;
} else if (len > 0) {
// to delete one char before cursor
start--;
iwgt.cursorStartPosition = start;
}
}
// do nothing if no selection
if (start != end) {
// replace selected range by ''
// (i.e., delete selected range)
this.replaceRange (start, end, '', iwgt);
}
}
]]></attribute>
<attribute w:name="executeInsert"><![CDATA[
function (str) {
var iwgt = this.currentInp, // input widget
range = getCursorRange(iwgt),
start = range[0], // selected range, never undefined
end = range[1];
// replace selected range by str
this.replaceRange (start, end, str, iwgt);
}
]]></attribute>
<attribute w:name="replaceRange"><![CDATA[
function (start, end, replacement, iwgt) {
iwgt.updatingByKeypad = true;
var inp = iwgt.getInputNode(), // dom input element
value = inp.value, // current value
len,
doubleDotTail;
// snice doublebox will remov
// '.' at the tail automatically,
// plus one '1' and select it automatically
// to keep the '.' and the selected '1'
// will be replaced by next input
if (iwgt.$instanceof(zul.inp.Doublebox)) {
if (end == value.length
&& replacement == '.') {
doubleDotTail = true;
replacement = '.1';
}
}
if (!this.capsLockEnabled)
replacement = replacement.toLowerCase();
// replace selected range by replacement
inp.value = value.substr(0, start) + replacement + value.substr(end);
// update change if needed,
iwgt.updateChange_();
if (replacement
&& (len = replacement.length)) {
var vlen = inp.value.length;
if (doubleDotTail) {
// to focus the last '1'
start = vlen-1;
end = vlen;
} else {
start += len;
// in case intbox/doublebox fix 00 to 0
if (start > vlen)
start = vlen;
end = start;
}
} else {
end = start;
}
// set cursor back
zk(inp).setSelectionRange(start, end);
// update selected range
updateCursorRange(iwgt);
iwgt.updatingByKeypad = null;
}
]]></attribute>
<attribute w:name="bind_"><![CDATA[
function (a, b, c) {
this.$bind_(a, b, c);
// override onFloatUp after bind_
// since it is not the normal function as bind_
this.overrideFloatup();
}
]]></attribute>
<attribute w:name="overrideFloatup"><![CDATA[
function () {
if (!this.oldFloatup) {
var wgt = this;
// keep original function
this.oldFloatup = this['onFloatUp'];
// override
this['onFloatUp'] = function (ctl) {
// do nothing if triggered by focus current input widget
var cwgt = ctl.origin;
if (wgt.currentInp == cwgt)
return;
wgt.oldFloatup(ctl);
}
}
}
]]></attribute>
<attribute w:name="switchCapsLock"><![CDATA[
function () {
// enable/disable caps lock
var capsLockEnabled = !this.capsLockEnabled,
iwgt = this.currentInp,
range = getCursorRange(iwgt),
capsLock = jq('.capslock-btn')[0];
this.capsLockEnabled = capsLockEnabled;
if (capsLock) {
if (capsLockEnabled) {
capsLock.style.color = 'green';
} else {
capsLock.style.color = '';
}
}
zk(iwgt.getInputNode()).setSelectionRange(range[0], range[1]);
}
]]></attribute>
<attribute w:name="overrideInp"><![CDATA[
function (iwgt) {
// override doKeyUp_
if (!iwgt.oldKeyUp) {
// override doKeyUp_
// keep original function
iwgt.oldKeyUp = iwgt['doKeyUp_'];
// override
iwgt['doKeyUp_'] = function (evt) {
// run original function
iwgt.oldKeyUp(evt);
// update selected range
updateCursorRange(iwgt);
}
// override doMouseUp_ similarly
iwgt.oldMouseUp = iwgt['doMouseUp_'];
iwgt['doMouseUp_'] = function (evt) {
iwgt.oldMouseUp(evt);
updateCursorRange(iwgt);
}
// override fire
iwgt.oldFire = iwgt['fire'];
iwgt['fire'] = function (evtnm, data, opts, timeout) {
// delay onChange event if
// updating by keypad to reduce ajax call
if ('onChange' == evtnm) {
var oldTimer = iwgt.deferEventTimer;
if (oldTimer) {
clearTimeout(oldTimer);
iwgt.deferEventTimer = null;
}
if (iwgt.updatingByKeypad) {
iwgt.deferEventTimer = setTimeout(function () {
iwgt.oldFire(evtnm, data, opts, timeout);
iwgt.deferEventTimer = null;
}, 500);
} else
iwgt.oldFire(evtnm, data, opts, timeout);
} else
iwgt.oldFire(evtnm, data, opts, timeout);
}
}
}
]]></attribute>
</popup>
</zk>
Keypad.java
This is the java class for keypad component, handle status of a keypad.
package test.marcro.component.keypad;
import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Button;
import org.zkoss.zul.Hlayout;
import org.zkoss.zul.Popup;
import org.zkoss.zul.impl.InputElement;
/**
* java class of keypad macro component
*
* @author benbai123
*
*/
public class Keypad extends HtmlMacroComponent {
private static final long serialVersionUID = 1436975152609984604L;
@Wire
Popup pp;
/** char sequence, used to generate button in keypad */
private String _charSeq;
/** the related input element to update */
private InputElement _inp;
/** open status of keypad popup */
private boolean _open;
/**
* Constructor
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Keypad () {
compose();
final Keypad comp = this;
// since there is no isOpen() in Popup,
// update open status manually
pp.addEventListener(Events.ON_OPEN, new EventListener () {
public void onEvent (Event event) {
comp.updateOpen(((OpenEvent)event).isOpen());
}
});
}
// setter/getter
/**
* init buttons in keypad
*
* parse format: <br>
* [break] denotes create a new line (hlayout) for remaining buttons <br>
* [...] denotes create button by string in [] <br>
* [Del] and [CapsLock] are two supported function key <br>
* a single char otherwise<br>
*
* Note: '[' will be considered as start of [...], wrap it by [] (i.e., [[]) as needed
*/
public void setCharSeq (String charSeq) {
if (charSeq != null) {
if (!charSeq.equals(_charSeq)) {
_charSeq = charSeq;
initButtons();
}
} else {
_charSeq = null;
}
}
public String getCharSeq () {
return _charSeq;
}
public void setInp (InputElement inp) {
if (_inp != inp) {
_inp = inp;
if (_inp != null && _open) {
openPopup();
}
}
}
public InputElement getInp () {
return _inp;
}
public void setOpen (boolean open) {
if (_open != open) {
_open = open;
if (_open
&& _inp != null) {
openPopup();
} else {
pp.close();
}
}
}
public boolean isOpen () {
return _open;
}
/**
* open keypad popup
*/
private void openPopup () {
pp.open(_inp, "after_center");
}
/**
* init buttons in keypad
*
* parse format: <br>
* [break] denotes create a new line (hlayout) for remaining buttons <br>
* [Del] and [CapsLock] are two supported function keys <br>
* [...] denotes create button by string in [] <br>
* a single char otherwise<br>
*
* Note: '[' will be considered as start of [...], wrap it by [] (i.e., [[]) as needed
*/
private void initButtons () {
// clear old children
pp.getChildren().clear();
if (_charSeq != null) {
int len = _charSeq.length();
// used to build label of [...]
StringBuilder sb = new StringBuilder("");
// used to contains a line of buttons
Hlayout hl = new Hlayout();
// label for create button
String label = "";
for (int i = 0; i < len; i++) {
char ch = _charSeq.charAt(i);
if (ch != '[') {
// single char
label = ch + "";
} else {
// find ... in [...]
while (i < len) {
i++;
ch = _charSeq.charAt(i);
if (ch == ']') {
break;
}
sb.append(ch);
}
label = sb.toString();
sb.setLength(0);
}
if ("break".equals(label)) {
// break line
hl.setParent(pp);
hl = new Hlayout();
} else {
// create button
createButton(label).setParent(hl);
if (i+1 == len) {
hl.setParent(pp);
}
}
}
}
}
private Button createButton (String label) {
Button btn = new Button(label);
String doOriginalClickAndFindParentPopup = "function (evt) {\n" +
" this.$doClick_(evt);\n" + // do original function
" var p = this.parent;\n" + // find the parent popup
" while (p && !p.$instanceof(zul.wgt.Popup))\n" +
" p = p.parent;\n" +
" if (p)\n";
if ("Del".equals(label)) {
btn.setWidgetOverride("doClick_",
doOriginalClickAndFindParentPopup +
" p.executeDelete()\n" + // call delete function
"}");
} else if ("CapsLock".equals(label)) {
btn.setSclass("capslock-btn");
btn.setWidgetOverride("doClick_",
doOriginalClickAndFindParentPopup +
" p.switchCapsLock()\n" + // call switch (enable/disable) caps lock function
"}");
} else {
if ("\\".equals(label)
|| "'".equals(label)) {
// special char, need one more escape char at client side
label = "\\" + label;
}
btn.setWidgetOverride("doClick_",
doOriginalClickAndFindParentPopup +
" p.executeInsert('"+label+"')\n" + // insert label
"}");
}
return btn;
}
private void updateOpen (boolean open) {
_open = open;
}
}
Reference
Related thread at ZK Forum
http://forum.zkoss.org/question/86182/keypad-component/
Macro component document
http://books.zkoss.org/wiki/ZK_Developer's_Reference/UI_Composing/Macro_Component
widget.js
https://github.com/zkoss/zk/blob/master/zk/src/archive/web/js/zk/widget.js
dom.js
https://github.com/zkoss/zk/blob/master/zk/src/archive/web/js/zk/dom.js
Download
keypad_test.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/folders/macrocomponents/keypad/keypad_test.zul
KeypadVM.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/marcro/component/keypad/KeypadVM.java
keypad.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/folders/macrocomponents/keypad/keypad.zul
Keypad.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/marcro/component/keypad/Keypad.java
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/macro_component/zk_macro_keypad.swf
Subscribe to:
Posts (Atom)