-
Notifications
You must be signed in to change notification settings - Fork 0
Step 5 actions
Purpose: in this step we will develop add, update and delete actions.
cd ~/devel/apps
git clone git://github.com/opensas/play-demo.git
cd play-demo
git checkout origin/05-actions
First we'll create an action that takes care of finding the desired event and delete it from the database.
controllers/Application.java
public static void delete(Long id) {
Event event = Event.findById(id);
event.delete();
list();
}
Then we'll add a link to the list template
views/Application/list.html
[...]
<td>${event.place}</td>
<td>${event?.date?.format('MM-dd-yyyy HH:mm')}</td>
<td>#{a @delete(event.id)}<img src="@{'/public/images/delete.jpg'}" />#{/a}</td>
</tr>
[...]
Now you can delete events.
We can make it even better by deleting the event via an ajax call. That is, instead of reloading the whole page, we will just call the delete action an then remove the row from the web page.
In list template we call a javascript function that will take care of the ajax call
[...]
<td>${event.place}</td>
<td>${event?.date?.format('dd-MM-yyyy HH:mm')}</td>
<td><a href="javascript:del(${event.id})"><img src="@{'/public/images/delete.jpg'}" /></a></td>
</tr>
[...]
and at the end of list.html we will add the function
[...]
<script>
function del(id) {
$.ajax( {
type: "DELETE",
url: '/event/' + id
});
$("#row_" + id).fadeOut(1000);
}
</script>
You also have to specify the route in routes file, just add
[...]
DELETE /event/{id} Application.delete
[...]
Go ahead and try it. If you use firebug or any other utility that shows you http messages, you'll see that a DELETE call is made to the /event/xx url.
But there's a problem with this approach. If we latter decide to change the url routing to, let's say, events/delete/{id}, we would manually look for the places where we used that url. Play gives us a solution for that with the jsAction tag, returns a JavaScript function which constructs a URL based on a server action and free variables. For more info check here.
Our function would be like this
[...]
<script>
function del(id) {
var urlBuilder = #{jsAction @delete(':id') /}
var url = urlBuilder( { "id": id} );
$.ajax( {
type: "DELETE",
url: url
});
$("#row_" + id).fadeOut(1000);
}
</script>
The sharp reader might have noticed that the http delete method is harcoded, and that if we later change this from the routes file we would have to update every page anyway. We asked it at play framework google group and in hours there was a pull request with this new functionality ready to be integrated in the framework. That speaks a lot about play community's willingness to help other people and improve the framework.
So with the new tag contributed by play community, the script would end up like this
[...]
<script>
function del(id) {
var router = #{jsRoute @delete(':id') /}
$.ajax( {
type: router.method,
url: router.url( {"id": id} );
});
$("#row_" + id).fadeOut(1000);
}
</script>
To use this functionality you would have to patch the framework yourself, which is not as difficult as it sounds, or just wait for the next play release.
Now we will develop two actions, one is in charge of setting up the form to create or update an event, and the other will save the information to the database.
controllers.Application
[...]
public static void form(Long id) {
final Event event;
if (id==null) {
event = new Event();
} else {
event = Event.findById(id);
}
render(event);
}
public static void save(@Valid Event event) {
if (validation.hasErrors()) {
render("@form", event);
}
event.save();
flash.success("event successfully saved!");
list();
}
[...]
Now we will create the template for the form action
views/Application/form.html
#{extends 'main.html' /}
#{set title:'Events' /}
#{ifErrors}
<p>errors found!</p>
#{/ifErrors}
#{form @save()}
<input type="hidden" name="event.id" value="${event.id}" />
name: <input name="event.name" value="${event.name}" /> <br />
<span>#{error 'event.name' /}</span><br /><br />
type: <input name="event.type.id" value="${event.type?.id}" /> <br />
<span>#{error 'event.type' /}</span><br /><br />
place: <input name="event.place" value="${event.place}" /> <br />
<span>#{error 'event.place' /}</span><br /><br />
date: <input name="event.date" value="${event.date?.format('MM-dd-yyyy HH:mm')}" /> <br />
<span>#{error 'event.date' /}</span><br /><br />
<br />
<input type="submit" value="Save changes" /> or #{a @list()}cancel#{/a}
#{/form}
Pay attention to the names of each input. Play automatically binds each value posted within the http message, with the corresponding property of the Event model based on it's name. Moreover, in the case of an update, it detects the event.id input, loads the Event from database, and updates it with the values posted.
finally we need to add a link to edit an event in list template
views/Application/list.html
[...]
<tr id="row_${event.id}">
<td>${event.id}</td>
<td>#{a @form(event.id)}${event.name}#{/a}</td>
<td>${event.type.name}</td>
<td>${event.place}</td>
[...]
and a button to create a new one
[...]
<div class="list-actions">
#{a @form(), class:'btn primary'}new event#{/a}
#{a @loadFromYamlFile(), class:'btn'}load from yaml#{/a}
</div>
[...]
For selecting the event type we will build a combo box. Replace the event's type input box in list.html with the following:
type:
<select name="event.type.id">
<option value=""${event.type==null ? ' selected' : ''}>--select an option--</option>
#{list types, as:'type'}
<option value="${type.id}"${event.type?.id==type.id ? ' selected' : ' '}>${type.name}</option>
#{/list}
</select><br />
<span>#{error 'event.type' /}</span><br /><br />
and now, in the controller, we will pass the list of EventTypes to build the combo.
public static void form(Long id) {
final List<EventType> types = EventType.find("order by name").fetch();
[...]
render(event, types);
}
public static void save(@Valid Event event) {
if (validation.hasErrors()) {
final List<EventType> types = EventType.find("order by name").fetch();
render("@form", event, types);
}
[...]
You'll see that it's working, but there's a better solution. In order no to duplicate code, we can use an interceptor to add the list of event types to every request. Just add a private static method annotated with the @Before annotation and play will execute it before any action on that controller.
controllers.Application
[...]
@SuppressWarnings("unused")
@Before
private static void loadEventTypes() {
renderArgs.put("types", EventType.find("order by name").fetch());
}
Be sure to use play.mvc.Before annotation and not the one from org.junit
Now we have a fully functional CRUD page that allows us to list, create, update and deleted events.
Move on to Step 6 - styling form to make our pages look a bit better.