Tuesday, July 24, 2012

validation at datatable level, datatable row iteration

Suppose we have a list of object which is rendered as a h:dataTable. User can modify and delete the list item. But we must make sure there is at least one item in the list. The list can not be empty.

We could attach the validation rule as a PostValidationEvent Listener in datatable. But how can we know the value in the list. be aware of that we are at PostValidation stage. The value has not be pushed to model layer. We can not check the model layer. We have check all the local value in the input fields.  So it boils down to how to access the row values programmatically.

Here is how I do it.
int size=0;
HtmlDataTable table=(HtmlDataTable)component
                HtmlColumn column=(HtmlColumn)table.getChildren().get(0);
                UIInput input=null;
                for(UIComponent rowfield: column.getChildren())
                {
                    if (rowfield instanceof UIInput)
                    {
                        input=(UIInput)rowfield;
                        break;
                    }
                }
                if (input!=null)
                {
                    for (int i=0; i<table.getRowCount(); i++)
                    {
                        table.setRowIndex(i);
                        if (input.getLocalValue()!=null)
                        {
                            logger.info(input.getLocalValue().toString());
                            size++;
                        }
                    }
                }
The trick here is table.setRowIndex(i). Once it is called, your input field in datatable has correct state.


JSF datatable and primitive type

suppose you have a list of integer, you want edit/delete them. h:dataTable seems to be a natural fit.
<h:datatable value="#{mylist}" var="{intitem}">
 <h:inputtext value="#{intitem}"/><h:inputtext>
</h:datatable>

Quickly you will find any value you set in the web interface will be ignored by the datatable. ui:repeat in facelet gives the same result.

It turns out that the var in datatable has to be an object type. Inside of the datatable, you access/modify the var's property. You can not modify the var itself. 

Modifying a var is like
var=newvalue;
in java
Here the var is a temporay reference.  The temporary reference points to original value in the list before modification. After the form is submitted, you basically create a new object, and make the temporary reference point to the newly created object.   The original value is not touched. The original list still has its values untouched.

This rule applies to string too.
To solve this, you need a wrapper type like this.

public class ValueHolder<T extends Serializable> implements Serializable
    {        private static final long serialVersionUID = 1L;
        public T value;
        public ValueHolder()
        {
        }
        public ValueHolder(T v)
        {
            value=v;
        }
        public T getValue()
        {
            return value;
        }
        public void setValue(T v)
        {
            value=v;
        }
        
    }
The primitive list has to be changed to a list of ValueHolder.  By this way, you change object access into property access.

<h:datatable value="#{mywrapperlist}" var="{intitem}">
 <h:inputtext value="#{intitem.value}"/><h:inputtext>
</h:datatable> 

Friday, July 13, 2012

Programmatic Facelet:Template

Inspired by the FaceletFactory.createComponent method in the reference implementration, I create a utility to use facelet template programmaitcally.

private String getTemplateComponentString(String template,
            FacesContext context, File tempFile, Object... definitions)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version='1.0' encoding='UTF-8' ?>");
        sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
        sb.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"\n");
        sb.append(" xmlns:f=\"http://java.sun.com/jsf/core\"\n");
        sb.append(" xmlns:ui=\"http://java.sun.com/jsf/facelets\">\n");
        sb.append("  <ui:composition template=\"")
                .append(fileToFile(tempFile.getAbsolutePath(), context
                        .getExternalContext().getRealPath(template)))
                .append("\">");

        // add the facet
        for (int i = 0; i < definitions.length; i++)
        {
            sb.append("<ui:define name=\"").append(definitions[i++].toString())
                    .append("\" >\n");
            sb.append("<ui:include src=\"")
                    .append(fileToFile(
                            tempFile.getAbsolutePath(),
                            context.getExternalContext().getRealPath(
                                    definitions[i].toString())))
                    .append("\" />\n");
            sb.append("</ui:define>\n");
        }

        // end the tag.
        sb.append("</ui:composition>");
        sb.append("</html>");

        return sb.toString();
    }

public static UIComponent instantiateTemplate(String template,
            Object... definitions)
    {
        FacesContext ctx = FacesContext.getCurrentInstance();
        FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder
                .getFactory(FactoryFinder.FACELET_FACTORY);

        UIPanel panel = (UIPanel) ctx.getApplication().createComponent(
                UIPanel.COMPONENT_TYPE);
        File tempFile = createTempFile(ctx);
        String content = getTemplateComponentString(template, ctx, tempFile,
                definitions);
        try
        {
            FileUtils.write(tempFile, content);
            URL furl = tempFile.toURI().toURL();
            Facelet f = faceletFactory.getFacelet(furl);
            f.apply(ctx, panel);
            return panel;
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        } finally
        {
            tempFile.delete();
        }
    }

The method createTempFile and fileToFile can be found here
http://www.flexdms.com/2012/07/programmatic-faceletcomposite-component.html.


Programmatic Facelet:Composite Component with Facet, API approach

In my blast post, I demonstrated how you can create a composite component with facet in it.  I used a temporary file to do the work. In this post, I use a purely API approach.  Suppose I have
I can do it like this

 First, I create the composite component.

UIComponen  compositeComponent=...

Then I create myfacet component
UIComponent fragment=....

 Finally, I attach the created fragment as facet to composite component.
compositeComponent.getFacets().put("firstfacet", fragment);


The advantage of this apporach: you do not need to worry about temporary file generation and path translation for fragment file.
The disadvantage of this approach: for each facet NAME, you can only attach one component.

Programmatic Facelet:Composite Component with Facet

If you use composite component directly in xhtml file, you can attach facet to component. However, the JSF 2.2 does not give a way to do. The standard API in FaceletFactory is like this
createComponent(String taglibURI, String tagName, Map<String,Object> attributes)

 You can only pass attributes to it.  Attach facet is a really neat feature  since you can insert arbitrary objects to the component  and use component as a template.

I digged into the reference implementation to see how RI implement this method. RI just creates a temporary file exactly like the static file you would write and asks FaceletFactory to create a component from the file.  I can definitely use this approach to include f:facet into the generated file. I verified this approach and it worked.  Through this approach, you can do anything programmatically if you can do it in a static approach. You just need to generate a file like the static file and let FaceletFactory to do the rest work.

There is a trick: If you have any "ui:include", you need to transfer the src path so it is relative to the temporary generated file.

Here I give an example how I do it.
private  File createTempFile(FacesContext context)
    {
        File tmpDir = (File) context.getExternalContext().getApplicationMap().get(
                "javax.servlet.context.tempdir");
        File tempFile;
        try
        {
            tempFile = File.createTempFile("faceletutils", ".xhtml", tmpDir);
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        return tempFile;
    }
/**
     *
     * return a string which navigates from fromfile to tofile.
     *
     * @param fromfile
     *            absolute file path
     * @param tofile
     *            absolute file path
     *
     * @return
     */
    public  String fileToFile(String fromfile, String tofile)
    {if (tofile.startsWith(File.separator))
        {
            tofile = tofile.substring(1);
        }
        if (fromfile.startsWith(File.separator))
        {
            fromfile = fromfile.substring(1);
        }
        String[] froms = fromfile.split(File.separator);
        String[] tos = tofile.split(File.separator);
        List<String> fs = new LinkedList<String>();
        int i = 0;
        for (i = 0; i < froms.length - 1; i++)
        {

            // strip common directory.
            if (!froms[i].equals(tos[i]))
            {
                break;
            }
        }

        if (i == froms.length - 1)
        {
            // reach the last file, all path are the same.
            fs.add(".");
        } else
        {
            for (int j = i; j < froms.length - 1; j++)
            {
                fs.add("..");
            }
        }
        for (int j = i; j < tos.length; j++)
        {
            fs.add(tos[j]);
        }

        return StringUtils.join(fs, "/");
    }
private  String getCompositeComponentString(String library,
            String name, Map<String, Object> attrs, FacesContext context,
            File tempFile, String tempId, Object... facets)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version='1.0' encoding='UTF-8' ?>");
        sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
        sb.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"\n");
        sb.append(" xmlns:f=\"http://java.sun.com/jsf/core\"\n");
        sb.append(" xmlns:ui=\"http://java.sun.com/jsf/facelets\"\n");
        sb.append("      xmlns:comp=\"http://java.sun.com/jsf/composite/")
                .append(library).append("\">\n");
        sb.append("  <comp:").append(name).append(" ");

        // have all the attributes
        if (attrs != null && !attrs.isEmpty())
        {
            for (Map.Entry<String, Object> attr : attrs.entrySet())
            {
                sb.append(attr.getKey()).append("=\"")
                        .append(attr.getValue().toString()).append("\"");
            }
        }
        // give component An ID

        sb.append(" id=\"").append(tempId).append("\" >\n");

        // add the facet
        for (int i = 0; i < facets.length; i++)
        {

            sb.append("<f:facet name=\"").append(facets[i++].toString())
                    .append("\" >\n");
            sb.append("<ui:include src=\"")
                    .append(fileToFile(
                            tempFile.getAbsolutePath(),
                            context.getExternalContext().getRealPath(
                                    facets[i].toString()))).append("\" />\n");
            sb.append("</f:facet>\n");
        }

        // end the tag.
        sb.append("</comp:").append(name).append(">");
        sb.append("</html>");

        return sb.toString();
    }
/**
     *
     * Construct a composite comonent given attribute and context-relative facet
     * file.
     *
     * @param library
     *            library of the composite component
     * @param name
     *            name of the composite component
     * @param attrs
     *            a series of attrs value for the component.
     * @param facets
     *            a series of paired value. The first value in pair is facet
     *            name. The second value is CONTEXT-RELATIVE facet file.
     * @return UIComponent represent the constructed composite component
     * @throws IOException
     */
    public static UIComponent applyFacetFileToCompositeComponent(
            String library, String name, Map<String, Object> attrs,
            Object... facets)
    {
        UIComponent result = null;

        FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder
                .getFactory(FactoryFinder.FACELET_FACTORY);
        FacesContext context = FacesContext.getCurrentInstance();
        Application app = context.getApplication();

        String tempId = context.getViewRoot().createUniqueId(context, name);
        File tempFile = createTempFile(context);
        String content = getCompositeComponentString(library, name, attrs,
                context, tempFile, tempId, facets);

        try
        {
            FileUtils.write(tempFile, content);
            URL furl = tempFile.toURI().toURL();
            Facelet f = faceletFactory.getFacelet(furl);
            UIForm form = (UIForm) app.createComponent(UIForm.COMPONENT_TYPE);
            form.setPrependId(false);
            form.setId(context.getViewRoot().createUniqueId());
            f.apply(context, form);
            result = form.findComponent(tempId);
            form.getChildren().clear();

            return result;
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        } finally
        {
            tempFile.delete();
        }
    }


With these methods, I can create my composite with facet like this
applyFacetFileToCompositeComponent("test", "component", null, "firstfacet", "test/firstfragment.xhtml");
where "test" and "component" defines one composite component in resource syntax, and "test/firstfragment.xhtml" is a regular facelet file under web application root directory.






   


 

Programmatic Facelet:create Composite Component

There is no official API way to do this before JSF 2.2.
Suppose we have this composite component under webapproot/resources/test/component.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:c="http://java.sun.com/jsp/jstl/core"
    xmlns:iedit="http://java.sun.com/jsf/composite/iedit/"
    >
<h:head>
    <title>This content will not be displayed</title>
</h:head>
<h:body>
    <composite:interface>
        <composite:attribute name="tiptext" required="false" />
    </composite:interface>
    <composite:implementation>
        <h:panelGroup styleClass="propertylabelcell">
            <composite:renderFacet name="firstfacet" required="false"></composite:renderFacet>
        </h:panelGroup>
        A extra line of text
        <h:outputText value="#{cc.attrs.tiptext}"></h:outputText>
    </composite:implementation>
</h:body>
</html>

We can create one INSTANCE of this component programtically like this

FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder
                .getFactory(FactoryFinder.FACELET_FACTORY);
Map<String, Object> attrs=new HashMap<String, Object>();
attrs.put("tiptext", "this is a test tip");
        UIComponent component = faceletFactory.createComponent(
                "http://java.sun.com/jsf/composite/" + library, name, attrs);

Programmatic facelet: resource to component

Facelet is a great way to write your UI layout in xml declaratively. However, in highly dynamically website, you need to control facelet construction programmatically.

This seems silly since the the purpose of the facelet to describe the component layout. If you use programmatic approach, you can program  component tree directly.  This is not completely true. Facelet has the concept of component, composition and template. I may define a fragment of tree structure as component or template and want to mix them programmatically. I do not need to program everything from scratch.

I will start a series article to demonstrate how you can do this. I assume JSF 2.2 is used. You can download the JSF reference implementation here:http://javaserverfaces.java.net/nonav/rlnotes/2.2.0-m01/

Suppose you have a RESOURCE like this
 WebContent/resources/test/fragment.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
<ui:component>
This is a fragment
</ui:component>
</h:body>
</html>
 

You can add this component to your tree structure like this

public  Facelet resouceToFacet(String resource)
    {
        FacesContext fctx = FacesContext.getCurrentInstance();
        Application app = fctx.getApplication();
        FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder
                .getFactory(FactoryFinder.FACELET_FACTORY);
        Resource res = app.getResourceHandler().createResource(resource);
        Facelet f;
        try
        {
            f = faceletFactory.getFacelet(res.getURL());
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        return f;

    }

public static UIComponent faceletToComponent(Facelet f)
    {
        FacesContext fctx = FacesContext.getCurrentInstance();
        Application app = fctx.getApplication();
        UIPanel panel = (UIPanel) app.createComponent(UIPanel.COMPONENT_TYPE);
        try
        {
            f.apply(fctx, panel);
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        UIComponent newComponent = panel.getChildren().remove(0);

        return newComponent;
    }

faceletToComponent(resouceToFacet("test/fragment.xhtml"));


Leaving fragment.xhtml as a resource gives us the advantage to use ResourceHandler to find the file. If it is not a resource, we need to find the URL for this file. We could do it like this
new File(FacesContext.getCurrentInstance().getExternalContext().getRealPath(PATH) ).toURI().toURL();
Once we have the URL for the fragment file, we can create a facelet from it using FaceletFactory. The created facelet can then be converted to UIComponent.







Friday, July 6, 2012

jsf commandLink's onclick pitfalls with f:ajax

For jsf commandLink and commandButton, you can attach onclick handler and ajax handler.
Here are several pitfalls you'd like to know

  • First the javascript you attach to commandLink onclick is a piece of code, not a function name. If your function is named as f, the onclick attribute is "f()", not "f".
  • Second, how can you stop the execution flow, in another word, how can you stop the subsequent ajax call from your function "f"? Return false from "f" is not enough. You can link the return code from "f" to onclick like this onclick="return f()".
  • Third, how can you catch the context such as event target and event when onclick is invoked? When onclick is called, two contextal variables are available: this and event. You can pass them to your function like this: onclick="return f(this, event);" or onclick="return f.call(this, event);";