In the previous ZK in Action posts we went through implementing a CRUD feature using ZK MVVM. We also quickly went through some styling code that may deserve more explanation.
In this post, we'll go over how to append new CSS styling rules onto ZK widgets and how to override the existing styling. We'll also introduce some basics of UI layout in ZK.
Objective
Use ZK's layout and container widgets to host the inventory CRUD feature we built in the previous posts.
Style the ZK widgets
ZK Features in Action
Borderlayout
Hlayout
Tabbox
Include
sclass
Using Layouts and Containers
The Borderlayout and Hlayout
The Borderlayout divides the window into 5 sections as shown below:
Without further ado let's dissect the markup and see how it works:
line 3 and 27, the north and south widgets can be adjusted for height but not width
line 9 and 26, the east and west widgets can be adjusted for width but not height
line 10, the center widget's dimensions are dependent on those entered for the north, west, south, and east widgets
from line 4 through 7, we wrap the two labels with an Hlayout so they'll be displayed side by side proportionally with respect to the "hflex" attribute we specified. That is, the Label assigned with hflex="9" has 9 times the width of the Label assigned with hflex="1".
each inner widget (north, west, etc.) can accept only a single child component, hence, multiple widgets must be wrapped by a single container widget such as Hlayout before placed inside the Borderlayout inner widgets (north, west, etc.)
line 11, we place a Tabbox element and set its orientation as vertical in anticipation of embedding our inventory CRUD feature inside it
line 12 to 16, we put the heading for each tab
line 18, a Tabpanel is a container that holds a tab's content
line 19, we embed our inventory CRUD feature inside an Include tag. The widgets on inventory.zul will be attached to this page
Overriding the Existing ZK Styling Rules
The ZK default font properties and the background colours were modified so headings would be presented more prominently. Let's quickly explain how this is accomplished.
Using Chrome Developer Tool or the Firebug extension, we could easily inspect the source of our Borderlayout and find the ZK styling class for the ZK widgets as shown below:
From here we learned that the naming pattern for the highlighted region is z-north-body. Similarly, we could do the same for all the markup of interest and go ahead overriding their CSS styling rules:
Appending Additional Styling Rules via Style Attribute
Here we're modifying the styling of the Labels contained in the North widget. Since we only want these two Labels, not all of them, to be affected by our new styling, it does not make sense for us to override the original styling as we did before. For these isolated modifications, it suffice to simply assign the styling rules to the "style" attribute that comes with the ZK widgets:
An alternative to assign styling rules directly in the markup and pollute the code is to declare a styling class, abbreviated as "sclass", and assign the rules to the "sclass" attribute as shown here:
Three ways to modify the default ZK styling is covered in this post: override the existing ZK styling class, assign styling rules directly to a widget's style attribute, or define a CSS class in a CSS file or inside a Style tag then assign the class to the widget's sclass attribute
Use a developer tool(such as Firebug) to inspect the ZK widgets and find out which ZK style class to override
The hlex attribute allows developers to define widgets' width proportionally with respect to each other
Layout widgets help developers to divide the presentation window into sections
A key distinction between ZK MVVM and ZK MVC implementation wise is that we do not access and manipulate the UI components directly in the controller(ViewModel) class. In this post, we'll see how we can delegate some of the UI manipulation to client side code, as well as how to pass parameters from View to ViewModel.
Objective
Build an update function to our simple inventory CRUD feature. Users can edit-in-place entries in the table and given the choice to update or discard the changes made. Modified entries are highlighted in red.
ZK Features in Action
ZK Client-side APIs
ZK Style Class
MVVM : Pass Parameters from View to ViewModel
Implementation in Steps
Enable In-place editing in Listbox so we can edit entries:
inplace="true" renders input elements such as Textbox without their borders displaying them as plain labels; the borders appear only if the input element is selected
line 2, 6,"each" refers to each Item object in the data collection
Once an entry is edited we want to give users the option to Update or Discard the change.
The Update and Discard buttons need to be visible only if user has made modifications on the Listbox entries. First we define JavaScript functions to show and hide the Update and Discard buttons:
line 2, we wrap the Update and Discard and set visibility to false
line 9, 13, we define functions which hide and show the Update and Discard buttons respectively
line 11, 15, we make use of jQuery selector jq( '$edit_btns') to retrieve the ZK widget whose ID is "edit_btns"; notice the selector pattern for a ZK widget ID is '$', not '#'
When entries in the Listbox are modified, we'll make the Update/Discard buttons visible and make the modified values red. Once either Update or Discard button is clicked, we'd like to hide the buttons again
Since this is pure UI interactions, we'll make use of ZK's client side APIs:
line 2, we specify a style class for our input elements (Textbox, Intbox, Doublebox, Datebox) and assign it to input elements' sclass attribute, eg. line 26; sclass defines style class for ZK widgets
line 18~20, we get all the input elements by matching their sclass name and assign an onChange event handler. Once the value in an input element is changed, the Update/Discard buttons will become visible and the modified value will be highlighted in red.
line 17, zk.afterMount is run when ZK widgets are created
line 6, we specify the client namespace so we can register client side onClick event listeners with the syntax "w:onClick". Note we can still register our usual onClick event listener that's handled at the server simultaneously.
line 9, 10, we assign client side onClick event listener; the hideEditBtns function would be called to make the buttons invisible again
Define a method to store the modified Item objects into a collection so the changes could be updated in batch if user choose to do so:
public class InventoryVM {
private HashSet<Item> itemsToUpdate = new HashSet<item>();
...
@Command
public void addToUpdate(@BindingParam("entry") Item item){
itemsToUpdate.add(item);
}
line 6, we annotate this method as a command method so it can be invoked from View
line 7, @BindingParam("entry") Item item binds an arbitrarily named parameter called "entry"; we anticipate the parameter would be of type Item
Create a method to update the changes made in View to the Data Model
public class InventoryVM {
private List<Item> items;
private HashSet<Item> itemsToUpdate = new HashSet<item>();
...
@NotifyChange("items")
@Command
public void updateItems() throws Exception{
for (Item i : itemsToUpdate){
i.setDatemod(new Date());
DataService.getInstance().updateItem(i);
}
itemsToUpdate.clear();
items = getItems();
}
When modifications are made on Listbox entries, invoke the addToUpdate method and pass to it the edited Item object which in turn is saved to the itemsToUpdate collection
@save(each.name, before='updateItems') ensures that modified values are not saved unless updateItems is called (ie. when user click the "Update" button)
Finally, when user clicks Update, we call the updateItems method to update changes to Data Model. If Discard is clicked, we call getItems to refresh the Listbox without applying any changes
Under the MVVM pattern we strive to keep the ViewModel code independent of any View components
Since we don't have direct reference to the UI components in the ViewModel code, we can delegate the UI manipulation (in our sample code, show/hide, style change) code to the client using ZK's client side APIs
We can make use of jQuery selectors and APIs at ZK client side
We can easily pass parameters from View to ViewModel with @BindingParam
Next up, we'll go over a bit more on ZK Styling before we tackle the MVVM validators and converters.
Reference
ViewModel (ZK in Action[0]~[3]):
public class InventoryVM {
private List<Item> items;
private Item newItem;
private Item selected;
private HashSet<Item> itemsToUpdate = new HashSet<Item>();
public InventoryVM(){}
//CREATE
@NotifyChange("newItem")
@Command
public void createNewItem(){
newItem = new Item("", "",0, 0,new Date());
}
@NotifyChange({"newItem","items"})
@Command
public void saveItem() throws Exception{
DataService.getInstance().saveItem(newItem);
newItem = null;
items = getItems();
}
@NotifyChange("newItem")
@Command
public void cancelSave() throws Exception{
newItem = null;
}
//READ
@NotifyChange("items")
@Command
public List<Item> getItems() throws Exception{
items = DataService.getInstance().getAllItems();
for (Item j : items){
System.out.println(j.getModel());
}
Clients.evalJavaScript("zk.afterMount(function(){jq('.inputs').removeClass('modified').change(function(){$(this).addClass('modified');showEditBtns();})});"); //how does afterMount work in this case?
return items;
}
//UPDATE
@NotifyChange("items")
@Command
public void updateItems() throws Exception{
for (Item i : itemsToUpdate){
i.setDatemod(new Date());
DataService.getInstance().updateItem(i);
}
itemsToUpdate.clear();
items = getItems();
}
@Command
public void addToUpdate(@BindingParam("entry") Item item){
itemsToUpdate.add(item);
}
//DELETE
@Command
public void deleteItem() throws Exception{
if (selected != null){
String str = "The item with name \""+selected.getName()+"\" and model \""+selected.getModel()+"\" will be deleted.";
Messagebox.show(str,"Confirm Deletion", Messagebox.OK|Messagebox.CANCEL, Messagebox.QUESTION,
new EventListener<Event>(){
@Override
public void onEvent(Event event) throws Exception {
if (event.getName().equals("onOK")){
DataService.getInstance().deleteItem(selected);
items = getItems();
BindUtils.postNotifyChange(null, null, InventoryVM.this, "items");
}
}
});
} else {
Messagebox.show("No Item was Selected");
}
}
public Item getNewItem() {
return newItem;
}
public void setNewItem(Item newItem) {
this.newItem = newItem;
}
public Item getselected() {
return selected;
}
public void setselected(Item selected) {
this.selected = selected;
}
}