Posts Tagged ‘javascript

30
Apr
11

JavaScript Sleep

This page describes a simple implementation of a sleep function in javascript.

Java has a Thread.sleep() functionality however there is nothing like this in javascript. In order to obtain this functionality you will need to create a custom function.

This is a simple way to implement a sleep function in JavaScript. It works by implementing an semi-infinite loop. In each iteration it checks and breaks out, if the difference in the start time and the current time is greater than the number of milliseconds specified as the argument.

function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}

Call the function like this:

sleep(1000); // sleep for 1 second

This is a form of busy waiting… But currently the only way. Please post a comment if you disagree.

27
Mar
11

Preventing XSS using commons validator

This page describes the process of validating html submitted form to prevent a Cross Site Scripting attack using commons validator.

Background

Adding input validation to your application adds an extra layer of security however its only the second best method to protect against cross site scripting attacks.[1]

The best method is to is to encode the output before displaying it to the user.[2]

Regardless, adding more layers of security doesn’t hurt.

Approach

The approach taken here is to white-list characters that are allowed to be entered by creating a custom validator. Developers can use “mask” rule however this

  1. clutters up the validator.xml file
  2. could possibly interfere with a a field’s existing mask rule

Requirements

  1. Successful completion of my previous post

Procedure

We will be re-using the project created in my previous post. Or you can skip the implementation and just follow along below.

Create a Custom Validator

The following class extends the FieldChecks class provided by the struts framework. The extra method is based on the mask method of the parent class. The only difference is that the mask pattern is hard coded into the implementation of the method.

src/main/java/com/test/InfoSecurityChecks.java

package com.test;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorUtil;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.validator.FieldChecks;
import org.apache.struts.validator.Resources;

public class InfoSecurityChecks extends FieldChecks {
	private static final long serialVersionUID = 1L;
	private static final Log log = LogFactory.getLog(InfoSecurityChecks.class);

	public InfoSecurityChecks() {
		super();
	}

	/**
	 * The code for this method was taken from the mask method of the parent
	 * class. The mask regular expression has been hard coded to only allow
	 * characters that are approved.
	 *
	 */
	public static boolean validateInfoSec(Object bean, ValidatorAction va,
			Field field, ActionErrors errors, HttpServletRequest request) {
		System.out.println("infosec validator ran");
		// The following is allowed
		// alpha numeric characters and space
		// special characters except < > ( )
		String mask = "^[A-Za-z0-9!\"\\\\#$%&'*+,/:;=\\.?@_\\`{\\|}\\~\\-\\^ ]*$";
		String value = null;
		if (isString(bean))
			value = (String) bean;
		else {
			value = ValidatorUtil.getValueAsString(bean, field.getProperty());
		}
		System.out.println(value);
		try {
			if ((!(GenericValidator.isBlankOrNull(value)))
					&& (!(GenericValidator.matchRegexp(value, mask)))) {
				errors.add(field.getKey(), Resources.getActionError(request,
						va, field));
				System.out.println("did not match returning false.");
				return false;
			}
			return true;
		} catch (Exception e) {
			 log.error(e.getMessage(), e);
		}
		return true;
	}
}

Modify the Validator Rules

Modify the validator-rules.xml and add an additional “validator” section towards the end.

src/main/webapp/WEB-INF/validator-rules.xml

      <validator name="infosec"
            classname="com.test.InfoSecurityChecks"
               method="validateInfoSec"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
                  msg="errors.infosec">
      </validator>

Add the following new line to the application.properties

src/main/resources/application.properties

errors.infosec=The input contains invalid characters.

Modify the validation.xml and add “infosec” to the end of the depends attribute of the “lastName” attribute of the “userInformation” form.

src/main/webapp/WEB-INF/validation.xml

    <field property="lastName" depends="required,infosec">
        <arg key="userInformation.lastName" />
    </field>

Test the application

Return to the project top level directory and execute the following command to start the jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/

An extJS form should display allowing you to enter values. Click submit and an Ajax form submission will be made to the Struts Action. Any validation failures will show next to the component.

Try entering a <script>alert(‘test xss’);</script> in the last name field.

System should display a validation failure message indicating that there are invalid characters in the input.

References

[1] XSS (Cross Site Scripting) Prevention Cheat Sheet
[2] http://www.ibm.com/developerworks/web/library/wa-secxss/

27
Mar
11

ExtJS form validation using commons-validator

This page describes the process of using commons-validator to validate extJS form submissions.

Background

Commons validation is a mature project that’s been out there for a while now. It became popular when it was introduced as part of the Struts framework. Commons validation allows developers to specify validation rules inside XML files, thus allowing them to be applied uniformly across the site. In addition the same set of rules can be used to generate both client side and server side validation thus reducing maintenance cost.

In the interest of brevity, this page will only cover the server side validation.

Using commons-validator by itself doesn’t give you much benefit. All you get is the ability to specify rules and validation logic that returns whether the validation passed or failed.

You are left on your own to:

  1. Write your own code to iterate thru the form fields and collect the failure messages
  2. Write a servlet to handle requests over HTTP and present the failure messages back to the client
  3. Internationalization Support

Although its not that much work, when you start implementing it you quickly realize that this boiler plate code is something that a framework should provide.

This boiler plate code has been written years ago by the makers of the Struts framework. By extending the framework slightly, we can use it to validate forms submitted by extJS components.

Struts comes pre-packaged with the “html:errors” tag. Upon validation failures struts returns the user back to the “input” page and the input page contains a “html:errors” tag that displays the failed validation along with a description of the failure in HTML format.

The Approach

The approach presented here focues on extending the “html:errors” tag to return JSON instead of html. The JSON is parsed by the extJS formPanel and the errors are presented to the user.

On success the extJS formPanel expects:

{success: true, msg : 'Thank you for your submission.'}

On failure the extJS formPanel expects:

{
   success : false,
   msg     : 'This is an example error message',
   errors  : {
      firstName : 'Sample Error for First Name',
      lastName  : 'Sample Error for Last Name'
   }
}

Note:
Some of the code on this page are not core to the understanding “approach”, for this reason the code will initially be collapsed.

Requirements

Since this page is about integrating 3 different technologies together a basic understanding of struts, commons-validation, and extJS is assumed. If any of these technologies are not clear then please visit some of my other tutorial pages.

  1. Maven 2
  2. Basic Understanding of extJS, Struts 1 and commons-validator

Outline

This tutorial is quite lengthy.

Here is an outline:

  1. Create and configure a new project
  2. Extend the html:errors tag
  3. Configure the Struts Framework
  4. Create the struts action class and JSP
  5. Configure the page validation

Create and configure a new project

I would recommend starting from a new project.

Create a new project using Maven 2.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

groupId: com.test
artifactId: form-validation

Answer the rest of the questions with defaults “Just hit the enter key”,

cd to the project base folder.

cd form-validation

Since this is a web project maven2 does not create the java source folder.

Create missing folder
mkdir -p src/main/java/com/test

Modify the project Configuration

Replace the pom.xml with the following.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>form-validation</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>form-validation Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>commons-validator</groupId>
			<artifactId>commons-validator</artifactId>
			<version>1.1.3</version>
		</dependency>
		<dependency>
			<groupId>struts</groupId>
			<artifactId>struts</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-servlet_2.5_spec</artifactId>
			<version>1.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.7.0</version>
		</dependency>
		<dependency>
			<groupId>commons-digester</groupId>
			<artifactId>commons-digester</artifactId>
			<version>1.8</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20090211</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>form-validation</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.0.2</version>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>7.0.0.v20091005</version>
				<configuration>
					<scanIntervalSeconds>2</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>


Extend the html:errors tag

Extend the html:errors tag to return errors in JSON format.

src/main/java/com/test/ExtJsonErrorsTag.java

package com.test;

import java.util.Iterator;

import javax.servlet.jsp.JspException;

import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.taglib.html.ErrorsTag;
import org.apache.struts.util.RequestUtils;
import org.apache.struts.util.ResponseUtils;
import org.json.JSONException;
import org.json.JSONObject;

public class ExtJsonErrorsTag extends ErrorsTag {

	private static final long serialVersionUID = 1L;

	public int doStartTag() throws JspException {
		ActionErrors errors = null;
		JSONObject jsonReturn = new JSONObject();
		try {
			errors = RequestUtils.getActionErrors(this.pageContext, this.name);
		} catch (JspException e) {
			RequestUtils.saveException(this.pageContext, e);
			throw e;
		}
		if ((errors == null) || (errors.isEmpty())) {
			
			try {
				jsonReturn.put("success", true);
				jsonReturn.put("msg", "Thank you for your submission.");
			} catch (JSONException e) {
				throw new JspException(e);
			}

			ResponseUtils.write(this.pageContext, jsonReturn.toString());
			return 1;
		}

		Iterator errorIterator = errors.properties();
		JSONObject jsonErrors = new JSONObject();

		while (errorIterator.hasNext()) {
			String property = (String) errorIterator.next();
			processErrors(errors, property, jsonErrors);
		}

		try {
			jsonReturn.put("success", false);
			jsonReturn.put("msg", "There was a validation failure.");
			jsonReturn.put("errors", jsonErrors);
			ResponseUtils.write(this.pageContext, jsonReturn.toString());
		} catch (JSONException e) {
			throw new JspException(e);
		}

		return 1;
	}

	private void processErrors(ActionErrors errors, String property,
			JSONObject jsonErrors) throws JspException {
		String message;
		Iterator reports = (property == null) ? errors.get() : errors
				.get(property);
		while (reports.hasNext()) {
			ActionError report = (ActionError) reports.next();

			message = RequestUtils.message(this.pageContext, this.bundle,
					this.locale, report.getKey(), report.getValues());

			if (message != null) {
				try {
					jsonErrors.put(property, message);
				} catch (JSONException e) {
					throw new JspException(e);
				}
			}
		}

	}
}

This is the tag descriptor for the above tag. Nothing interesting here. Just copy and paste this into struts-ext-html.tld.

src/main/webapp/WEB-INF/struts-ext-html.tld

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
	<tlibversion>1.2</tlibversion>
	<jspversion>1.1</jspversion>
	<shortname>html-ext</shortname>
	<uri>http://struts.apache.org/tags-html-ext</uri>
	<tag>
		<name>errors</name>
		<tagclass>com.test.ExtJsonErrorsTag</tagclass>
		<bodycontent>empty</bodycontent>
		<attribute>
			<name>bundle</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>footer</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>header</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>locale</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>name</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>prefix</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>property</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>suffix</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
</taglib>

Action Class and JSP

This is where you provide logic that will run after the form is validated. Control will only be passed to the execute method when the form is fully validated.

src/main/java/com/test/UserSubmitAction.java

package com.test;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
 
public class UserSubmitAction extends org.apache.struts.action.Action {
    public ActionForward execute(ActionMapping mapping,ActionForm form,
           HttpServletRequest request,HttpServletResponse response) throws Exception {
 
    	System.out.println("action ran.");

    	return mapping.findForward("success");
    }
}

The following JSP presents an extJS form to allow the user to enter data.

src/main/webapp/index.jsp

<html>
<head>
 
<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>
 
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
 
<script type="text/javascript"> 
function buildWindow() {
    Ext.QuickTips.init();
    Ext.form.Field.prototype.msgTarget = 'side';
    var bd = Ext.getBody();

    var fs = new Ext.FormPanel({
        labelWidth: 75, 
        frame: true,
        title:'Form with crazy amount of validation',
        bodyStyle:'padding:5px 5px 0',
        width: 350,
        defaults: {width: 230},
        waitMsgTarget: true,
        defaultType: 'textfield',       
 
        items: [{
                fieldLabel: 'First Name',
                name: 'firstName'
            },{
                fieldLabel: 'Last Name',
                name: 'lastName'
            },{
                fieldLabel: 'Company',
                name: 'company'
            }, {
                fieldLabel: 'Email',
                name: 'email',
                vtype:'email'
            },{
                fieldLabel: 'Time',
                name: 'time'
            } 
        ],       
 
    });
 
    var onSuccessOrFail = function(form, action) {
        var result = action.result;
 
        if(result.success) {
            Ext.MessageBox.alert('Success', 'Success!');
        } else { // put code here to handle form validation failure.
        }
 
    }
 
    var submit = fs.addButton({
        text: 'Submit',
        disabled:false,
        handler: function(){
            fs.getForm().submit({
                url:'/form/UserSubmit',
                waitMsg:'Submitting Data...',
                submitEmptyText: false,
                success : onSuccessOrFail,
                failure : onSuccessOrFail
            });
        }
    });
 
    fs.render('form-first');
}
Ext.onReady(buildWindow);
</script>
</head>
<body>
<div id="form-first"></div>
</body>
</html>

Success and Failure JSP’s

This JSP displays regardless if there was a validation failure or not. This is to satisfy the extJS component requirement that all form submissions should return a result.

src/main/webapp/WEB-INF/result.jsp

<%@ taglib uri="/tags/struts-html-ext" prefix="html-ext" %>
<html-ext:errors/> 


Validation configuration

All the fields of the form are required. In addition the first name must be at least 10 and no more than 15 characters. The email address must be valid formatted and the time field must be in the proper format in order to proceed. All of this is coded into the following file.

src/main/webapp/WEB-INF/validation.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
 
<form-validation>
<formset>
<form name="userInformation">
    <field property="firstName" depends="required,minlength,maxlength">
        <arg0 key="userInformation.firstName" />
        <arg1 name="minlength" key="${var:minlength}" resource="false"/>
        <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
        <var>
        	<var-name>minlength</var-name>
        	<var-value>10</var-value>
        </var>
        <var>
        	<var-name>maxlength</var-name>
        	<var-value>15</var-value>
        </var>
        
    </field>
    <field property="lastName" depends="required">
        <arg key="userInformation.lastName" />
    </field>
    <field property="company" depends="required">
        <arg key="userInformation.company" />
    </field>
    <field property="email" depends="required,email">
        <arg key="userInformation.email" />
    </field>
    <field property="time" depends="required,mask">
        <msg name="mask" key="userInformation.time.maskmsg"/>
        <arg0 key="userInformation.time" />
        <var><var-name>mask</var-name><var-value>^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$</var-value></var>
    </field>
</form>
</formset>
</form-validation>

Configure the Struts Framework

The following files are necessary to get the struts framework initialized and ready to process form submissions. The lines specific to this application have been highlighted.

Struts Config

The following struts configuration file contains a dyna-form and an action mapping. The /UserSubmit action mapping “input” page will be invoked if there are validation failures. If all goes well with validation the control will forward to “result.jsp” which will display JSON output expected by the extJS component.

src/main/webapp/WEB-INF/struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>
    <!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>
	<form-beans>
		<form-bean name="userInformation"
			type="org.apache.struts.validator.DynaValidatorForm">
			<form-property name="firstName" type="java.lang.String" />
			<form-property name="lastName" type="java.lang.String" />
			<form-property name="company" type="java.lang.String" />
			<form-property name="email" type="java.lang.String" />
			<form-property name="time" type="java.lang.String" />
		</form-bean>
	</form-beans>

	<action-mappings>
		<action name="userInformation" path="/UserSubmit" input="/WEB-INF/result.jsp"
			type="com.test.UserSubmitAction" validate="true">
			<forward name="success" path="/WEB-INF/result.jsp"></forward>
		</action>
	</action-mappings>

	<!-- The "application.properties" is located in the root of the classpath. -->
	<message-resources parameter="application" />

	<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
		<set-property property="pathnames"
			value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml," />
	</plug-in>

</struts-config>

The following file was taken from the commons-validator framework. It contains information about the various classes that perform validation. Just copy and paste this into validator-rules.xml. The client side JavaScript validation code has been removed since we will only be performing server side validation. I am thinking about writing another tutorial on how to have commons validator generate extJS specific client side JavaScript validation. Let me know if you guys are interested in seeing this.

src/main/webapp/WEB-INF/validator-rules.xml

<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">

<form-validation>
   <global>
      <validator name="required"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateRequired"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
                  msg="errors.required">
      </validator>

      <validator name="requiredif"
                 classname="org.apache.struts.validator.FieldChecks"
                 method="validateRequiredIf"
                 methodParams="java.lang.Object,
                               org.apache.commons.validator.ValidatorAction,
                               org.apache.commons.validator.Field,
                               org.apache.struts.action.ActionErrors,
                               org.apache.commons.validator.Validator,
                               javax.servlet.http.HttpServletRequest"
                 msg="errors.required">
      </validator>

      <validator name="minlength"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMinLength"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.minlength">
      </validator>


      <validator name="maxlength"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMaxLength"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.maxlength">

      </validator>


      <validator name="mask"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMask"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.invalid">

      </validator>


      <validator name="byte"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateByte"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.byte"
       jsFunctionName="ByteValidations">
      </validator>

      <validator name="short"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateShort"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.short"
       jsFunctionName="ShortValidations">
      </validator>

      <validator name="integer"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateInteger"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.integer"
       jsFunctionName="IntegerValidations">
      </validator>

      <validator name="long"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateLong"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.long"/>
      <validator name="float"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateFloat"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.float"
       jsFunctionName="FloatValidations">
      </validator>

      <validator name="double"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateDouble"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.double"/>

      <validator name="date"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateDate"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.date"
       jsFunctionName="DateValidations">


      </validator>

<!-- range is deprecated use intRange instead -->
      <validator name="range"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateIntRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="integer"
                  msg="errors.range">
      </validator>

      <validator name="intRange"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateIntRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="integer"
                  msg="errors.range">

      </validator>

      <validator name="floatRange"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateFloatRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="float"
                  msg="errors.range">
      </validator>

      <validator name="creditCard"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateCreditCard"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.creditcard">

      </validator>

     <validator name="email"
            classname="org.apache.struts.validator.FieldChecks"
              method="validateEmail"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                      org.apache.commons.validator.Field,
                      org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.email">
      </validator>
   </global>
</form-validation>

The following file contains the messages to display to the user.

src/main/resources/application.properties

errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.
 
errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.
 
errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.

userInformation.firstName=First Name
userInformation.lastName=Last Name
userInformation.company=company
userInformation.email=email
userInformation.time=time
userInformation.time.maskmsg=Time must be formatted like (##:## AM|PM)

This is a bare minimum log4j.properties file.

src/main/resources/log4j.properties

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=INFO, A1
 
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c - %m%n

src/main/webapp/WEB-INF/web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>
         /WEB-INF/struts-config.xml
        </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
 
    </servlet>
 
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>/form/*</url-pattern>
    </servlet-mapping>

  <!-- Struts Tag Library Descriptors -->
 
  <taglib>
    <taglib-uri>/tags/struts-html-ext</taglib-uri>
    <taglib-location>/WEB-INF/struts-ext-html.tld</taglib-location>
  </taglib>
  
</web-app>

Test the application

In the project top folder type in the following command to compile and run the code in jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/

An extJS form should display allowing you to enter values. Click submit and an Ajax form submission will be made to the Struts Action. Any validation failures will show next to the component.

That’s all for now.

13
Mar
11

Ajax Simulated Combo Boxes using ExtJS

This page describes the process of creating a combo box that behaves like its Ajax driven. Adding this component is easy and with very little effort you can improve the usability of your page.

Requirements

  • Text Editor
  • HTML Web Server

Procedure

  1. Include the proper script statements to reference the ext js libraries. (see below)
  2. Create an “id” field for the combo box
  3. Insert the highlighted code below onto your page and specify the “id” of the component you want to enhance in the “transform” field of the js code below.

index.html

<html>
<head>

<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>

<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>

<script type="text/javascript">

function buildWindow() {
 
var converted = new Ext.form.ComboBox({
    typeAhead: true,
    triggerAction: 'all',
    transform:'state',
    width:135,
    forceSelection:true
});

}
Ext.onReady(buildWindow);
</script>
 
</head>
<body>

<div>
Transformed select:<br/> <select name="state" id="state">

<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="FL">Florida</option>

<option value="GA">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>

<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>

<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH" selected>Ohio</option>
<option value="OK">Oklahoma</option>

<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>

<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</div>
</p>
</body>
</html>

12
Mar
11

ExtJS GridPanel RowExpander With Ajax Call

This page describes the process of creating an extJS grid panel where each row can be expanded to reveal additional information. The additional information will be retrieved using Ext.Ajax.request.

Requirements

  • Text Editor
  • HTML Web Server

Procedure

There are 2 techniques to get the data:

  1. Load the complete set of data initially and have the row expander simply display it. This is explained in Part I
  2. Allow for lazy loading of the the data when the row is expanded by making an Ajax request. This is explained in Part II

The advantage of the first method is that there is almost zero delay expanding the rows since the data is retrieved ahead of time. The disadvantage is that most of the data that is downloaded may never be displayed

In the second method only the necessary data is retrieved from the server at the cost of the extra time required to make the additional request for data. You will note that things start changing from line 20 to line 50. The remaining code is the same as above.

Part I

Navigate to an empty directory and create the following file.

index.html

<html>
<head>

<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>

<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
    <script type="text/javascript" src="http://dev.sencha.com/deploy/ext-3.3.1/examples/ux/RowExpander.js"></script>


<script type="text/javascript">

function buildWindow() {
var myColumns = [];


// row expander
var expander = new Ext.ux.grid.RowExpander({
    tpl : new Ext.Template(
        '<p>&nbsp;&nbsp;<b>Age:</b> {age}</p><br>'
    )
});

var myStore = new Ext.data.JsonStore({
	url: 'response2.do',
	autoLoad : false, // we will manually call the load function.
	listeners: {
		'load' : function () {
			myStore.each(function(r) {
				myColumns.push(expander);
				// Full Name			
				var this_column = {};		
				r.fields.each(function(currentField) { 
					if(currentField.name == 'fullName') {
						this_column['header'] = 'Full Name';
						this_column['sortable'] = true;
						this_column['dataIndex'] = 'fullName';
						myColumns.push(this_column); 
						return false; // used as a break statement
					}
				});

				// State
				this_column = {};		
				r.fields.each(function(currentField) { 				
					// Ext.grid.Column API docs has complete attribute list.
					if(currentField.name == 'state') {
						this_column['header'] = 'State';
						this_column['sortable'] = true;
						this_column['dataIndex'] = 'state';
						myColumns.push(this_column); 
						return false; // used as a break statement
					}
				});
																	
				return false; // used as a break statement			
			});
			
		}
	}            
});

var grid = new Ext.grid.GridPanel({
	title : 'Name State Grid',
	store : myStore,
	autoHeight : true,
	plugins: expander,
	cm : new Ext.grid.ColumnModel({}), // Empty columnModel initially.
	renderTo : Ext.getBody()
});

// load the data and perform a reconfigure in the callback.
myStore.load({ 
	callback : function(r, options, success) { 
		var myColumnModel = new Ext.grid.ColumnModel({
			columns : myColumns
		});
		grid.reconfigure(myStore, myColumnModel);			
	}
});

}
Ext.onReady(buildWindow);
</script>
 
</head>
<body>
</body>
</html>

Data for Part I

The following data is provided to the component in Part I of this page. It returns all the information necessary to display the row including the data for the expanded state.

response2.do

{ 
"totalCount" : "4",
"records" : [
    {"state":"NY","fullName":"Ted Williams", "age" : 25},
    {"state":"NJ","fullName":"Bob Smith", "age" : 37},
    {"state":"NY","fullName":"John Adams", "age" : 44},
    {"state":"CA","fullName":"John Doe", "age" : 47}
],
"metaData" : {
	"totalProperty" : 'totalCount',
	"root" : 'records',
	"fields" : ['state', 'fullName', 'age']	
}
}

Part II

This part explains how to take the above grid and make it retrieve additional data once the row is expanded.

Steps:

  1. The first step was to create a blank expander. Unlike the example above the expander will be filled with data on the “beforeexpand” event.
  2. An event listener defined on the “beforeexpand” event makes an Ajax request. The URL for the request is customized based on the row that was clicked on.
  3. The data is decoded and the body of the row is overwritten using a Ext.layout.Template

The highlighted lines below are the ones that have been changed.

index2.html

<html>
<head>

<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>

<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
    <script type="text/javascript" src="http://dev.sencha.com/deploy/ext-3.3.1/examples/ux/RowExpander.js"></script>


<script type="text/javascript">

function buildWindow() {
var myColumns = [];

var myTemplate = new Ext.Template(
		'<p>&nbsp;&nbsp;<b>Age:</b> {age}</p><br>',
		'<p>&nbsp;&nbsp;<b>Address:</b> {address}</p><br>',
		'<p>&nbsp;&nbsp;<b>City:</b> {city}</p><br>',
		'<p>&nbsp;&nbsp;<b>Zip:</b> {zip}</p><br>'
	);

// row expander
var expander = new Ext.ux.grid.RowExpander({});

expander.on('beforeexpand', function( rowexpander, record, body, rowindex) {
	// make the ajax request
    Ext.Ajax.request({
        url : 'app/users/' + record.get('id') ,
        method: 'GET',
        success: function ( result, request ) {
			// retrive the data
	        var jsonData = Ext.util.JSON.decode(result.responseText);	        
			// pass the data to the overwrite function.
			myTemplate.overwrite(body, jsonData);
        },
        failure: function ( result, request) {
            Ext.MessageBox.alert('Failed', result.responseText);
            return false;
        }
    });
	
	return true;
});

var myStore = new Ext.data.JsonStore({
	url: 'response2.do',
	autoLoad : false, // we will manually call the load function.
	listeners: {
		'load' : function () {
			myStore.each(function(r) {
				myColumns.push(expander);
				// Full Name			
				var this_column = {};		
				r.fields.each(function(currentField) { 
					if(currentField.name == 'fullName') {
						this_column['header'] = 'Full Name';
						this_column['sortable'] = true;
						this_column['dataIndex'] = 'fullName';
						myColumns.push(this_column); 
						return false; // used as a break statement
					}
				});

				// State
				this_column = {};		
				r.fields.each(function(currentField) { 				
					// Ext.grid.Column API docs has complete attribute list.
					if(currentField.name == 'state') {
						this_column['header'] = 'State';
						this_column['sortable'] = true;
						this_column['dataIndex'] = 'state';
						myColumns.push(this_column); 
						return false; // used as a break statement
					}
				});
																	
				return false; // used as a break statement			
			});
			
		}
	}            
});

var grid = new Ext.grid.GridPanel({
	title : 'Name State Grid',
	store : myStore,
	autoHeight : true,
	plugins: expander,
	cm : new Ext.grid.ColumnModel({}), // Empty columnModel initially.
	renderTo : Ext.getBody()
});

// load the data and perform a reconfigure in the callback.
myStore.load({ 
	callback : function(r, options, success) { 
		var myColumnModel = new Ext.grid.ColumnModel({
			columns : myColumns
		});
		grid.reconfigure(myStore, myColumnModel);			
	}
});

}
Ext.onReady(buildWindow);
</script>
 
</head>
<body>
</body>
</html>

Data for Part II

The following files simulate data being returned by a server side resource (Servlet or RESTlet). You can create them using a standard text editor. The files below don’t have an extension so keep that in mind when creating these files in windows.

Note: The information for User 4 is missing on purpose. This is to show the message that would display if information about a user was not found.

response2.do

{ 
"totalCount" : "4",
"records" : [
    {"id" : 1, "state":"NY","fullName":"Ted Williams", "age" : 25},
    {"id" : 2, "state":"NJ","fullName":"Bob Smith", "age" : 37},
    {"id" : 3, "state":"NY","fullName":"John Adams", "age" : 44},
    {"id" : 4, "state":"CA","fullName":"John Doe", "age" : 47}
],
"metaData" : {
	"totalProperty" : 'totalCount',
	"root" : 'records',
	"fields" : ['id', 'state', 'fullName', 'age']	
}
}

app/users/1

{ 
"age" : "45",
"address" : "145-30 Chestnut Street",
"city" : "Rutherford, CT",
"zip" : 10010
}

app/users/2

{ 
"age" : "46",
"address" : "145-31 Chestnut Street",
"city" : "Rutherford, CT",
"zip" : 10010
}

app/users/3

{ 
"age" : "23",
"address" : "145-32 Chestnut Street",
"city" : "Rutherford, CT",
"zip" : 10010
}

That’s All for now!

05
Mar
11

ExtJS Dynamic Grid Columns

This page describes the process of dynamically creating a ExtJS columnModel based on data from a Ajax call. Generating a data Grid when the columns can not be predefined requires a different approach. The data coming from the server is evaluated and the columnModel is constructed in a set of if-else conditions. Once the column model and store have been initialized they are passed to the gridpanel for display.

Requirements

  • Html Editor
  • HTTP Server to serve the pages from

Procedure

Create the following html page:

Procedure:

  1. Make an Ajax request using the Standard XMLHttpRequest browser object.
  2. Read the meta-data from the response
  3. Run thru a series of if-else conditions to create and insert columns into the dynamic column model
  4. Feed the GridPanel with the store and the column model.

The following html file sets up the styles and imports the core JavaScript libraries.

index.html

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Stateful + Dynamic Grid Panel with Grouping and Totals</title>


    <!-- ** CSS ** -->
    <!-- base library -->
    <link rel="stylesheet" type="text/css" href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css" />

    <!-- overrides to base library -->

    <!-- page specific -->
    <link rel="stylesheet" type="text/css" href="http://dev.sencha.com/deploy/ext-3.3.1/examples/shared/examples.css" />

    <style type=text/css>
/*!
 * Ext JS Library 3.3.1
 * Copyright(c) 2006-2010 Sencha Inc.
 * licensing@sencha.com
 * http://www.sencha.com/license
 */
#grid-example .x-grid-col-1 {
	text-align: right;
}
#grid-example .x-grid-col-2{
	text-align: right;
}
#grid-example .x-grid-col-3 {
	text-align: right;
}
#grid-example .x-grid-col-4 {
	text-align: right;
}
#grid-example.x-grid-mso{
	border: 1px solid #6593cf;
}
#grid-example.x-grid-vista{
	border: 1px solid #b3bcc0;
}
#xml-grid-example{
	border: 1px solid #cbc7b8;
	left: 0;
	position: relative;
	top: 0;
}
#editor-grid .x-grid-col-2{
    text-align:right;
}
.x-grid3-td-topic b {
    font-family:tahoma, verdana;
    display:block;
}
.x-grid3-td-topic b i {
    font-weight:normal;
    font-style: normal;
    color:#000;
}
.x-grid3-td-topic .x-grid3-cell-inner {
    white-space:normal;
}
.x-grid3-td-topic a {
    color: #385F95;
    text-decoration:none;
}
.x-grid3-td-topic a:hover {
    text-decoration:underline;
}
.details .x-btn-text {
    background-image: url(details.gif);
}
.x-resizable-pinned .x-resizable-handle-south{
    background:url(../../resources/images/default/sizer/s-handle-dark.gif);
    background-position: top;
}
        /* style rows on mouseover */
        .x-grid3-row-over .x-grid3-cell-inner {
            font-weight: bold;
        }

        /* style for the "buy" ActionColumn icon */
        .x-action-col-cell img.buy-col {
            height: 16px;
            width: 16px;
            background-image: url(../shared/icons/fam/accept.png);
        }

        /* style for the "alert" ActionColumn icon */
        .x-action-col-cell img.alert-col {
            height: 16px;
            width: 16px;
            background-image: url(../shared/icons/fam/error.png);
        }

    </style>
    
    <!-- overrides to base library -->
    <link rel="stylesheet" type="text/css" href="http://dev.sencha.com/deploy/ext-3.3.1/examples/ux/css/GroupSummary.css" />

    <!-- ** Javascript ** -->

    <!-- ExtJS library: base/adapter -->
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>

    <!-- overrides to base library -->
    <!-- extensions -->
    <script type="text/javascript" src="http://dev.sencha.com/deploy/ext-3.3.1/examples/ux/GroupSummary.js"></script>

    <!-- page specific -->

    <script type="text/javascript" src="array-grid.js"></script>

</head>
<body>
    <h1>Stateful + Dynamic Grid Panel with Grouping and Totals</h1>
    <p>Note that the js is not minified so it is readable. See <a href="array-grid.js">array-grid.js</a>.</p>
    
    <div id="grid-example2"></div>

</body>
</html>

array-grid.js

Ext.onReady(function(){
    Ext.QuickTips.init();
    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());

	// The columns are constructed dynamically here 
	// Ext.Ajax.request() introduces timing issues since it is asynchronous.
	// use XMLHttpRequest since it is synchronous in nature.
    var xmlhttp = {};
    if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
      xmlhttp=new XMLHttpRequest();
    } else { // code for IE6, IE5
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.open("GET",'response1.do',false); // make a synchronous request
    xmlhttp.send(null);

	var prefs = {};
    if(xmlhttp.status == 200) {
	    var ajaxReader = new Ext.data.JsonReader();
		var ajaxStore = new Ext.data.GroupingStore({
			groupField:'industry',
			reader: ajaxReader,
			data: Ext.decode(xmlhttp.responseText)
		});

		var myColumns = [];
		// create the column model dynamically.
		for (var i = 0, len = ajaxReader.meta.fields.length; i < len; i++) {
			var field = ajaxReader.meta.fields[i];
			if(field.name == 'company') {
 				var this_column = {};
                this_column['id'] = 'company';
                this_column['header'] = 'Company';
                this_column['width'] = 60;
                this_column['sortable'] = true;
                this_column['dataIndex'] = 'company';
                this_column['hideable'] = false;
                this_column['summaryType'] = 'max';                    
                myColumns.push(this_column); 					
			} else if(field.name == 'price') {
                var this_column = {};
                this_column['id'] = 'price';
                this_column['header'] = 'Price';
                this_column['width'] = 20;
                this_column['sortable'] = true;
                this_column['dataIndex'] = 'price';
				this_column['renderer'] = Ext.util.Format.usMoney;
                this_column['summaryType'] = 'average';
                myColumns.push(this_column); 				
			} else if (field.name == 'change') {
				// Change 				
                var this_column = {};
                this_column['id'] = 'change';
                this_column['header'] = 'Change';
                this_column['width'] = 20;
                this_column['sortable'] = true;
				this_column['renderer'] = Ext.util.Format.usMoney;
                this_column['dataIndex'] = 'change';
                this_column['summaryType'] = 'max';
                myColumns.push(this_column); 				
			} else if (field.name == 'industry') {                    
				// Industry
                var this_column = {};
                this_column['id'] = 'industry';
                this_column['header'] = 'Industry';
                this_column['width'] = 20;
                this_column['sortable'] = true;
                this_column['dataIndex'] = 'industry';
                myColumns.push(this_column);
			} else if (field.name == 'lastChange') {                    
				// Last Updated
                var this_column = {};
                this_column['id'] = 'lastChange';
                this_column['header'] = 'Last Change';
                this_column['width'] = 20;
                this_column['sortable'] = true;
                this_column['dataIndex'] = 'lastChange';
                this_column['summaryType'] = 'max';
                myColumns.push(this_column);
			}	 					 				
		}					
		var grid2 = new Ext.grid.GridPanel({
			store: ajaxStore,
			stripeRows: true,
			height: 350,
			width: 600,
			stateful: true,
			stateId: 'grid2',
	        plugins: new Ext.ux.grid.GroupSummary(),
	        title: 'Stateful + Dynamic Grid Panel with Grouping and Totals',				
			view: new Ext.grid.GroupingView({
			    forceFit:true,
			    groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
			}),
			fbar  : ['->', {
			    text:'Clear Grouping',
			    iconCls: 'icon-clear-group',
			    handler : function(){
			        ajaxStore.clearGrouping();
			        grid2.fireEvent('groupchange', this);
			    }
			}],        
			cm: new Ext.grid.ColumnModel({
				defaults: {
					width: 120,
					sortable: true
				},
				columns: myColumns
			})		
		});
		grid2.render('grid-example2'); 				 				
		
    } else {
        // there was an error
//        console.error('there was an error getting data from the server');
    } 
});

The “metaData” in the response below allows the JsonReader to configure itself.

response1.do

{
"metaData" : {
	totalProperty : 'totalCount',
	root : 'records',
	successProperty : "success",
	fields : [
       {name: 'company', type : 'string'},
       {name: 'price', type: 'float'},
       {name: 'change', type: 'float'},
       {name: 'pctChange', type: 'float'},
       {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
       {name: 'industry', type : 'string'}
	]
},
"success": true,
"totalCount" : "30",
"records" : [
    {company: '3m Co'								,price: 71.72,change: 0.02,pctChange: 0.03,lastChange: '4/2 12:00am', industry: 'Manufacturing'},
    {company: 'Alcoa Inc'							,price: 29.01,change: 0.42,pctChange: 1.47,lastChange: '4/1 12:00am', industry: 'Manufacturing'},
    {company: 'Altria Group Inc'					,price: 83.81,change: 0.28,pctChange: 0.34,lastChange: '4/3 12:00am', industry: 'Manufacturing'},
    {company: 'American Express Company'			,price: 52.55,change: 0.01,pctChange: 0.02,lastChange: '4/8 12:00am', industry: 'Finance'},
    {company: 'American International Group, Inc.'	,price: 64.13,change: 0.31,pctChange: 0.49,lastChange: '4/1 12:00am', industry: 'Services'},
    {company: 'AT&T Inc.'							,price: 31.61,change: -0.48,pctChange: -1.54,lastChange: '4/8 12:00am', industry: 'Services'},
    {company: 'Boeing Co.'							,price: 75.43,change: 0.53,pctChange: 0.71,lastChange: '4/8 12:00am', industry: 'Manufacturing'},
    {company: 'Caterpillar Inc.'					,price: 67.27,change: 0.92,pctChange: 1.39,lastChange: '4/1 12:00am', industry: 'Services'},
    {company: 'Citigroup, Inc.'						,price: 49.37,change: 0.02,pctChange: 0.04,lastChange: '4/4 12:00am', industry: 'Finance'},
    {company: 'E.I. du Pont de Nemours and Company'	,price: 40.48,change: 0.51,pctChange: 1.28,lastChange: '4/1 12:00am', industry: 'Manufacturing'},
    {company: 'Exxon Mobil Corp'					,price: 68.1,change: -0.43,pctChange: -0.64,lastChange: '4/3 12:00am', industry: 'Manufacturing'},
    {company: 'General Electric Company'			,price: 34.14,change: -0.08,pctChange: -0.23,lastChange: '4/3 12:00am', industry: 'Manufacturing'},
    {company: 'General Motors Corporation'			,price: 30.27,change: 1.09,pctChange: 3.74,lastChange: '4/3 12:00am', industry: 'Automotive'},
    {company: 'Hewlett-Packard Co.'					,price: 36.53,change: -0.03,pctChange: -0.08,lastChange: '4/3 12:00am', industry: 'Computer'},
    {company: 'Honeywell Intl Inc'					,price: 38.77,change: 0.05,pctChange: 0.13,lastChange: '4/3 12:00am', industry: 'Manufacturing'},
    {company: 'Intel Corporation'					,price: 19.88,change: 0.31,pctChange: 1.58,lastChange: '4/2 12:00am', industry: 'Computer'},
    {company: 'International Business Machines'		,price: 81.41,change: 0.44,pctChange: 0.54,lastChange: '4/1 12:00am', industry: 'Computer'},
    {company: 'Johnson & Johnson'					,price: 64.72,change: 0.06,pctChange: 0.09,lastChange: '4/2 12:00am', industry: 'Medical'},
    {company: 'JP Morgan & Chase & Co'				,price: 45.73,change: 0.07,pctChange: 0.15,lastChange: '4/2 12:00am', industry: 'Finance'},
    {company: 'McDonald\'s Corporation'				,price: 36.76,change: 0.86,pctChange: 2.40,lastChange: '4/2 12:00am', industry: 'Food'},
    {company: 'Merck & Co., Inc.'					,price: 40.96,change: 0.41,pctChange: 1.01,lastChange: '4/2 12:00am', industry: 'Medical'},
    {company: 'Microsoft Corporation'				,price: 25.84,change: 0.14,pctChange: 0.54,lastChange: '4/2 12:00am', industry: 'Computer'},
    {company: 'Pfizer Inc'							,price: 27.96,change: 0.4,pctChange: 1.45,lastChange: '4/8 12:00am', industry: 'Services'},
    {company: 'The Coca-Cola Company'				,price: 45.07,change: 0.26,pctChange: 0.58,lastChange: '4/1 12:00am', industry: 'Food'},
    {company: 'The Home Depot, Inc.'				,price: 34.64,change: 0.35,pctChange: 1.02,lastChange: '4/8 12:00am', industry: 'Retail'},
    {company: 'The Procter & Gamble Company'		,price: 61.91,change: 0.01,pctChange: 0.02,lastChange: '4/1 12:00am', industry: 'Manufacturing'},
    {company: 'United Technologies Corporation'		,price: 63.26,change: 0.55,pctChange: 0.88,lastChange: '4/1 12:00am', industry: 'Computer'},
    {company: 'Verizon Communications'				,price: 35.57,change: 0.39,pctChange: 1.11,lastChange: '4/3 12:00am', industry: 'Services'},
    {company: 'Wal-Mart Stores, Inc.'				,price: 45.45,change: 0.73,pctChange: 1.63,lastChange: '4/3 12:00am', industry: 'Retail'},
    {company: 'Walt Disney Company (The) (Holding Company)',price: 29.89,change: 0.24,pctChange: 0.81,lastChange: '4/1 12:00am', industry: 'Services'}
]
}

Test the page

The page will NOT work if you simply save the files to a local directory and point your browser there. The pages must be served by a HTTP Server. Setting up a server is beyond the scope of this page.

05
Mar
11

Displaying Data on an extJS Grid

This page describes how to display JSON data from an Ajax call on a extjs grid panel.

Requirements

  • html editor
  • Access to a HTTP Server

Start in an empty directory and create the following file.

index.html

<html>
<head>

<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>

<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>

<script type="text/javascript">

function buildWindow() {

var grid = new Ext.grid.GridPanel({
	title : 'Name State Grid',
	store : new Ext.data.JsonStore({
		url: 'response1.do',
		autoLoad : 'true',
		totalProperty: 'totalCount',
		id : 'state',
		root: 'records',
		fields: ['state', 'fullName']
	}),
	autoHeight : true,
	cm : new Ext.grid.ColumnModel( [ {
        header : 'Full Name',
        sortable : true,
        dataIndex : 'fullName'
    }, {
        header : 'State',
        sortable : true,        
        dataIndex : 'state'
    } ]),
	renderTo : Ext.getBody()
});

}
Ext.onReady(buildWindow);
</script>
 
</head>
<body>
</body>
</html>

response1.do

{ 
"totalCount" : "4",
"records" : [
    {"state":"NY","fullName":"Ester Jones"},
    {"state":"NJ","fullName":"Bob Smith"},
    {"state":"NY","fullName":"John Adams"},
    {"state":"CA","fullName":"John Doe"}
]}

21
Sep
10

Direct Web Remoting (DWR) Hello World Example

This page describes the process of setting up a very simple hello world application using direct web remoting (DWR) Java library. At the end of this you will have a working DWR enabled application running in jetty.

Background

DWR is used along with other AJAX technologies to allow for creation of rich internet applications. Often times you will find yourself using frameworks like Dojo or extJS to display the data. If your working with Java, getting the data to those frameworks often involves working with JSON or XML data and writing Servlets or something similar. DWR allows you avoid the pain and hassle of all that and allows you to directly surface your data from Java Code (possibly SpringBeans). DWR does this by wrapping your Java Beans with Javascript Objects. These objects make Ajax calls to your back end Java code right from your html page. Since these are asynchronous Javascript calls, data from your Java code can be used to update your html page without requiring your web page be refreshed.

Requirements

Procedure

We start by generating the web application using Maven 2.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

Answer the questions like seen below.

[INFO] Generating project in Interactive mode
Define value for groupId: : com.test
Define value for artifactId: : dwrHelloWorld
Define value for version:  1.0-SNAPSHOT: :
Define value for package:  com.test: :
Confirm properties configuration:
groupId: com.test
artifactId: dwrHelloWorld
version: 1.0-SNAPSHOT
package: com.test
 Y: : Y

Hit enter for the rest of the defaults.

cd to the project’s folder and modify the pom.xml so that it looks like the one below.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>dwrHelloWorld</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>dwrHelloWorld Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.directwebremoting</groupId>
			<artifactId>dwr</artifactId>
			<version>2.0.3</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-servlet_2.5_spec</artifactId>
			<version>1.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.activation</groupId>
					<artifactId>activation</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.0.4</version>
		</dependency>

	</dependencies>
	<build>
		<finalName>dwrHelloWorld</finalName>
		<plugins>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>7.0.0.v20091005</version>
				<configuration>
					<scanIntervalSeconds>2</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Next we create the src/main/java folder since this is not done for us using the archetype.

on unix you type: mkdir src/main/java

Next we will modify the web.xml file

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>dwrHelloWorld</display-name>

	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <display-name>DWR Servlet</display-name>
  <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
  <init-param>
     <param-name>debug</param-name>
     <param-value>true</param-value>
  </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

</web-app>

The following maps your java classs to the JavaScript object. The DWR library uses reflection to identify the public methods and creates javascript functions that wrap the functionality they provide.

src/main/webapp/WEB-INF/dwr.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
    "http://getahead.org/dwr/dwr20.dtd">

<dwr>
  <allow>
    <create creator="new" javascript="HelloWorldModelImpl">
      <param name="class" value="com.test.HelloWorldModelImpl"/>
    </create>
  </allow>
</dwr>

src/main/java/com/test/HelloWorldModelImpl.java

public class HelloWorldModelImpl {
    public String getData() {
        return "Hello World";
    }
}

Next we will create the JSP’s used to display the pages.

src/main/webapp/index.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Hello World with Direct Web Remoting</title>

<script type="text/javascript"
    src="${request.contextPath}/dwr/interface/HelloWorldModelImpl.js"> </script>
<script type="text/javascript"
    src="${request.contextPath}/dwr/engine.js"> </script>

</head>
<body>
<script type="text/javascript">
function handleGetData(str) {
  alert(str);
}

HelloWorldModelImpl.getData(handleGetData);
</script>
</body>
</html>

Test Your Application

Start up jetty and test your application

cd to your project’s base folder and type:

mvn jetty:run

navigate to http://localhost:8080/

You should see a Hello World alert pop-up on your screen.

Subscribe to this blog to get more articles on this topic in the future.

That’s all for now.

30
Dec
09

Listbox Picker Using JSF and Tomahawk

This page is about creating an HTML Switchlist or Picker using JSF and the Tomahawk library. We will start by creating a basic multiselect listbox and then convert that to a switchlist. If you have Maven installed you may follow along. The full program should not take more then 20 minutes to complete and have up and running.

Requirements

  • Maven
  • Eclipse or IDEA Java IDE
  • That’s about it :)

Step 1: Project Setup

cd to your workspace directory and create a new project using the maven archetype “maven-archetype-webapp”.

I did it the following way

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

Answer the rest of the questions like this… hit enter for the defaults.

Define value for groupId: : com.test 
Define value for artifactId: : testSwitchList
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  com.test: : 
Confirm properties configuration:
groupId: com.test
artifactId: testSwitchList
version: 1.0-SNAPSHOT
package: com.test
 Y: : 

Make sure you Answer the highlighted items exactly as above. The rest of the page depends on this.

cd testSwitchList; mvn eclipse:clean eclipse:eclipse (if you have IDEA then mvn idea:idea)

Import the project Into your IDE.

The pom.xml file should look like this…

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>testSwitchList</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>testSwitchList Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
<dependency>
    <groupId>org.apache.myfaces.core</groupId>
    <artifactId>myfaces-api</artifactId>
    <version>1.2.8</version>
</dependency>

<dependency>
    <groupId>org.apache.myfaces.tomahawk</groupId>
    <artifactId>tomahawk</artifactId>
    <version>1.1.9</version>
</dependency>

<dependency>
    <groupId>org.apache.myfaces.core</groupId>
    <artifactId>myfaces-impl</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>org.apache.geronimo.specs</groupId>
    <artifactId>geronimo-servlet_2.5_spec</artifactId>
    <version>1.2</version>
    <type>jar</type>
    <scope>provided</scope>
</dependency>
  </dependencies>
  <build>
    <finalName>testSwitchList</finalName>
<plugins>
    <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
                <version>7.0.0pre1</version>
        <configuration>
            <scanIntervalSeconds>2</scanIntervalSeconds>
        </configuration>
    </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
</plugins>
  </build>
</project>

Create a folder named java in the src/main directory of your project. By default this archetype does not create one.

Regenerate the eclipse or idea project (ex. mvn eclipse:clean eclipse:eclipse) and refresh the project in your IDE.

Step 2: Configuration

Replace the web.xml file in the src/main/webapp/WEB-INF folder with this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.jsf</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

faces-config.xml

Create a faces-config.xml file in the src/main/webapp/WEB-INF folder.

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
	version="1.2">

	<converter>
	    <converter-for-class>com.test.Genre</converter-for-class>
	    <converter-class>com.test.GenreConverter</converter-class>
	</converter>

	<managed-bean>
		<managed-bean-name>genreBean</managed-bean-name>
		<managed-bean-class>com.test.GenreBean</managed-bean-class>
		<managed-bean-scope>session</managed-bean-scope>
	</managed-bean>

</faces-config>

Implementation

The following is the Genre Entity Class.

src/main/java/com/test/Genre.java

package com.test;

import java.io.Serializable;

public class Genre implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;

	public Genre() {
		super();
	}

	public Genre(Integer id, String name) {
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Genre [id=" + id + ", name=" + name + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Genre other = (Genre) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id)) {
			return false;
		}
		return true;
	}
}

The following is the Genre Managed Bean.

src/main/java/com/test/GenreBean.java

package com.test;

import java.util.ArrayList;
import java.util.List;

import javax.faces.model.SelectItem;

public class GenreBean {
    private Genre[] selectedDataList;

	public Genre[] getSelectedDataList() {
		return selectedDataList;
	}

	public void setSelectedDataList(Genre[] selectedDataList) {
		this.selectedDataList = selectedDataList;
	}

    public List<SelectItem> getSelectItemsStatic() {
        List<SelectItem> selectItems = new ArrayList<SelectItem>();
        for (Genre genre : GenreBean.getAllGenreStatic()) {
            selectItems.add(new SelectItem(genre, genre.getName()));
        }
        return selectItems;
    }

    public static Genre getById(Integer id) {
    	List<Genre> list = getAllGenreStatic();
    	int index = list.indexOf(new Genre(id, "doesnt matter based on equals()"));
    	Genre genre = null;
    	if(index != -1) {
    		genre = list.get(index);
    	}
    	return genre;
    }

    private static List<Genre> getAllGenreStatic() {
    	List<Genre> list = new ArrayList<Genre>();
    	list.add(new Genre(1, "Action"));
    	list.add(new Genre(2, "Drama"));
    	list.add(new Genre(3, "Documentary"));
		return list;
	}

    public String countSelected() {
    	System.out.println("selected: " + selectedDataList.length);
    	selectedDataList = null;
    	return "delete";
    }
}

The following is the Converter

The converter helps to translate between the string representation of the object and the object itself. Typical objects like String dont need converters. Any object that is an entity that you want displayed in Any JSF component needs a converter. Below you see an object that converts the Genre bean from a string representation to an actual Genre bean. In reality you would replace the source of the data to be a object cache or database datamanager. But for now we are using hard coded data in the GenreBean.

src/main/java/com/test/GenreConverter.java

package com.test;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class GenreConverter implements Converter {

	/**
	 * This method is responsible for re-constructing the object from the text
	 * representation of the object from the HTML form element. Typically this
	 * method would use a datamanager that will "lookup" the information.
	 */
	@Override
	public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2)
			throws ConverterException {
		Genre genre = new Genre();
		if("".equals(arg2)) {
			return genre;
		}
		// typically would go to the database to get the object.
		Integer id = Integer.parseInt(arg2);
		// call a static method for now. But this really should be coming
		// from a datamanager.
		genre = GenreBean.getById(id);
		return genre;
	}

	@Override
	public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2)
			throws ConverterException {		
		if(arg2 instanceof Genre) {
			Genre genre = (Genre)arg2;
			return ""+ genre.getId();
		}
		return "";
	}
}

The following is the JSP

/src/main/webapp/index.jsp

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<f:view>
	<html>
	<head>
	<title>Hello World JSF Example</title>
	</head>
	<body>
	<h:form id="myform">
	<h3>Select Movie Genre</h3>
		<h:selectManyListbox id="genre_select"
			value="#{genreBean.selectedDataList}" size="5">
			<f:selectItems value="#{genreBean.selectItemsStatic}" />
		</h:selectManyListbox>
		<br />
		<h:commandButton action="#{genreBean.countSelected}" />
	</h:form>
	</body>
	</html>
</f:view>

test the application

mvn jetty:run
http://localhost:8080/testSwitchList/index.jsf

Convert to a Switch List

The final step is to convert the listbox to a switchlist. We are going to use the advanced features provided by the tomahawk jsf plugin to generate ourselves the switchlist as well as the javascript that supports it.

The old school method of doing this is described here. As you can see this process was tedious because the javascript had to be manually customized and some additional frameworks like JQuery needed to be used in order to make things easier.

On this page we will describe the easier way of getting this done with JSF.

Go ahead and add the following to the web.xml file.

<filter>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
</filter>

<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <!-- servlet-name must match the name of your javax.faces.webapp.FacesServlet entry -->
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.)  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
</filter-mapping>

Include the following up top in the jsp file.

<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

Simply changing the h:selectManyListbox to the t:selectManyPicklist enables the switchlist functionality on the page.

		<t:selectManyPicklist id="genre_select"
			value="#{genreBean.selectedDataList}" size="5">
			<f:selectItems value="#{genreBean.selectItemsStatic}" />
		</t:selectManyPicklist>

The selectManyPicklist imports all the necessary javascript on the page in addition to the command buttons that let you move items from left to right.

test the application

mvn jetty:run

http://localhost:8080/testSwitchList/index.jsf

By viewing the source for the page you notice that most of the coding is already done for you. JSF makes all this easy.

Thats all for now!

29
Dec
09

Listbox Picker using Javascript

This page is about creating an HTML Switchlist or Picker using Javascript.

Background

Listbox pickers are 2 listboxes with left move and right move buttons in between them. Items in each listbox can be added or removed from each other.

Requirements

  • Basic Text Editor
  • HTML Browser

Implementation

In order to accomplish this the 2 lists are displayed as html select elements. Each item on the list is an option element. When the user clicks on one of the 2 buttons a javascript call is made.

The javascript is responsible for moving selected items from the source to the target select element. The javascript function goes thru the following  process.

  1. Create a temporary arrays and a map for the source and target select boxes.
  2. Copy all the elements from the target listbox to the target temp array
  3. Copy all the selected items from the source listbox to the temp target array.
  4. Copy all the unselected elements from the source listbox to the source temp array.
  5. Clear out the listboxes and regenerate them using the target and source arrays.
  6. Assign the values of each element using the map structure.

<script language="JavaScript" type="text/JavaScript">
function moveItems(srcSelect, targetSelect) {
	var srcTmpArr = new Array();
	var targetTmpArr = new Array();
	// map type structure that stores the value of a entry given its display text.
	var valStoreMap = new Array();
	var i;
	// iterate thru the target select box and store all the values and
	// display text into a Map type structure. All values because this
	// is the target.
	for (i = 0; i < targetSelect.options.length; i++) {
		// need to hold on to value of item so we load up the map with value from target.
		valStoreMap[targetSelect.options[i].text] = targetSelect.options[i].value;
		targetTmpArr[i] = targetSelect.options[i].text;
	}
	// get the length of the target Arr that we populated above.
	// this is faster than doing a targetLength++ each time in the loop.
	var targetLength = targetTmpArr.length;

	// next we start working on the source array. This is tricky since
	// the selected items in the list will need to be MOVED over to
	// the target array. Unselected items remain behind.

	// start from a fresh source array.
	var srcLength = 0;
	// iterate thru the source structure and store all the values.
	for (i = 0; i < srcSelect.options.length; i++) {
		// need to hold on to value of item so we load up the map with value from source.
		valStoreMap[srcSelect.options[i].text] = srcSelect.options[i].value;

		// if something in the source is selected and its not blank then
		// copy it to the target Temp Array.
		if (srcSelect.options[i].selected && srcSelect.options[i].value != "") {
			targetTmpArr[targetLength] = srcSelect.options[i].text;
			targetLength++;
		} else {
			// else just add it to the source Temp array as if nothing happend.
			srcTmpArr[srcLength] = srcSelect.options[i].text;
			srcLength++;
		}
	}
	srcTmpArr.sort();
	targetTmpArr.sort();
	// reset the source and target select boxes to prepare them for copy.
	srcSelect.length = 0;
	targetSelect.length = 0;

	// populate the select boxes with the new values from the temp arrays.
	var i;
	for (i = 0; i < srcTmpArr.length; i++) {
		var optionItem = new Option();
		// lookup the value of the option Item from the map
		optionItem.value = valStoreMap[srcTmpArr[i]];
		optionItem.text = srcTmpArr[i];
		srcSelect[i] = optionItem;
	}
	for (i = 0; i < targetTmpArr.length; i++) {
		var optionItem = new Option();
		// lookup the value of the option Item from the map
		optionItem.value = valStoreMap[targetTmpArr[i]];
		optionItem.text = targetTmpArr[i];
		targetSelect[i] = optionItem;
	}
}

function selectAll(box) {
	for (var i = 0; i < box.length; i++) {
		box[i].selected = true;
	}
}
</script>

The following is the code to generate the 2 listboxes and the buttons between them.

		<table border="0" cellspacing="2" cellpadding="0">
			<tr align="center" valign="middle">
				<td align="right"><select name="FirstList" size="15" multiple
					id="FirstList" style="width: 250">
					<option value="A">Bob</option>
					<option value="B">Ted</option>
					<option value="C">John</option>
					<option value="D">Greg</option>
					<option value="E">Steve</option>
					<option value="F">Mike</option>
				</select></td>
				<td><input type="button"
					onClick="moveItems(this.form.SecondList,this.form.FirstList)"
					value="<<" style="height:50"> <input type="button"
					onClick="moveItems(this.form.FirstList,this.form.SecondList)"
					value=">>" style="height: 50"></td>
				<td align="left"><select name="SecondList" size="15" multiple
					id="SecondList" style="width: 250">
				</select></td>
			</tr>
			<tr align="center" valign="middle">
				<td colspan="3"><input type="submit" name="Submit"
					value="Submit" style="width: 250" class="button"
					onClick="selectAll(document.form1.SecondList);"></td>
			</tr>
		</table>

One thing to note here is that when the form submits all items on the second list are selected. This is the only way the items will be submitted as part of the form.

Thats all for now.




Follow

Get every new post delivered to your Inbox.

Join 34 other followers