Part four: Making the UI with jQuery and Freemarker

This is the fourth and final part of my Minesweeper portlet tutorial.

This part focuses on the html side of the portlet and shows how we use jQuery and freemarker to make our UI dynamic.

Implementing the hover effect

First we will show how the jQuery javascript library is used to get a hover effect on the squares in the game board. The liferay portal 5.2.3 comes built-in with jQuery version 1.2.6, which is slightly dated but works in most cases. In liferay the used notation is jQuery() instead of the normal $() used in the jQuery documentation.

The freemarker template _game_board.ftl contains the following javascript which changes the class of the <td> element on hover.
jQuery(document).ready(function() {
    jQuery(".not-clicked").hover(
        function() {
            if (jQuery(this).hasClass("not-clicked")) {
                jQuery(this).removeClass("not-clicked").addClass("not-clicked-hover");
            }
        },
        function() {
            if (jQuery(this).hasClass("not-clicked-hover")) {
                jQuery(this).removeClass("not-clicked-hover").addClass("not-clicked");
            }
        }
    );
});
With the following style definitions in minesweeper.css
.not-clicked {
    background-image: url('../images/button.png');
}

.not-clicked-hover {
    cursor: pointer;
    background-image: url('../images/button_hover.png');
}
we get the desired effect.


Doing the click with Ajax

Next we will see how we can update parts of the html using Ajax. We are going to use jQuery for that also.

First lets look at the following snippet of javascript from _game_board.ftl
jQuery(document).ready(function() {
    jQuery(".game-board .not-clicked").click(function() {        
Here we use jQuery selectors to bind a handler for the click event on those squares, which are not yet clicked. The actual event handling code does first the following:
        
        var x = jQuery(this).attr("xloc");
        var y = jQuery(this).attr("yloc");
        var url = '<@s.url action="click"/>';
The attributes xloc and yloc contain the x and y coordinates of the square. Then we use the freemarker built in @s.url taglib to get the url for the click action.

Next line in the click handler changes the p_p_state parameter value to exclusive:
        
        url = url.replace('p_p_state=normal', 'p_p_state=exclusive');
The exclusive tells liferay that the page should be loaded without any of the layout html that is normally used. Finally we have
        
        jQuery("#game-board-container").load(url, {x: x, y: y});
    });
});
which loads the click result to the <div> containing the game board.

References

http://docs.jquery.com/Main_Page
http://freemarker.sourceforge.net/docs/ref.html

Part three: Wiring the backend with Spring Framework and Hibernate

This is part three of the Minesweeper portlet tutorial.

This part of the tutorial describes how we can wire our Struts actions with Spring beans and how we can utilize the Spring dao support with Hibernate.

Saving the highscore to database

The following steps try to illustrate how to combine Struts with Spring and Hibernate for easy data access.
  1. First we create the HighScore class to represent a highscore entry in the database
  2. public class HighScore {
        private Long id;
        private int score;
        private String name;
    }
    
  3. We map it to database table highscore with HighScore.hbm.xml mapping file
  4. <hibernate-mapping>
        <class name="com.blogspot.pragmatastic.minesweeper.HighScore" table="highscore">
            <id name="id" column="id">
                <generator class="native"/>
            </id>
            <property name="score" column="score"/>
            <property name="name" column="name"/>
        </class>
    </hibernate-mapping>
    
  5. Then we create a HighScoreDao class for saving the data with the spring hibernate support
  6. public class HighScoreDaoImpl extends HibernateDaoSupport implements HighScoreDao {
        public void save(HighScore highScore) {
            getHibernateTemplate().save(highScore);
        }
    }
    
  7. Now we create a spring application context file minesweeper-applicationContext.xml
  8. First we define the data source for the database which is configured by db.properties
  9. <beans>
        <bean id="dbConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location">
                <value>classpath:db.properties</value>
            </property>
            <property name="placeholderPrefix" value="${db."/>
            <property name="ignoreUnresolvablePlaceholders" value="true"/>
        </bean>
    
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="${db.url}"/>
            <property name="username" value="${db.username}"/>
            <property name="password" value="${db.password}"/>
        </bean>
    
  10. Then we define the hibernate session factory and list our HighScore.hbm.xml mapping file as resource
  11.     
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="mappingResources">
                <list>
                    <value>com/blogspot/pragmatastic/minesweeper/HighScore.hbm.xml</value>
                </list>
            </property>
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.hbm2ddl.auto">update</prop>
                    <prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
                </props>
            </property>
        </bean>
    
  12. And finally we define our dao
  13.     
        <bean id="highScoreDao" class="com.blogspot.pragmatastic.minesweeper.HighScoreDaoImpl">
            <property name="sessionFactory" ref="sessionFactory"/>
        </bean>
    </beans>
    
  14. Now we need to make the beans defined in the minesweeper-context.xml available for our struts action. First we configure spring to load the context file and create a application context from it in web.xml
  15. <web-app>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:minesweeper-applicationContext.xml</param-value>
        </context-param>    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    </web-app>
    
  16. Next we enable the struts spring integration in the struts.xml
  17. <struts>
        <constant name="struts.objectFactory" value="org.apache.struts2.spring.StrutsSpringObjectFactory"/>
    </struts>
    
  18. After that we can refer to the HibernateDao object in HighScoreAction, spring will automatically wire our action with the bean
  19. public class HighScoreAction extends ActionSupport {
        private HighScore highScore;
        private HighScoreDao highScoreDao;
    
        // Auto wired byName from spring
        public void setHighScoreDao(HighScoreDao highScoreDao) {
            this.highScoreDao = highScoreDao;
        }
    
        public HighScore getHighScore() {
            return highScore;
        }
    
        // Saves the form
        public String save() {
            highScoreDao.save(highScore);
            return SUCCESS;
        }
    }
    
Continue to Part four: Making the UI with jQuery and Freemarker

References

http://www.springbyexample.org/examples/simple-hibernate-xml-config.html
http://static.springsource.org/spring/docs/2.5.x/reference/orm.html#orm-hibernate
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html
http://struts.apache.org/2.0.11/docs/spring-plugin.html

Part two: Making the portlet with Struts2

This is part two of the Minesweeper portlet tutorial.

The portlet is configured with several xml files which can be found in the minesweeper/src/main/webapp/WEB-INF and minesweeper/src/main/resources directories. The following steps try to illustrate how a Struts2 action can be configured to become a Liferay portlet.
  1. Our action class GameAction has a method start for starting a new game
  2. public class GameAction extends ActionSupport {
        private Game game;
        
        public String start() {
            game.init();
            return SUCCESS;
        }
    }
    
  3. We configure action startGame in the struts.xml to call that method and render the result success with a freemarker template
  4. <struts>
        <package name="minesweeper" extends="struts-portlet-default" namespace="/minesweeper">
            <action name="startGame" class="com.blogspot.pragmatastic.minesweeper.GameAction" method="start">
                <result name="success" type="freemarker">/pages/game.ftl</result>
            </action>
        </package>
    </struts>
    
  5. We define the minesweeper struts portlet in the portlet.xml
  6. <portlet-app>
        <portlet id="minesweeper">
            <portlet-name>minesweeper</portlet-name>
            <display-name>Minesweeper Portlet</display-name>
            <portlet-class>org.apache.struts2.portlet.dispatcher.Jsr168Dispatcher</portlet-class>
    
  7. We map the portlet to the action defined in the struts.xml by setting the viewNameSpace and defaultViewAction parameters
  8.         <!-- The view mode namespace. Maps to a namespace in the Struts2 config file. -->
            <init-param>
                <name>viewNamespace</name>
                <value>/minesweeper</value>
            </init-param>
            <!-- The default action to invoke in view mode. -->
            <init-param>
                <name>defaultViewAction</name>
                <value>startGame</value>
            </init-param>
        </portlet>
    </portlet-app>
    
  9. We add Liferay specific configuration for our portlet in the liferay-portlet.xml, in this case we define a portlet specific css file
  10. <liferay-portlet-app>
        <portlet>
            <portlet-name>minesweeper</portlet-name>
            <header-portlet-css>/css/minesweeper.css</header-portlet-css>
        </portlet>
    </liferay-portlet-app>
    
  11. And finally we make it visible in the dock menu "Add application" listing by defining the category for it in the liferay-display.xml file
  12. <display>
        <category name="example">
            <portlet id="minesweeper"/>
        </category>
    </display>
    
Continue to Part three: Wiring the backend with Spring Framework and Hibernate

References

http://struts.apache.org/2.0.14/docs/struts-2-portlet-tutorial.html
http://www.liferay.com/community/wiki/-/wiki/Main/liferay-portlet.xml+%285.2.0%29

Part one: POJOs behind the game

This is part one of the Minesweeper portlet tutorial.



The source classes can be found in the minesweeper/src/main directory. The Game class represents a single minesweeper game session. It has one GameBoard object which contains a two dimensional array of Cell objects. Each Cell object represents a square in the game board.
public class Game {
    public long score = 0;
    private GameBoard gameBoard;
    private GameState gameState = GameState.ACTIVE;
}

public class GameBoard {
    private Cell[][] cells;
}

public class Cell {
    private GameBoard gameBoard;
    private boolean mine;
    private int x;
    private int y;
    private boolean clicked;
}

Handling the click

The main functionality of the Game class is the handling of the click event. Depending on the clicked cell the click can result in:
  • Uncovering a number, which tells how many mines lay hidden in the eight surrounding cells.
  • Uncovering an empty cell, and revealing all empty cells in the area.
  • Uncovering a mine, and the game is lost.
  • Uncovering the last clear cell, and winning the game.
The Game object delegates the click event to GameBoard and handles the losing and winning of the game.
public class Game {
   public void click(int x, int y) {
        Cell cell = gameBoard.click(x,y);
        if (cell.isMine()) {
            gameState = GameState.LOST;
            gameBoard.clickAll();
        } else if (gameBoard.isAllCleared()) {
            gameState = GameState.WIN;
            score = MAX_TIME - (System.currentTimeMillis() - startTime);
            if (score < 0) {
                score = 0;
            }
            gameBoard.clickAll();
        }
    }
}
The GameBoard delegates the click to the corresponding Cell object and handles the special case of uncovering all empty cells in the area if the cell is empty (count == 0).
public class GameBoard {
    public Cell click(int x, int y) {
        Cell cell = get(x, y);
        click(cell);
        return cell;
    }

    protected void click(Cell cell) {
        if (!cell.isClicked()) {
            cell.click();
            if (!cell.isMine()) {
                int count = cell.countNearMines();
                if (count == 0) {
                    click(listNear(cell));
                }
            }
        }
    }

    protected void click(Collection<Cell> cells) {
        for (Cell cell : cells) {
            click(cell);
        }
    }
}
The Cell just updates its state to clicked.
public class Cell {
    public void click() {
        clicked = true;        
    }
}

Generating the game board html

The Cell has methods which are used to generate the html for the game board.
public class Cell {
    public String getLabel() {
        if (clicked) {
            if (mine || countNearMines() == 0) {
                return "";
            } else {
                return String.valueOf(countNearMines());
            }
        } else {
            return "";
        }
    }

    public String getCssClass() {
        if (clicked) {
            StringBuilder builder = new StringBuilder();
            builder.append("clicked ");
            if (mine) {
                builder.append("mine");
            } else {
                builder.append("near-mines-" + countNearMines());
            }
            return builder.toString();
        } else {
            return "not-clicked";
        }
    }
}
These methods are used in the freemarker template _game_board.ftl.
<td class="${cell.cssClass}" xloc="${cell.x?c}" yloc="${cell.y?c}" >${cell.label}</td>
The actual style is defined in the minesweeper.css file.

Continue to Part two: Making the portlet with Struts2

Minesweeper portlet tutorial

This tutorial illustrates the technologies I use in liferay portlet development. It's a portlet version of the minesweeper game and you can download the sources here. I have broken the tutorial to the following parts:

I would recommend that you build and run the portlet before reading the other parts of this tutorial.

Building and running the tutorial

To build and run this tutorial you will need:
The following instructions assume that you have successfully installed the above listed items on your local environment.
  1. Download the sources from here
  2. Extract the minesweeper.zip archive
  3. In MySQL create a new database with the name minesweeper
  4. CREATE DATABASE minesweeper;
    
  5. Edit the minesweeper/src/main/resources/db.properties to set the right connection settings for your local MySQL instance
  6. url=jdbc:mysql://localhost:3306/minesweeper
    username=root
    password=
    
  7. Start your Liferay portal instance
  8. Edit the todir attribute in the following line in the minesweeper/pom.xml to point to the deploy directory under your Liferay directory:
  9. <copy todir="C:\liferay-portal-5.2.3\deploy" file="${project.build.directory}/${project.build.finalName}.war"/>
    
  10. In the minesweeper directory run following maven command:
  11. mvn integration-test
  12. This should build and deploy the minesweeper.war. Check the Liferay output for the following lines to verify that the portlet was deployed successfully:
  13. INFO  [PortletHotDeployListener:346] 1 portlet for minesweeper is available for use
  14. Open your browser at http://localhost:8080 and login
  15. From the top right dock menu choose "Add application"
  16. Select the example category and click "Add" for Minesweeper portlet
  17. Now the portlet is ready to go!
Continue to Part one: POJOs behind the game

References

http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Quick+Installation+Instructions
http://maven.apache.org/download.html
http://dev.mysql.com/doc/refman/5.1/en/installing.html
http://en.wikipedia.org/wiki/Minesweeper_%28computer_game%29