Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Wednesday, July 11, 2012

ZK in Action [3] : MVVM - Working Together with ZK Client API


In the previous posts we've implemented the following functionalities with ZK's MVVM:

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:

<listcell>
       <textbox inplace="true" value="@load(each.name)" ...</textbox>
   </listcell>
   ....
   <listcell>
       <doublebox inplace="true" value="@load(each.price)" ...</textbox>
   </listcell>
   ...
  • 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:
<toolbar>
    ...
    <span id="edit_btns" visible="false" ...>
        <toolbarbutton label="Update" .../>
        <toolbarbutton label="Discard" .../>
    </span>
</toolbar>

    <script type="text/javascript">
        function hideEditBtns(){
     jq('$edit_btns').hide();
        }
  
        function showEditBtns(){ 
     jq('$edit_btns').show();
        }

    </script>
    ...
  • 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:
<style>
   .inputs { font-weight: 600; }
   .modified { color: red; }
</style>
...
    <toolbar xmlns:w="client" >
    ...
    <span id="edit_btns" visible="false" ...>
         <toolbarbutton label="Update" w:onClick="hideEditBtns()" .../>
         <toolbarbutton label="Discard" w:onClick="hideEditBtns()" .../>
    </span>
    </toolbar>

    <script type="text/javascript">
        //show hide functions

        zk.afterMount(function(){
            jq('.inputs').change(function(){
            showEditBtns();
            $(this).addClass('modified');
     })
        });
    </script>
    ...
    <listcell>
       <doublebox inplace="true" sclass="inputs" value="@load(each.price)" ...</textbox>
   </listcell>
   ...
  • 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

<listitem>
 <listcell>
  <doublebox value="@load(each.price) 
                @save(each.name, before='updateItems')"  
                onChange="@command('addToUpdate',entry=each)" />
 </listcell>
 ...
</listitem>
  • @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

...
 <toolbarbutton label="Update" onClick="@command('updateItems')" .../>
 <toolbarbutton label="Discard" onClick="@command('getItems')" .../>
 ...


In a Nutshell

  • 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;
 }
}
View (ZK in Action[0]~[3]):
<zk>
 <style>
  .z-toolbarbutton-cnt { font-size: 17px;} .edit-btns {border: 2px
  solid #7EAAC6; padding: 6px 4px 10px 4px; border-radius: 6px;}
  .inputs { font-weight: 600; } .modified { color: red; }
 </style>
 <script type="text/javascript">
  function hideEditBtns(){ jq('$edit_btns').hide(); }

  function showEditBtns(){ jq('$edit_btns').show(); }

  zk.afterMount(function(){ jq('.inputs').change(function(){
  $(this).addClass('modified'); showEditBtns(); }) });
 </script>
 <window apply="org.zkoss.bind.BindComposer"
  viewModel="@id('vm') @init('lab.sphota.zk.ctrl.InventoryVM')"
  xmlns:w="client">
  <toolbar width="100%">
   <toolbarbutton label="Add"
    onClick="@command('createNewItem')" />
   <toolbarbutton label="Delete"
    onClick="@command('deleteItem')"
    disabled="@load(empty vm.selected)" />
   <span id="edit_btns" sclass="edit-btns" visible="false">
    <toolbarbutton label="Update" 
     onClick="@command('updateItems')" w:onClick="hideEditBtns()"/>
    <toolbarbutton label="Discard"
     onClick="@command('getItems')" w:onClick="hideEditBtns()" />
   </span>
  </toolbar>
  <groupbox mold="3d"
   form="@id('itm') @load(vm.newItem) @save(vm.newItem, before='saveItem')"
   visible="@load(not empty vm.newItem)">
   <caption label="New Item"></caption>
   <grid width="50%">
    <rows>
     <row>
      <label value="Item Name" width="100px"></label>
      <textbox value="@bind(itm.name)" />
     </row>
     <row>
      <label value="Model" width="100px"></label>
      <textbox value="@bind(itm.model)" />
     </row>
     <row>
      <label value="Unit Price" width="100px"></label>
      <decimalbox value="@bind(itm.price)"
       format="#,###.00" constraint="no empty, no negative" />
     </row>
     <row>
      <label value="Quantity" width="100px"></label>
      <spinner value="@bind(itm.qty)"
       constraint="no empty,min 0 max 999: 
       Quantity Must be Greater Than Zero" />
     </row>
     <row>
      <cell colspan="2" align="center">
       <button width="80px" label="Save" mold="trendy"
        onClick="@command('saveItem')"  />
       <button width="80px" label="Cancel" mold="trendy"
        onClick="@command('cancelSave')" />
      </cell>
     </row>
    </rows>
   </grid>
  </groupbox>
  <listbox selectedItem="@bind(vm.selected)" model="@load(vm.items) ">
   <listhead>
    <listheader label="Name" sort="auto" hflex="2" />
    <listheader label="Model" sort="auto" hflex="1" />
    <listheader label="Quantity" sort="auto" hflex="1" />
    <listheader label="Unit Price" sort="auto" hflex="1" />
    <listheader label="Last Modified" sort="auto" hflex="2" />
   </listhead>
   <template name="model">
    <listitem>
     <listcell>
      <textbox inplace="true" width="110px" sclass="inputs"
       value="@load(each.name) @save(each.name, before='updateItems')"
       onChange="@command('addToUpdate',entry=each)">
      </textbox>
     </listcell>
     <listcell>
      <textbox inplace="true" width="110px" sclass="inputs" 
       value="@load(each.model) @save(each.model, before='updateItems')"
       onChange="@command('addToUpdate',entry=each)" />
     </listcell>
     <listcell>
      <intbox inplace="true" sclass="inputs" 
       value="@load(each.qty) @save(each.qty, before='updateItems')"
       onChange="@command('addToUpdate',entry=each)" />
     </listcell>
     <listcell>
      <doublebox inplace="true" sclass="inputs" format="###,###.00" 
       value="@load(each.price) @save(each.price, before='updateItems')"
       onChange="@command('addToUpdate',entry=each)" />
     </listcell>
     <listcell label="@load(each.datemod)" />
    </listitem>
   </template>
  </listbox>
 </window>
</zk>

Tuesday, March 27, 2012

JavaScript Closure Under the Hood

Closure is perhaps the most talked about feature in JavaScript. This post will attempt to deal with just enough of the theoretical aspects for us to appreciate the inner workings of the language behaviour we take granted for. The context of this discussion is  not specific to any JavaScript engine implementation.

Omni-Closures


In his book Secrets of the JavaScript Ninja, John Resig gave an informal description of closure as "A closure is a way to access and manipulate external variables from within a function."
Here is a rudimentary manifestation of closure that echoes the above description:

var y = 6;             // y is a global variable

function getSum(x){
    var z = 0;         // z is a local variable of  
    return x + y + z;  // getSum, whereas y is 
}                      // an external variable to getSum

getSum(4);             //returns 10

The variable y is not a local variable of getSum and yet it's been operated on within getSum. Closures are at work even for the most trivial code we'd write and take granted for.

A more explicit show of closure is when the local variables of an outer function remains to be referenced by the inner function even after the outer function's execution is complete:

function setOperators(y){       // outer function
    var z = 5;               
    function getSum(x){         //inner function
        return x + y + z;
    }
    return getSum;
}

var getSumRef = setOperators(4);//outer function's execution is complete and returns the function getSum
                                //
getSumRef(1);                   //returns 10

Function Scope?


Simply put, the scope for an entity Z refers to what other entities Z has access to. In JavaScript, a variable is said to have function scope. Instead of a rigorous but lengthy definition for it, let's consider this structure instead:

var i = 1;

function x(a){
    var j = 1;

    function y(){
        var k = 1;
    }
}

In the perspective of function x, var j and function y are accessible because they are declared within x, that is, they are local to x. Function x cannot access var k since k is within function y, hence local to y, but not to x. So far, functions x and y's behaviour adheres to that determined by their respective scopes.

However, by the merits of closure, functions can access variables outside of its local scope. Specifically, function y can access var j, and function x can access var i.

A closure is formed for function y which preserves the environment which y was created in, allowing y continue to have access to that environment. The variable j and argument a (argument for function x) are two of the constituents in this environment.

A Naive Visualization of Scope and Environment


Albeit neglecting some important details, sometimes a casual visualization can provide a mental picture that would eventually lead to a fuller understanding. So here is the attempt:

For function x:
  • local scope is the yellow area
  • environment is the blue area
  • x can access variables in yellow(local) and blue(environment), but not in green

For function y:
  • local scope is the green area
  • environment is the yellow area, plus the blue area
  • y can access variables in green(local), yellow(environment), and blue(environment)

Now we're better equipped to appreciate Wikipedia's definition of closure:
"A closure is a function together with a referencing environment for the non-local variables of that function."

Inner Workings of Closure


For ECMAScript, the proper name for this referencing environment 'e' is called an Activation Object when e is the local scope of a function, and a Global Object when e is the global scope. The activation object keeps local variables, named arguments, the arguments collection and this as key-value pairs.
Every time a function is being executed, an internal object called Execution Context is created for that specific execution. Execution context keeps a list of all activation objects and global object for the respective function being executed.

For instance, for our previous sample snippet:
function setOperators(y){       
    var z = 5;               
    function getSum(x){         
        return x + y + z;
    }
    return getSum;
}

var getSumRef = setOperators(4);  

getSumRef(1); //returns 10                  

The corresponding execution context would be:

When a closure is not needed for a function to reference its non-local variables, the function's execution context is discarded along with its activation objects. However, since the inner function getSum references non-local variable z, and argument y, a closure is created.

The closure makes a reference to the activation object for the "var getSumRef = setOperators(4)" execution context. Hence, even when setOperators' execution is completed, getSumRef remains to have access to the variables and the function getSum for evaluating the addition for us.

Digging Deeper(and Broader) for the Whole Picture


Here we've really only seen an expansion on the term "environment" in the context of a typical closure definition. To get a complete view of how scopes are managed and what function execution entails, refer to the following excellent resources:
High Performance JavaScript
http://jibbering.com/faq/notes/closures/
http://perfectionkills.com/understanding-delete/