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