Monday, September 5, 2011

How To Validate And Block Ajax Callbacks

* One in a series of articles on Wicket Interfaces & Methods To Master *

Wicket allows you to easily add Ajax support to Wicket components by adding some concrete subclass of the AbstractAjaxBehavior class to them which ties a Javascript DOM event on the client to an event listener in an AjaxEventBehavior or some subclass of.
For instance, suppose you want to add Ajax support to a Wicket component backing an input text element that supports the DOM onkeyup event. Doing so is easily accomplished using the following markup and Java code fragments:

Markup
  1:     <body> 
  2:         <h1>Default Wicket Ajax</h1>
  3:         <form wicket:id="defaultajaximplmentationform">
  4:             Enter Something <input type="text" wicket:id="enterSomething"/>
  5:         </form>
  6:         <div>You entered: <span class="message" wicket:id="message"/></div>
  7:     </body> 
  8: 
Java
  1: public class HomePage extends WebPage {
  2: 
  3:     private Form<HomePage> form;
  4:     private TextField<String> textField;
  5:     private Label message;
  6:     private String enterSomething = "";
  7:     
  8:     public HomePage() {
  9:         form = new Form<HomePage>("defaultajaximplmentationform");
 10:         add(form);
 11:         textField = new TextField<String>("enterSomething", new PropertyModel<String>(this, "enterSomething")); 
 12:         textField.add(new AjaxFormSubmitBehavior("onkeyup") {
 13: 
 14:             @Override
 15:             protected void onSubmit(AjaxRequestTarget target) {
 16:                 // do something meaningful here like updating the database
 17:                 target.addComponent(message);
 18:             }
 19: 
 20:             @Override
 21:             protected void onError(AjaxRequestTarget target) {
 22:                 throw new UnsupportedOperationException("Not supported yet.");
 23:             }
 24:         });
 25:         form.add(textField);
 26:         message = new Label("message", new PropertyModel<String>(this, "enterSomething"));
 27:         message.setOutputMarkupId(true);
 28:         add(message);
 29:     }
 30: 
 31: }
 32: 
Looking at the Java code above we can see that I added an Ajax callback for the Javascript onkeyup event to the textfield component by adding an AjaxFormSubmittingBehavior to it. When rendered the AjaxFormSubmittingBehavior contributes Javascript code to the input element's onkeyup event that submits the form that the input element is a child of. The Javascript that submits the form calls into Wicket's Ajax Javascript library whenever the user presses and releases a key when the input element has the input focus.Here is what the rendered markup looks like:
  1: 
  2: <!DOCTYPE html> 
  3: <html xmlns:wicket="http://wicket.apache.org"> 
  4:     <head> 
  5:         <meta charset="UTF-8"> 
  6:         <title>Wicket Example</title> 
  7:         <style type="text/css"> 
  8:             span.message {
  9:                 color: #0000FF;
 10:             }
 11:         </style> 
 12:     <script type="text/javascript" src="resources/org.apache.wicket.markup.html.WicketEventReference/wicket-event.js"></script>

 13: <script type="text/javascript" src="resources/org.apache.wicket.ajax.WicketAjaxReference/wicket-ajax.js"></script>

 14: <script type="text/javascript" src="resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/wicket-ajax-debug.js"></script>

 15: <script type="text/javascript" id="wicket-ajax-debug-enable"><!--/*--><![CDATA[/*><!--*/
 16: wicketAjaxDebugEnable=true;
 17: /*-->]]>*/</script> 
 18: 

 19: </head> 
 20:     <body> 
 21:         <h1>Default Wicket Ajax</h1> 
 22:         <form wicket:id="defaultajaximplmentationform" id="defaultajaximplmentationform4" method="post" action="?wicket:interface=:1:defaultajaximplmentationform::IFormSubmitListener::"><div style="display:none"><input type="hidden" name="defaultajaximplmentationform4_hf_0" id="defaultajaximplmentationform4_hf_0" /></div> 
 23:             Enter Something <input type="text" wicket:id="enterSomething" value="" name="enterSomething" id="enterSomething5" onkeyup="var wcall=wicketSubmitFormById('defaultajaximplmentationform4', '?wicket:interface=:1:defaultajaximplmentationform:enterSomething::IActivePageBehaviorListener:0:&amp;wicket:ignoreIfNotActive=true', null,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&amp;&amp;Wicket.$$('defaultajaximplmentationform4')}.bind(this));;"/> 
 24:         </form> 
 25:         <div>You entered: <span class="message" wicket:id="message" id="message6"></span></div> 
 26:     </body> 
 27: </html> 

Line 23 in the rendered markup above shows that the AjaxFormSubmittingBehavior added the onkeyup event to the input element. The Javascript code it added to the event is the following:

var wcall=wicketSubmitFormById('defaultajaximplmentationform7', '?wicket:interface=:2:defaultajaximplmentationform:enterSomething::IActivePageBehaviorListener:0:&amp;wicket:ignoreIfNotActive=true', null,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&amp;&amp;Wicket.$$('defaultajaximplmentationform7')}.bind(this));;

The Javascript added by AjaxFormSubmittingBehavior calls wicketSubmitFormById which is a method that resides in the wicket-event.js, a Javascript file that was also contributed by AjaxFormSubmittingBehavior to the page.

All of Wicket's numerous Ajax components and behaviors work similarly which is fine if you don't need to do anything on the client after the DOM event and before the Ajax call back to the server. But suppose you want to perform validation before the Ajax call back to the server and possibly block the call back to the server if validation fails, how can you do this?

Well, the solution is actually quite easy but poorly documented. Even the holly grail of Wicket books, Wicket In Action, doesn't cover this because it was written before the solution presented here in this article was provided in the Wicket library so many readers of the book are left scratching their heads in confusion.

Prepending Your Own Javascript To Wicket's Generated Javascript

The solution requires that you override the getAjaxCallDecorator method defined in AbstractDefaultAjaxBehavior in your own behaviors to return an implementation of IAjaxCallDecorator that prepends the Javascript that Wicket generates with your own Javascript which can do anything that your use case might require such as validation and preventing the Ajax call to the server should the validation fail.

First, lets take a look at IAjaxCallDecorator:

  1: public interface IAjaxCallDecorator extends IClusterable
  2: {
  3:   /**
  4:    * Name of javascript variable that will be true if ajax call was made, false otherwise. This
  5:    * variable is available in the after script only.
  6:    */
  7:   public static final String WICKET_CALL_RESULT_VAR = "wcall";
  8: 
  9:   /**
 10:    * Decorates the script that performs the ajax call
 11:    * 
 12:    * @param script
 13:    * @return decorated script
 14:    */
 15:   CharSequence decorateScript(CharSequence script);
 16: 
 17:   /**
 18:    * Decorates the onSuccess handler script
 19:    * 
 20:    * @param script
 21:    * @return decorated onSuccess handler script
 22:    */
 23:   CharSequence decorateOnSuccessScript(CharSequence script);
 24: 
 25:   /**
 26:    * Decorates the onFailure handler script
 27:    * 
 28:    * @param script
 29:    * @return decorated onFailure handler script
 30:    */
 31:   CharSequence decorateOnFailureScript(CharSequence script);
 32: 
 33: }
 34: 

This interface declares 3 methods that must be implemented, all taking a CharSequence script parameter and returning a CharSequence. The CharSequence script parameter passed to these methods is the Javascript that Wicket generated to call it's Ajax Javascript library and that it assigns as the DOM event handler on the client.

Wicket provides a convenience class, AjaxCallDecorator, which is an adapter class which provides default implementations for these 3 methods.

  1: public abstract class AjaxCallDecorator implements IAjaxCallDecorator
  2: {
  3: 
  4:   /**
  5:    * 
  6:    */
  7:   private static final long serialVersionUID = 1L;
  8: 
  9:   /**
 10:    * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateScript(CharSequence)
 11:    */
 12:   public CharSequence decorateScript(CharSequence script)
 13:   {
 14:     return script;
 15:   }
 16: 
 17:   /**
 18:    * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateOnSuccessScript(CharSequence)
 19:    */
 20:   public CharSequence decorateOnSuccessScript(CharSequence script)
 21:   {
 22:     return script;
 23:   }
 24: 
 25:   /**
 26:    * @see org.apache.wicket.ajax.IAjaxCallDecorator#decorateOnFailureScript(CharSequence)
 27:    */
 28:   public CharSequence decorateOnFailureScript(CharSequence script)
 29:   {
 30:     return script;
 31:   }
 32: 
 33: 
 34: }
 35: 

To add Javascript that will call our validation routine and prevent the Ajax call should validation fail is just a matter of implementing the AjaxCallDecorator's decorateScript method and prepending script with our own Javascript which I will discuss next but first lets define a use case and build the implementation around that.

Our use case is the following:

Provide a form with an input text field in which the user can enter some text. The validation rules specify that the text the user enters should be at least 3 characters or they shouldn't be allowed to submit the form (via Ajax).

Now here's the implementation:

Markup

The markup we provide is very straight forward. It contains a form with a wicket id of form that has 1 input text element which we give a wicket id of enterSomething. We also provide a button the user can click to submit the form via Ajax which we give a wicket id of submitbutton. When the form is submitted we will echo what the user enters in a label which we have assigned the wicket id of message.

  1: <!DOCTYPE html> 
  2: <html xmlns:wicket="http://wicket.apache.org"> 
  3:     <head> 
  4:         <meta charset="UTF-8"> 
  5:         <title>Wicket Example</title> 
  6:         <script type="text/javascript" src="javascript/jquery.js"></script>
  7:         <style type="text/css">
  8:             span.message {
  9:                 color: #0000FF;
 10:             }
 11:         </style>
 12:     </head> 
 13:     <body> 
 14:         <h1>Wicket Ajax With Client-Side Validation Using Script Prepending</h1>
 15:         <form wicket:id="form">
 16:             Enter at least 3 characters to call the AJAX callback: <input type="text" wicket:id="enterSomething"/>
 17:         </form>
 18:         <button wicket:id="submitbutton">Submit</button>
 19:         <div>You entered: <span class="message" wicket:id="message"/></div>
 20:     </body> 
 21: </html>
 22: 
Java

On the server side we create the Wicket components corresponding to the markup elements discussed above. The key point here is that for the button I am using Wicket's AjaxSubmitLink component whose implementation adds an AjaxFormSubmitBehavior which will contribute the Javascript code for the click event on the button to submit the form back to the server using Ajax. If you spy the source code for AjaxSubmitLink you'll see that it exposes the AjaxFormSubmitBehavior's getAjaxCallDecorator method which we can override to return our own implementation of IAjaxCallDecorator:
  1: public class HomePage extends WebPage {
  2: 
  3:     private Form<HomePage> form;
  4:     private TextField<String> textField;
  5:     private Label messageLabel;
  6:     private String message = "";
  7:     private String enterSomething = "";
  8:     
  9:     public HomePage() {
 10:         form = new Form<HomePage>("defaultajaximplmentationform");
 11:         add(form);
 12:         textField = new TextField<String>("enterSomething", new PropertyModel<String>(this, "enterSomething")); 
 13:         textField.setOutputMarkupId(true);
 14:         textField.add(new PreventFormSubmitOnEnterBehavior());
 15:         form.add(textField);
 16:         add(new AjaxSubmitLink("submitbutton", form) {
 17: 
 18:             @Override
 19:             public void renderHead(HtmlHeaderContainer container) {
 20:                 super.renderHead(container);
 21:                 String javascript = "validate = function(){ var inputElement = $('#" + textField.getMarkupId() + "'); if($(inputElement).val().length < 3){ $(inputElement).css('background-color', '#ff0000'); return false; }else{ $(inputElement).css('background-color', '#ffffff'); return true; } }";
 22:                 container.getHeaderResponse().renderJavascript(javascript, "entersomethingvalidation");
 23:             }
 24: 
 25:             @Override
 26:             protected IAjaxCallDecorator getAjaxCallDecorator() {
 27:                 return new AjaxCallDecorator() {
 28: 
 29:                     @Override
 30:                     public CharSequence decorateScript(CharSequence script) {
 31:                         PrependingStringBuffer psb = new PrependingStringBuffer(script.toString());
 32:                         psb.prepend("if(validate() == false) return false;");
 33:                         return psb.toString();
 34:                     }
 35:                     
 36:                 };
 37: 
 38:             }
 39: 
 40:             @Override
 41:             protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
 42:                 System.out.println("yada yada");
 43:                 // do something meaningful here like updating the database
 44:                 message = enterSomething;
 45:                 enterSomething = "";
 46:                 target.addComponent(textField);
 47:                 target.addComponent(messageLabel);
 48:             }
 49:         });
 50:         messageLabel = new Label("message", new PropertyModel<String>(this, "message"));
 51:         messageLabel.setOutputMarkupId(true);
 52:         add(messageLabel);
 53:     }
 54: 
 55: }
 56: 

Above we create an instance of AjaxCallDecorator and we override its decorateScript method to prepend the script parameter with our own Javascript on line 30. We prepend Javascript on line 32  that calls our own Javascript function validate which we contributed to the page in our overriding of the renderHead method on line 19. To prevent the Ajax call back to the server should our validate function return false the prepended Javascript returns false.

  1: @Override
  2: protected IAjaxCallDecorator getAjaxCallDecorator() {
  3:   return new AjaxCallDecorator() {
  4: 
  5:       @Override
  6:       public CharSequence decorateScript(CharSequence script) {
  7:     PrependingStringBuffer psb = new PrependingStringBuffer(script.toString());
  8:     psb.prepend("if(validate() == false) return false;");
  9:     return psb.toString();
 10:       }
 11: 
 12:   };
 13: 
 14: }

When run the markup for HomePage is rendered as follows:

  1: 
  2: <!DOCTYPE html> 
  3: <html xmlns:wicket="http://wicket.apache.org"> 
  4:     <head> 
  5:         <meta charset="UTF-8"> 
  6:         <title>Wicket Example</title> 
  7:         <script type="text/javascript" src="../javascript/jquery.js"></script> 
  8:         <style type="text/css"> 
  9:             span.message {
 10:                 color: #0000FF;
 11:             }
 12:         </style> 
 13:     <script type="text/javascript" ><!--/*--><![CDATA[/*><!--*/
 14: $(document).ready(function(){$('#enterSomething5').live('keypress', function(e){var c = e.which ? e.which : e.keyCode;if (c == 13) {return false;}});});
 15: /*-->]]>*/</script> 
 16: 

 17: <script type="text/javascript" src="resources/org.apache.wicket.markup.html.WicketEventReference/wicket-event.js"></script>

 18: <script type="text/javascript" src="resources/org.apache.wicket.ajax.WicketAjaxReference/wicket-ajax.js"></script>

 19: <script type="text/javascript" src="resources/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/wicket-ajax-debug.js"></script>

 20: <script type="text/javascript" id="wicket-ajax-debug-enable"><!--/*--><![CDATA[/*><!--*/
 21: wicketAjaxDebugEnable=true;
 22: /*-->]]>*/</script> 
 23: 

 24: <script type="text/javascript" id="entersomethingvalidation"><!--/*--><![CDATA[/*><!--*/
 25: validate = function(){ var inputElement = $('#enterSomething5'); if($(inputElement).val().length < 3){ $(inputElement).css('background-color', '#ff0000'); return false; }else{ $(inputElement).css('background-color', '#ffffff'); return true; } }
 26: /*-->]]>*/</script> 
 27: 

 28: </head> 
 29:     <body> 
 30:         <h1>Wicket Ajax With Client-Side Validation Using Script Prepending</h1> 
 31:         <form wicket:id="defaultajaximplmentationform" id="defaultajaximplmentationform6" method="post" action="?wicket:interface=:1:defaultajaximplmentationform::IFormSubmitListener::"><div style="display:none"><input type="hidden" name="defaultajaximplmentationform6_hf_0" id="defaultajaximplmentationform6_hf_0" /></div> 
 32:             Enter at least 3 character to call the AJAX callback: <input type="text" wicket:id="enterSomething" value="" name="enterSomething" id="enterSomething5"/> 
 33:         </form> 
 34:         <button wicket:id="submitbutton" id="submitbutton7" onclick="if(validate() == false) return false;var wcall=wicketSubmitFormById('defaultajaximplmentationform6', '?wicket:interface=:1:submitbutton::IActivePageBehaviorListener:0:&amp;wicket:ignoreIfNotActive=true', 'submitbutton' ,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&amp;&amp;Wicket.$$('defaultajaximplmentationform6')}.bind(this));;; return false;">Submit</button> 
 35:         <div>You entered: <span class="message" wicket:id="message" id="message8"></span></div> 
 36:     </body> 
 37: </html> 

Notice that in the above markup on line 34 that our button's onclick event includes our prepended Javascript. Below is the Javascript we contributed by prepending in bold blue highlight:

onclick="if(validate() == false) return false;var wcall=wicketSubmitFormById('form6', '?wicket:interface=:1:submitbutton::IActivePageBehaviorListener:0:&amp;wicket:ignoreIfNotActive=true', 'submitbutton' ,function() { }.bind(this),function() { }.bind(this), function() {return Wicket.$$(this)&amp;&amp;Wicket.$$('form6')}.bind(this));;; return false;"

And here's what HomePage looks like when rendered in the browser:


2011-09-17 12h22_23


If you enter 2 characters or less and then click the submit button you will see the following:


2011-09-17 12h25_31


When we clicked submit the button's onclick event was called where we call validate. In this case, because validation failed false was returned and the Ajax call back to the server was blocked by returning false.

When we enter 3 characters and click submit here's what we get:


2011-09-17 12h26_43


Since we entered 3 characters validation succeeded and returned true so the Ajax call back to the server wasn't blocked.. On the server we assigned the value of input's model to the message's model and then we cleared out the input's model to an empty string. Then we added the input and message components to the AjaxTarget which will cause them to render in the response by replacing their markup in the DOM.

Summary

As this article has shown, Wicket's enables you to to prepend its Ajax calls to the server with your own Javascript giving you great control over your Ajax processing. What might not have been so obvious is that Wicket doesn't just limit you to prepending and validation - you can also contribute Javascript that will be called in the success handler and the failure handler as well but those are subjects for a future article.

1 comment:

  1. Nice article Jeff. I went through this myself last month and I couldn't find any helpful resources. I'm sure your article would have saved me some experimentation. I would encourage you to continue on to describe the success and failure handlers in a subsequent article.

    ReplyDelete