These are the new classes for Division & Sponsor:
package models;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import play.db.jpa.Model;
@Entity
public class Division extends Model
{
public String name;
@OneToMany(cascade=CascadeType.ALL)
public List<Club> members;
public Division(String name)
{
this.name = name;
members = new ArrayList<Club>();
}
public void addClub(Club club)
{
members.add(club);
}
public String toString()
{
return name;
}
public static Division findByName(String name)
{
return find("name", name).first();
}
}
package models;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import play.db.jpa.Model;
@Entity
public class Sponsor extends Model
{
public String name;
@ManyToMany (mappedBy="sponsors")
public List<Club> support;
public Sponsor(String name)
{
this.name = name;
support = new ArrayList<Club>();
}
public void addSuport(Club club)
{
support.add(club);
}
public String toString()
{
return name;
}
}
//...
@ManyToMany
public List<Sponsor> sponsors;
//...
public Club(String name)
{
this.name = name;
this.players = new ArrayList<Player>();
this.sponsors = new ArrayList<Sponsor>();
}
public void addSponsor(Sponsor company)
{
sponsors.add(company);
}
If you are still using git, Commit these with the message "Division + Sponsor + modification to Club"
We can significantly simplify aspects of the testing if we invest some time in setting up test objects in yaml first.
Open 'test/data.yaml'. Note this is a different version of the yaml file from the one in conf. Replace its contents with the following:
Club(dunmore):
name: dunmore
Club(tramore):
name: tramore
Club(fenor):
name: fenor
Player(jim):
name: jim
club: dunmore
Player(mary):
name: mary
club: dunmore
Player(sam):
name: sam
club: tramore
Player(john):
name: john
club: tramore
Player(mike):
name: mike
club: fenor
Player(linda):
name: linda
club: fenor
Division(senior):
name: senior
members:
- tramore
- dunmore
Division(junior):
name: junior
members:
- fenor
Sponsor(newsagent):
name: newsagent
Sponsor(pub):
name: pub
Now bring in a new test class that will exercise this model:
import org.junit.*;
import java.util.*;
import play.Logger;
import play.test.*;
import models.*;
public class ComprehensiveTest extends UnitTest
{
public static void loadSponsorships()
{
Club tramore = Club.find("byName", "tramore").first();
Club dunmore = Club.find("byName", "dunmore").first();
Sponsor newsagent = Sponsor.find("byName", "newsagent").first();
tramore.addSponsor(newsagent);
dunmore.addSponsor(newsagent);
newsagent.addSuport(tramore);
newsagent.addSuport(dunmore);
tramore.save();
dunmore.save();
newsagent.save();
}
@Before
public void setup()
{
Fixtures.loadModels("data.yml");
loadSponsorships();
}
@After
public void teardown()
{
Fixtures.deleteAllModels();
}
@Test
public void testPlayerClubLong()
{
Player jim;
Club dunmore;
jim = Player.find("byName", "jim").first();
assertNotNull(jim);
assertEquals(jim.name, "jim");
dunmore = jim.club;
assertEquals("dunmore", dunmore.name);
dunmore = Club.find("byName", "dunmore").first();
assertNotNull(dunmore);
assertEquals("dunmore", dunmore.name);
assertEquals(2, dunmore.players.size());
Player p1 = dunmore.players.get(0);
assertTrue (p1.name.equals("jim") || p1.name.equals("mary"));
Player p2 = dunmore.players.get(1);
assertTrue (p2.name.equals("jim") || p2.name.equals("mary"));
}
@Test
public void testDivisionClubLong()
{
Division senior = Division.find("byName", "senior").first();
assertNotNull(senior);
assertEquals(2, senior.members.size());
Club c1 = senior.members.get(0);
Club c2 = senior.members.get(1);
assertTrue (c1.name.equals("tramore") || c1.name.equals("dunmore"));
assertTrue (c2.name.equals("tramore") || c2.name.equals("dunmore"));
}
//----------------------------------------------------------------------
@Test
public void testPlayerClub()
{
Club dunmore = Club.find("byName", "dunmore").first();
Player jim = Player.find("byName", "jim").first();
Player mary = Player.find("byName", "mary").first();
assertNotNull(mary);
assertTrue (dunmore.players.contains(jim));
assertTrue (dunmore.players.contains(mary));
}
@Test
public void testDivisionClub()
{
Division senior = Division.find("byName", "senior").first();
Club dunmore = Club.find("byName", "dunmore").first();
Club tramore = Club.find("byName", "tramore").first();
assertTrue (senior.members.contains(dunmore));
assertTrue (senior.members.contains(tramore));
}
@Test
public void testClubSponsorShort()
{
Sponsor newsagent = Sponsor.find("byName", "newsagent").first();
Club dunmore = Club.find("byName", "dunmore").first();
Club tramore = Club.find("byName", "tramore").first();
assertTrue(newsagent.support.contains(dunmore));
assertTrue(newsagent.support.contains(tramore));
assertTrue(dunmore.sponsors.contains(newsagent));
assertTrue(tramore.sponsors.contains(newsagent));
}
@Test
public void testEditPlayerClub()
{
Club dunmore = Club.find("byName", "dunmore").first();
Player jim = Player.find("byName", "jim").first();
Player mary = Player.find("byName", "mary").first();
dunmore.players.remove(mary);
mary.delete();
dunmore.save();
assertEquals (dunmore.players.size(), 1);
assertTrue (dunmore.players.contains(jim));
assertEquals(0, Player.find("byName", "mary").fetch().size());
Player sara = new Player("sara");
dunmore.addPlayer(sara);
dunmore.save();
assertEquals (dunmore.players.size(), 2);
}
@Test
public void testEditClubSponsor()
{
Sponsor newsagent = Sponsor.find("byName", "newsagent").first();
Club dunmore = Club.find("byName", "dunmore").first();
assertEquals(2, newsagent.support.size());
newsagent.support.remove(dunmore);
dunmore.sponsors.remove(newsagent);
newsagent.save();
dunmore.save();
assertEquals(1, newsagent.support.size());
}
}
Run the app in test mode, and run this new test suite. All of these tests should run.
Commit these changes with the message 'Comprehensive tests for new model objects'
Incorporate all of these new artifacts:
package controllers;
import java.util.List;
import models.Player;
import play.mvc.Controller;
public class PlayersController extends Controller
{
public static void index()
{
List<Player> players = Player.findAll();
render (players);
}
}
package controllers;
import java.util.List;
import models.Club;
import play.mvc.Controller;
public class ClubsController extends Controller
{
public static void index()
{
List<Club> clubs = Club.findAll();
render (clubs);
}
}
package controllers;
import java.util.List;
import models.Sponsor;
import play.mvc.Controller;
public class SponsorsController extends Controller
{
public static void index()
{
List<Sponsor> sponsors = Sponsor.findAll();
render (sponsors);
}
}
package controllers;
import java.util.List;
import models.Division;
import play.mvc.Controller;
public class DivisionController extends Controller
{
public static void index()
{
List<Division> divisions = Division.findAll();
render (divisions);
}
}
You will also need the corresponding views:
#{extends 'main.html' /}
#{set title:'Home' /}
#{include 'nav/open.html' /}
<li> <a href="/divisions"> Divisions </a></li>
<li> <a href="/clubs"> Clubs </a></li>
<li class="active"> <a href="/players"> Players </a></li>
<li> <a href="/sponsors"> Sponsors </a></li>
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Players</h1>
</div>
<div class="row">
<div class="span8">
<div class="well">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#{list items:players, as:'player'}
<tr>
<td>${player.name}</td>
<td></td>
<td></td>
</tr>
#{/list}
</tbody>
</table>
</div>
</div>
</div>
</div>
#{extends 'main.html' /}
#{set title:'Home' /}
#{include 'nav/open.html' /}
<li> <a href="/divisions"> Divisions </a></li>
<li class="active"> <a href="/clubs"> Clubs </a></li>
<li> <a href="/players"> Players </a></li>
<li> <a href="/sponsors"> Sponsors </a></li>
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Clubs</h1>
</div>
<div class="row">
<div class="span8">
<div class="well">
<table class="table">
<thead>
<tr>
<th>Club</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#{list items:clubs, as:'club'}
<tr>
<td>${club.name}</td>
<td></td>
<td></td>
</tr>
#{/list}
</tbody>
</table>
</div>
</div>
</div>
</div>
#{extends 'main.html' /}
#{set title:'Home' /}
#{include 'nav/open.html' /}
<li> <a href="/divisions"> Divisions </a></li>
<li> <a href="/clubs"> Clubs </a></li>
<li> <a href="/players"> Players </a></li>
<li class="active"> <a href="/sponsors"> Sponsors </a></li>
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Sponsors</h1>
</div>
<div class="row">
<div class="span8">
<div class="well">
<table class="table">
<thead>
<tr>
<th>Sponsor</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#{list items:sponsors, as:'sponsor'}
<tr>
<td>${sponsor.name}</td>
<td></td>
<td></td>
</tr>
#{/list}
</tbody>
</table>
</div>
</div>
</div>
</div>
#{extends 'main.html' /}
#{set title:'Home' /}
#{include 'nav/open.html' /}
<li class="active"> <a href="/divisions"> Divisions </a></li>
<li> <a href="/clubs"> Clubs </a></li>
<li> <a href="/players"> Players </a></li>
<li> <a href="/sponsors"> Sponsors </a></li>
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Divisions</h1>
</div>
<div class="row">
<div class="span8">
<div class="well">
<table class="table">
<thead>
<tr>
<th>Division</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#{list items:divisions, as:'division'}
<tr>
<td>${division.name}</td>
<td></td>
<td></td>
</tr>
#{/list}
</tbody>
</table>
</div>
</div>
</div>
</div>
These templates need a folder called 'nav' to be created in 'views' - and it must contain:
<nav class="navbar navbar-default" role="navigation">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
</ul>
</div>
</nav>
Now incorporate the following routes:
# Home page
GET / DivisionController.index
GET /divisions DivisionController.index
GET /clubs ClubsController.index
GET /players PlayersController.index
GET /sponsors SponsorsController.index
In addition, as we are powering up the ui, we will need to make sure data source in application.conf is configured:
db=mem
Save everything and run (not in test mode). Browse to
It views will be largely bank and unformatted.
Commit these changes, nevertheless, using the string "Controllers/Views for all models"
There is a data.yaml file in your test folder. Copy this to the conf folder (there will now me two files of this name).
Create a class called 'BootStrap.java' in you 'app' folder, and replace its content with this source here:
import models.Division;
import play.jobs.*;
import play.test.*;
@OnApplicationStart
public class Bootstrap extends Job<Object>
{
public void doJob()
{
if (Division.count() == 0)
{
Fixtures.loadModels("data.yml");
}
}
}
Run the app again, and you should start to see some data.
The views are constructed expecting to have twitter bootstrap loaded. We will do this now.
Replace your 'views/main.html' with the following:
<!DOCTYPE html>
<html>
<head>
<title>#{get 'title' /}</title>
<meta charset="${_response_encoding}">
<link rel="stylesheet" href="@{'/public/bootstrap/css/bootstrap.min.css'}">
<script src="@{'/public/javascripts/jquery-1.6.4.min.js'}"></script>
<script src="@{'/public/bootstrap/js/bootstrap.min.js'}"></script>
<link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">
<style>
body
{
padding-top: 60px;
}
</style>
</head>
<body>
#{doLayout /}
</body>
</html>
Note that we need the bootstrap files in public for this to work. Locate these files and copy them in. Bootstrap has been updated to version 3 since you may have used it last. Locate the v3 version from the web and incorporate.
Reload the app - you should have the menus/views as expected.
Commit this with the message 'Test Data + bootstrap files'
Currently UI for the model is read only - and we can only see each model in isolation (we cant see relationships). We would like to make the relationships visible.
Replace the <table>
element of the following views with this version:
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Club </th>
</tr>
</thead>
<tbody>
#{list items:players, as:'player'}
<tr>
<td>${player.name}</td>
<td>${player.club.name}</td>
</tr>
#{/list}
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th>Club</th>
</tr>
</thead>
<tbody>
#{list items:clubs, as:'club'}
<tr>
<td>${club.name}</td>
<td>
<table class="table">
<tr>
#{list items:club.players, as:'player'}
<td>${player.name}</td> </tr>
#{/list}
</tr>
</table>
</td>
</tr>
#{/list}
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th>Division</th>
<th>Club </th>
</tr>
</thead>
<tbody>
#{list items:divisions, as:'division'}
<tr>
<td>${division.name}</td>
<td>
<table class="table">
<tr>
#{list items:division.members, as:'club'}
<td>${club.name}</td> </tr>
#{/list}
</tr>
</table>
</td>
</tr>
#{/list}
</tbody
See if you can make sense of the above templates. Save everything and reload. You should be able to see what players belong to which clubs, and also what divisions the clubs are in.
Commit these changes with a message 'show Division/Club/Player relationships'
Introduce the following method into the controllers/PlayersController:
public static void delete(Long id)
{
Player player = Player.findById(id);
if (player != null)
{
if (player.club != null)
{
player.club.removePlayer(player);
player.club.save();
}
player.delete();
}
index();
}
Which requires this method in models/Club.java
public void removePlayer(Player player)
{
players.remove(player);
}
Now change the loop in the 'views/PlayersController/index.html' file:
#{list items:players, as:'player'}
<tr>
<td>${player.name}</td>
<td>${player.club.name}</td>
<td> <a class="btn btn-danger btn-mini" href="@{PlayersController.delete(player.id)}">Delete</a> </td>
</tr>
#{/list}
(just one line changed in the above).
Run the app - players now have a delete button, which should work as expected.
Commit this modification with a 'Delete Player Feature' commit message.
Create the following new methods in PlayersController:
public static void newPlayer()
{
List<Club> clubs = Club.findAll();
render(clubs);
}
public static void createPlayer(String name, String club)
{
Logger.info("Name: " + name + ": Club: " + club);
Player player = new Player (name);
Club theClub = Club.findByName(club);
if (theClub != null)
{
theClub.addPlayer(player);
theClub.save();
}
else
{
player.save();
}
index();
These imports will be needed:
import java.util.List;
import models.Club;
import play.Logger;
In PlayersController/index.html, add this at the end inside the closing <div>
</div>
<a class="btn btn-primary" href="@{PlayersController.newPlayer()}">New Player</a>
</div>
Now create a new html file in the views/PlayersController folder called 'newplayer.html'
#{extends 'main.html' /}
#{set title:'Add Player' /}
#{include 'nav/open.html' /}
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Create new Player</h1>
</div>
<div class="alert alert-info hidden-phone">
<a class="close" data-dismiss="alert">x</a>
<b>Enter name and select club of new player</b>
</div>
<form action="@{PlayersController.createPlayer()}" method="POST">
<div class="well">
<div class="row">
<div class="span4">
<div class="control-group">
<label class="control-label">Player name</label>
<div class="controls">
<input name="name" type="text" class="input-medium input-block-level">
</div>
</div>
</div>
<div class="span7">
<div class="control-group">
<label class="control-label">Club</label>
<div class="controls">
<select name="club">
#{list items:clubs, as:'club'}
<option>${club.name}</option>
#{/list}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="span2">
<button class="btn btn-block">Save</button>
</div>
</form>
<div class="span2">
<a href="@{PlayersController.index()}" class="btn btn-block">Cancel</a>
</div>
</div>
Save everything - and verify that you can now add players using the app.
Commit all changes with the message 'New Player Feature'
A new version of ClubsController which has several new methods:
package controllers;
import java.util.List;
import models.Club;
import models.Division;
import play.Logger;
import play.mvc.Controller;
public class ClubsController extends Controller
{
public static void index()
{
List<Club> clubs = Club.findAll();
render (clubs);
}
public static void newClub()
{
List<Division> divisions = Division.findAll();
render(divisions);
}
public static void delete(Long id)
{
Club club = Club.findById(id);
if (club != null)
{
Logger.info("Trying to delete " + club.name);
List<Division> divisions = Division.findAll();
for (Division division : divisions)
{
if (division.members.contains(club))
{
division.members.remove(club);
division.save();
Logger.info ("removing club from division");
}
}
club.delete();
}
index();
}
public static void createClub (String name, String division)
{
Logger.info("name: " + name + "Division: " + division);
Club club = new Club(name);
club.save();
Division theDivision = Division.findByName(division);
if (theDivision != null)
{
theDivision.addClub(club);
theDivision.save();
}
index();
}
}
~~~~
A new version of the ClubsController/index.html:
~~~html
#{extends 'main.html' /}
#{set title:'Home' /}
#{include 'nav/open.html' /}
<li> <a href="/divisions"> Divisions </a></li>
<li class="active"> <a href="/clubs"> Clubs </a></li>
<li> <a href="/players"> Players </a></li>
<li> <a href="/sponsors"> Sponsors </a></li>
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Clubs</h1>
</div>
<div class="row">
<div class="span8">
<div class="well">
<table class="table">
<thead>
<tr>
<th>Club</th>
<th>Players</th>
</tr>
</thead>
<tbody>
#{list items:clubs, as:'club'}
<tr>
<td>${club.name}</td>
<td>
<table class="table">
<tr>
#{list items:club.players, as:'player'}
<td>${player.name}</td> </tr>
#{/list}
</tr>
</table>
</td>
<td> <a class="btn btn-danger btn-mini" href="@{ClubsController.delete(club.id)}">Delete</a> </td>
</tr>
#{/list}
</tbody>
</table>
</div>
</div>
</div>
<a class="btn btn-primary" href="@{ClubsController.newClub()}">New Club</a>
</div>
and a new view - ClubsController/newclub.html:
#{extends 'main.html' /}
#{set title:'Add Player' /}
#{include 'nav/open.html' /}
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Create new Club</h1>
</div>
<div class="alert alert-info hidden-phone">
<a class="close" data-dismiss="alert">x</a>
<b>Enter name of club</b>
</div>
<form action="@{ClubsController.createClub()}" method="POST">
<div class="well">
<div class="row">
<div class="span4">
<div class="control-group">
<label class="control-label">Club name</label>
<div class="controls">
<input name="name" type="text" class="input-medium input-block-level">
</div>
</div>
</div>
<div class="span7">
<div class="control-group">
<label class="control-label">Division</label>
<div class="controls">
<select name="division">
#{list items:divisions, as:'division'}
<option>${division.name}</option>
#{/list}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="span2">
<button class="btn btn-block">Save</button>
</div>
</form>
<div class="span2">
<a href="@{ClubsController.index()}" class="btn btn-block">Cancel</a>
</div>
</div>
Verify that you can now add a club using the app.
Commit these changes with the message: "Create and Delete Clubs feature"
Introduce the following two methods into the PlayersController:
public static void changePlayer(Long id, String name, Long club)
{
Player player = Player.findById(id);
if (player != null)
{
player.name = name;
Club theClub = Club.findById(club);
player.club = theClub;
player.save();
}
index();
}
public static void editPlayer(Long id)
{
Player player = Player.findById(id);
List<Club> clubs = Club.findAll();
Integer clubIndex = clubs.indexOf(player.club);
clubIndex++;
render(player, clubs, clubIndex);
}
Incorporate the following new view in views/PlayersController/editplayer.html
#{extends 'main.html' /}
#{set title:'Add Player' /}
#{include 'nav/open.html' /}
#{include 'nav/close.html' /}
<div class="container">
<div class="page-header">
<h1>Edit Player</h1>
</div>
<div class="alert alert-info hidden-phone">
<a class="close" data-dismiss="alert">x</a>
<b>Change Player name or Club affiliation</b>
</div>
#{form @PlayersController.changePlayer(player.id), method:'post'}
<div class="well">
<div class="row">
<div class="span4">
<div class="control-group">
<label class="control-label">Player name</label>
<div class="controls">
<input name="name" type="text" class="input-medium input-block-level" value="${player.name}">
</div>
</div>
</div>
<div class="span7">
<div class="control-group">
<label class="control-label">Club</label>
<div class="controls">
#{select 'club', items:clubs, value:clubIndex, valueProperty:'id'}
#{/select}
</div>
</div>
</div>
</div>
</div>
<div class="span2">
<button class="btn btn-block">Save</button>
</div>
#{/form}
<div class="span2">
<a href="@{PlayersController.index()}" class="btn btn-block">Cancel</a>
</div>
</div>
In views/PlayersController/index.html add a new 'edit' button after the 'delete' button:
<td> <a class="btn btn-info btn-mini" href="@{PlayersController.editPlayer(player.id)}">Edit</a> </td>
You should now be able to edit the and change player details.
Commit these changes as "Edit Player Feature"
Completed version:
Edit the file ComprehensiveTest.java test case, and comment our all of the @Test annotations except one (say the first one). This will remove all tests from the test runner.
Also, comment out the call to 'deleteAllModels()' from tearDown()
@After
public void teardown()
{
//Fixtures.deleteAllModels();
}
Restart the app in test mode, and run just the comprehensive test (not the others).
While still in test mode, browse to:
You should be able to see a fully populate UI + Database. You may be interested in looking at each of the tables in turn on the database to get a feel for the mapping performed by JPA. A useful exercise would be to use pen & paper to draw the all the tables and their contents. Use ER mapping notation to show the table relationships. This could prove invaluable in debugging relationships you may be designing for your assignments.
Be sure to comment the test annotation back in when you finish.
The divisions and sponsors controllers/view are currently read-only. I.ee it is not possible to add or edit the entities. Using the Players and CLubs as examples, bring in the ability create/edit divisions and sponsors.
If you have made the switch to bootstrap 3 - then you might be interested in this new site with free bootstrap templates here:
It includes some very useful Full Site Templates:
See this demo here:
In particular, try the last link on a mobile device. If you dont have one connected, browse to that site using the Android Simulator.