Sunday, December 28, 2008

Remove wicket:id attribute

If you want to remove the wicket:id attributes from the generated html source,
you can call the method setRenderBodyOnly(true) on the particular component.

In case you want to remove it from all the pages, then use Settings.setStripWicketTags on the WebApplication.

Sunday, November 23, 2008

AJAXified Confirmation box - Wicket

I have a ajax version of the confirmation box which can be used for Ajax button or Ajax Links.

import org.apache.wicket.ajax.calldecorator.AjaxCallDecorator;

/**
* Javascript Confirmation box for ajax components.
* Can be used for confirmation before deletion of a record.
*
* @author Srikanth NT
*/
public class ConfirmationBoxAjaxRenderer extends AjaxCallDecorator {

/** Serial Version UID. */
private static final long serialVersionUID = 284826838568155159L;

/** Message to be desplayed in the confirm box. */
private String message;

/**
* Constructor.
* @param message Message to be shown in the confirm box.
*/
public ConfirmationBoxAjaxRenderer(final String message) {
super();
this.message = message;
}

/**
* @param script existing script around the component.
* @return modified string.
* @see org.apache.wicket.ajax.calldecorator.AjaxCallDecorator#decorateScript(java.lang.CharSequence)
*/
@Override
public CharSequence decorateScript(final CharSequence script) {
return "if(!confirm('" + message + "')) return false;" + script;
}

}


Enjoy !!!

Confirmation Dialog - Wicket

Sometimes we want the confirmation from the user before doing the request. Say for example, we have a delete button on our application. The user accidently clicks on the delete button due to which he might lose the important record. In case he got a dialog saying, "Hey dude, wanna delete this?" with Yes/No button, he can safely avoid the disastrous deletion. So how to do we do that in wicket ?


import org.apache.wicket.Component;
import org.apache.wicket.behavior.AbstractBehavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.link.Link;

/**
* Javascript Confirmation box.
* Can be used for confirmation before deletion of a record.
*
* @author Srikanth NT
*/
public class ConfirmationBoxRenderer extends AbstractBehavior {

/** Serial Version UID. */
private static final long serialVersionUID = -3398632388257916688L;

/** Message to be desplayed in the confirm box. */
private String message;

/**
* Constructor.
* @param message Message to be shown in the confirm box.
*/
public ConfirmationBoxRenderer(final String message) {
super();
this.message = message;
}

/**
* @param component Component to attach.
* @param tag Tag to modify.
* @see org.apache.wicket.behavior.AbstractBehavior#onComponentTag(org.apache.wicket.Component, org.apache.wicket.markup.ComponentTag)
*/
@Override
public void onComponentTag(final Component component, final ComponentTag tag) {
if (component instanceof Button || component instanceof Link) {
tag.getAttributes().remove("onclick");
tag.getAttributes().put("onclick", "return confirm('" + message + "')");
}
}
}


I have created a simple behavior which you can attach to the normal Button or Link.

Sunday, November 09, 2008

International Calls

Recently I found a new connecting number to call my parents at India.
Its cheaper ...Yes just 1p per minute.

At last I can save some money during this economy crunch !

http://www.telesavers.co.uk/accessnumbers.php#i

Saturday, November 08, 2008

Caching JS (JavaScript) files

When a JavaScript file is requested through the browser such as Internet Explorer, Mozilla and Netscape, this is accompanied by HTTP header directives that tell the browser how long the resource can be retrieved directly from the browser cache as opposed to from the origin server.
Since the browser represents the cache closest to the end user it offers the maximum performance benefit whenever content can be stored there.

But this brings in a problem when the content is changed in the js file. The browser does not load the file from the server but keeps using the one from cache.

In our application, any change we make to the js file is seen at the end user's machine only after repetitive refreshing in the browser. It might take some time to load the correct file from our server. So we have found a solution to fix this problem.

Every time we do a new build (either a major revision or minor release), the timestamp of release will be always newer than older releases.
Say the timestamp for the latest release 9.07 is 19-10-2008 09:10 whereas last major release say 9.06 was 11-10-2008 09:10. Even for the next minor release, it would be 20-10-2007 09:00 or 19-10-2007 10:30.

We were then attaching this timestamp to the src attribute of the script tag. Note that we are not going to do anything with this parameter 'v' in our js script!

For example, instead of
<script type="text/javascript" src="./javascript/MyScript.js"></script>

we are going to use

<script type="text/javascript" src="./javascript/MyScript.js?v=17-10-2008at16:11:19"></script>

So every time we deploy the files, the html seeks for the file similar to MyScript.js?v=17-10-2008at16:11:19 which will be different for every release. Since the browser will not find this file in its cache, it will automatically download the file from the server.

Expanding Columns - Wicket

Recently I wanted a component(table like) which expands column wise.
I googled a bit but found no appropirate component. So decided to build my own panel which can expand column-wise.
Thats the beauty of wicket! You can create custom components by using more than one out of box components easily.

So for my proto, I created two models, Row and Column.
A custom panel which takes up number of rows required in the constructor. And columns can be added to / removed from using defined
methods.

For example, I have made each column to have a radio button which has a radio group binded to it horizontally.
It can be expanded very easily to contain multiple copies of columns.
Let us see the code.


Row.java
import java.io.Serializable;

/**
 * Row Model
 * @author Srikanth NT
 */
public class Row implements Serializable {
    private static final long serialVersionUID = 1L;

    private int id;

    private String desc;

    public Row(final int id, final String desc) {
        super();
        this.id = id;
        this.desc = desc;
    }

    public int getId() {
        return id;
    }

    public void setId(final int id) {
        this.id = id;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(final String desc) {
        this.desc = desc;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((desc == null) ? 0 : desc.hashCode());
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Row other = (Row) obj;
        if (desc == null) {
            if (other.desc != null) {
                return false;
            }
        } else if (!desc.equals(other.desc)) {
            return false;
        }
        if (id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Row [" + id + ":" + desc + "]";
    }

}

Column.java

import java.io.Serializable;

/**
 * Column Model.
 *
 * @author Srikanth NT
 */
public class Column implements Serializable {
    private static final long serialVersionUID = 1L;

    private int id;

    private String desc;

    public Column(final int id, final String desc) {
        super();
        this.id = id;
        this.desc = desc;
    }

    public int getId() {
        return id;
    }

    public void setId(final int id) {
        this.id = id;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(final String desc) {
        this.desc = desc;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((desc == null) ? 0 : desc.hashCode());
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Column other = (Column) obj;
        if (desc == null) {
            if (other.desc != null) {
                return false;
            }
        } else if (!desc.equals(other.desc)) {
            return false;
        }
        if (id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Column [" + id + ":" + desc + "]";
    }

}



My custom panel is :
CustomPanel.javaimport java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Radio;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;

/**
 * Custom Panel with expanding columns.
 *
 * @author Srikanth NT
 */
public class CustomPanel extends Panel {

    private List<Row> rows;

    private Map<Row, List<Column>> elements = new LinkedHashMap<Row, List<Column>>();

    private final ListView rowRepeater;

    public CustomPanel(final String id, final List<Row> rowsToAdd) {
        super(id);
        rows = rowsToAdd;

        for (Row row : rows) {
            elements.put(row, new ArrayList<Column>());
        }

        rowRepeater = new ListView("rowRepeater", rows) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void populateItem(final ListItem item) {
                Row row = (Row) item.getModelObject();
                item.add(new Label("rowDesc", row.getDesc()));
                final RadioGroup columnSelectionGroup = new RadioGroup("columnSelectionGroup", new Model());
                final ListView columnRepeater = new ListView("columnRepeater", elements.get(row)) {
                    private static final long serialVersionUID = 1L;

                    @Override
                    protected void populateItem(final ListItem item) {
                        Column column = (Column) item.getModelObject();
                        item.add(new Radio("columnSelection", new Model(column)));
                    }
                };
                columnRepeater.setReuseItems(true);
                columnSelectionGroup.add(columnRepeater);
                item.add(columnSelectionGroup);
            }
        };
        rowRepeater.setReuseItems(true);
        add(rowRepeater);
        rowRepeater.setOutputMarkupId(true);

    }

    public void addColumn(final Column column) {
        for (Entry<Row, List<Column>> entry : elements.entrySet()) {
            if(!entry.getValue().contains(column)) {
                entry.getValue().add(entry.getValue().size(), column);
            }
        }
    }

    public void removeColumn(final Column column) {
        for (Entry<Row, List<Column>> entry : elements.entrySet()) {
            while (entry.getValue().contains(column)) {
                entry.getValue().remove(column);
            }
        }
    }

    public Map<Row, Column> getRowColumnsMapping() {
        final Map<Row, Column> rowColumnsMapping = new LinkedHashMap<Row, Column>();
        for (final Iterator it = rowRepeater.iterator(); it.hasNext();) {
            final ListItem item = (ListItem) it.next();
            final Row row = (Row) item.getModelObject();

            item.visitChildren(new IVisitor() {

                public Object component(final Component component) {
                    if (component instanceof RadioGroup) {
                        Column column = (Column) component.getModelObject();
                        rowColumnsMapping.put(row, column);
                    }
                    return CONTINUE_TRAVERSAL;
                }
            });
        }
        return rowColumnsMapping;
    }
}


And its corresponding html:
CustomPanel.html

<wicket:panel>
    <table>
        <tr wicket:id="rowRepeater">
            <td wicket:id="rowDesc"></td>
            <span wicket:id="columnSelectionGroup">
            <span wicket:id="columnRepeater">
            <td><input type="radio" wicket:id="columnSelection"></td>
            </span>
            </span>
        </tr>
    </table>
</wicket:panel>



And now a test page to check my custom component.
CustomTestPage.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;

/**
 * Test Page.
 *
 * @author Srikanth NT
 */
public class CustomTestPage extends WebPage {

    public CustomTestPage() {
        final Form columnSelectorForm = new Form("columnSelectorForm");
        add(columnSelectorForm);

        List<Row> rowList = new ArrayList<Row>();
        rowList.add(new Row(1, "Row-1"));
        rowList.add(new Row(2, "Row-2"));

        final CustomPanel columnSelectorPanel = new CustomPanel("columnSelectorPanel", rowList);
        columnSelectorPanel.setOutputMarkupId(true);
        columnSelectorForm.add(columnSelectorPanel);

        columnSelectorForm.add(new AjaxButton("addColumn1", columnSelectorForm) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form form) {
                target.addComponent(columnSelectorPanel);

                Column column = new Column(1, "Column 1");
                columnSelectorPanel.addColumn(column);
            }

        });

        columnSelectorForm.add(new AjaxButton("removeColumn1", columnSelectorForm) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form form) {
                target.addComponent(columnSelectorPanel);

                Column column = new Column(1, "Column 1");
                columnSelectorPanel.removeColumn(column);
            }

        });

        columnSelectorForm.add(new AjaxButton("addColumn2", columnSelectorForm) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form form) {
                target.addComponent(columnSelectorPanel);

                Column column = new Column(1, "Column 2");
                columnSelectorPanel.addColumn(column);
            }

        });

        columnSelectorForm.add(new AjaxButton("removeColumn2", columnSelectorForm) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form form) {
                target.addComponent(columnSelectorPanel);

                Column column = new Column(1, "Column 2");
                columnSelectorPanel.removeColumn(column);
            }

        });

        columnSelectorForm.add(new Button("submit") {
            @Override
            public void onSubmit() {
                Map<Row, Column> list = columnSelectorPanel.getRowColumnsMapping();
                for (Entry<Row, Column> entry : list.entrySet()) {
                    System.out.println(entry.getKey() + " is assigned to " + entry.getValue());
                }
            }
        });
    }
}

and related html:CustomTestPage.html

<html xmlns="http://www.w3.org/1999/xhtml">
<body>
    <form wicket:id="columnSelectorForm">
    <div style="border: 1px solid blue;">
    <table cellpadding="5px">
        <tr>
            <td>Column 1</td>
            <td><input value="Add" wicket:id="addColumn1" type="button" ></td>
            <td><input value="Remove" wicket:id="removeColumn1" type="button" ></td>
        </tr>
        <tr>
            <td>Column 2</td>
            <td><input value="Add" wicket:id="addColumn2" type="button" ></td>
            <td><input value="Remove" wicket:id="removeColumn2" type="button" ></td>
        </tr>
    </table>
    </div>
    <div style="border: 1px solid red;">
        <span wicket:id="columnSelectorPanel"> </span>
    </div>
    <input type="submit" value="Submit" wicket:id="submit">
    </form>
</body>
</html>


I have used some ajax functionality built into it.


On submit of the form, you would see some log like
Row [1:Row-1] is assigned to Column [1:Column 2]
Row [2:Row-2] is assigned to Column [1:Column 1]

( I have added the column 2 followed by column1.)

Wednesday, November 05, 2008

Add a favicon to your page - Wicket

You can add the favicon to all the pages of your website with a simple HeaderContributor.

Below is a simple static method which returns a headercontributor behavior:

class WicketUtils {

   public static HeaderContributor headerContributorForFavicon(final ResourceReference reference)
{
     return new HeaderContributor(new IHeaderContributor() {
                     private static final long serialVersionUID = 1L;

          public void renderHead(final IHeaderResponse response) {
                               response.renderString("<link type=\"image/x-icon\" rel=\"shortcut icon\" href=\"resources/favicon.ico\" />");
                     }
       });
   }
}

call it in your abstract web page constructor.

public abstract class MyAbstractWebPage extends WebPage {

MyAbstractWebPage() {
   add(WicketUtils.headerContributorForFavicon());
}


How to find your apache version

Find your httpd execution file. On my machine, it is under /usr/sbin/
Executing it with a argument -v gives you the apache version.

$/usr/sbin> ./httpd2 -v
Server version: Apache/2.2.0
Server built: Dec 27 2006 12:50:52

How to find your linux version

Look for a file in the pattern *-release under your /etc folder.
And do a CAT on it.

$cat /etc/SuSE-release
SUSE Linux Enterprise Server 10 (x86_64)
VERSION = 10

Sunday, October 19, 2008

Configure 404 on Apache and Tomcat

Its simple.

To configure custom error pages on apache,
in your virtual host configuration

<virtualhost 127.0.0.1:80>
DocumentRoot /www/example1
ServerName www.example1.com

ErrorDocument 404 /errordocs/404.html

</virtualhost>

And in tomcat, have something like

<web-app>
<error-page>
<error-code>404</error-code>
<location>/error404.jsp</location>
</error-page>
...
<web-app>

in your web.xml

Monday, May 05, 2008

What is getOutputMarkupPlaceholderTag

Let us take a table with 1 row and 2 columns. I want to show only one column at a time.
for example, in the pic above, first cell contains list of data with a radio button to select and a edit button. On the right side, we have editing screen with save and cancel button. Now we want to display only either of these cells.

<table>
<tr>
<td>
<div id="viewBlock">
Some content comes here
</div>
</td>
<td>
<div id="editBlock">
</div>
</td>
</tr>
</table>

On screen load, only table of contents will be shown. Now on selection of a record and click of Edit, first will be hidden and second cell will be visible.

To acheive this, we can use wicket ajax components. Say we create two webmarkupcontainers viewBlock and editBlock. Set the editBlock's visibility to false. And show it onclick of the ajax button Edit. But what happens is the first cell will be hidden but the second cell is not shown on click of edit button. This is because, when the setVisible(false) is called on a component, it is not redenered on the screen. So the placeholder is also missed on the rendered html.


<table>
<tr>
<td>
<div id="viewBlock">
Some content comes here
</div>
</td>
<td>
</td> DIV tag missing !
</tr>
</table>

Due to this, when you click on Edit button, the html code for second cell comes to the frontend but not rendered because of unavailability of parent place holder.

So we have set the getOutputMarkupPlaceholderTag property on both viewBlock and editBlock containers to true.

<table>
<tr>
<td>
<div id="viewBlock">
Some content comes here
</div>
</td>
<td>
<div id="editBlock">
</div> --No content. only place holder !
</td>
</tr>
</table>

Friday, April 04, 2008

London Wicket Event

A week ago, I reserved a seat for London Wicket Event held at Google office on April 2nd. It was the first event I attended after wetting my hands on Wicket.

To tell you the truth, Wicket is amazing. Full of flexibility, as a Java developer you get more control, no big configuration XMLs to maintain, LITTLE or NO JAVA SCRIPT !!! You can do more wonders, less coding, easy code maintenance and more over everything is JAVA.

Around 27 people in the conference room were from different companies and different domains but they were all murmuring only one buzz word "WICKET".

The event went very well with one presenter explaining about the DOJO integration with Wicket while Al gave a superb presentation on doing Photoshop like activities with our Wicket. Once it was completed, then we formed small groups discussing the solutions for various real scenarios we faced.

If you like to attend the event, watch out this space.