Here we use BootStrap's Modal dialog.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
<h3>Modal</h3>
<noscript class="js-required">
${message:javascript_required}
</noscript>
<p>Here we use BootStrap's Modal dialog.</p>
<div class="eg">
<t:zone t:id="paneZone" id="modalExamplePaneZone">
<t:if test="isFunction('review')">
<t:beandisplay object="person"/>
<t:eventlink event="toUpdate" context="personId" async="true">Update...</t:eventlink>
</t:if>
</t:zone>
<t:zone t:id="modalZone" id="modalExampleModalZone">
<t:if test="isFunction('update')">
<div class="modal fade" tabindex="-1" role="dialog" id="${personUpdateModalId}" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<t:together.ajaxcomponentscrud.PersonUpdate t:id="personUpdate" personId="personId"/>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
</div>
</div>
</div>
</div>
</t:if>
</t:zone>
</div>
References:
<a href="http://getbootstrap.com/javascript/#modals">Bootstrap Modal</a>,
<a href="http://tapestry.apache.org/javascript.html">Tapestry JavaScript</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/javascript/JavaScriptSupport.html">JavaScriptSupport</a>,
<a href="http://requirejs.org">RequireJS</a>,
<a href="http://api.jquery.com">jQuery API</a>.<br/><br/>
<t:pagelink page="Index">Home</t:pagelink><br/><br/>
The source for PersonEditor is shown in the Smaller Components CRUD example.<br/>
The source for IPersonFinderServiceLocal and @EJB is shown in the Session Beans and @EJB examples.<br/><br/>
<t:tabgroup>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/javascript/Modal.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/javascript/Modal.java"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/modules/simple-modal.js"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/modal.css"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/together/ajaxcomponentscrud/PersonUpdate.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/together/ajaxcomponentscrud/PersonUpdate.java"/>
</t:tabgroup>
</body>
</html>
package jumpstart.web.pages.examples.javascript;
import javax.ejb.EJB;
import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
import org.apache.tapestry5.services.ajax.JavaScriptCallback;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@Import(stylesheet = "css/examples/modal.css")
public class Modal {
public enum Function {
REVIEW, UPDATE;
}
// The activation context
@Property
private Long personId;
// Screen fields
@Property
private Person person;
@Property
private String personUpdateModalId = "personUpdateModal";
// Work fields
private Function function;
// Generally useful bits and pieces
@InjectComponent
private Zone paneZone;
@InjectComponent
private Zone modalZone;
@EJB
private IPersonFinderServiceLocal personFinderService;
@Inject
private Request request;
@Inject
private JavaScriptSupport javaScriptSupport;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
// The code
void onActivate(Long personId) {
this.personId = personId;
}
Long onPassivate() {
return personId;
}
public void setupRender() {
function = Function.REVIEW;
populateBody();
}
void onToUpdate(Long personId) {
this.personId = personId;
function = Function.UPDATE;
if (request.isXHR()) {
ajaxResponseRenderer.addCallback(makeScriptToShowModal());
ajaxResponseRenderer.addRender(modalZone);
}
}
void onCanceledFromPersonUpdate(Long personId) {
this.personId = personId;
function = Function.REVIEW;
populateBody();
if (request.isXHR()) {
ajaxResponseRenderer.addCallback(makeScriptToHideModal());
ajaxResponseRenderer.addRender(paneZone);
}
}
void onUpdatedFromPersonUpdate(Person person) {
this.personId = person.getId();
function = Function.REVIEW;
populateBody();
if (request.isXHR()) {
ajaxResponseRenderer.addCallback(makeScriptToHideModal());
ajaxResponseRenderer.addRender(paneZone);
}
}
public void populateBody() {
// Get person with id 1 - ask business service to find it (from the
// database).
person = personFinderService.findPerson(1L);
if (person == null) {
throw new IllegalStateException("Database data has not been set up!");
}
}
private JavaScriptCallback makeScriptToShowModal() {
return new JavaScriptCallback() {
public void run(JavaScriptSupport javascriptSupport) {
javaScriptSupport.require("simple-modal").invoke("activate")
.with(personUpdateModalId, new JSONObject());
}
};
}
private JavaScriptCallback makeScriptToHideModal() {
return new JavaScriptCallback() {
public void run(JavaScriptSupport javascriptSupport) {
javaScriptSupport.require("simple-modal").invoke("hide").with(personUpdateModalId);
}
};
}
public boolean isFunction(Function function) {
return function == this.function;
}
}
define(["jquery", "bootstrap/modal"], function($) {
var activate = function(modalId, options) {
$('#' + modalId).modal(options);
}
var hide = function(modalId) {
var $modal = $('#' + modalId);
if ($modal.length > 0) {
// Hide will trigger removal.
$modal.modal('hide');
}
else {
// The modal's already gone, but the backdrop may still be there.
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
}
}
return {
activate : activate,
hide : hide
}
});
.eg {
margin: 20px 0;
padding: 14px;
border: 1px solid #ddd;
border-radius: 6px;
-webkit-border-radius: 6px;
-mox-border-radius: 6px;
}
.eg .modal .modal-dialog .close {
position: absolute;
top: 6px; right: 16px;
}
.eg .modal .modal-dialog .modal-body h1 {
text-align: center;
margin: 0 0 20px;
border-bottom: 1px solid #ddd;
font-size: 24px;
line-height: 40px;
}
.js-required {
color: red;
display: block;
margin-bottom: 14px;
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd" xmlns:p="tapestry:parameter">
<t:content>
<h1>Update</h1>
<t:zone t:id="formZone" id="formZone">
<t:form t:id="form" class="form-horizontal" validate="person" context="personId" async="true">
<t:errors globalOnly="true"/>
<t:if test="person">
<!-- If optimistic locking is not needed then comment out this next line. It works because Hidden fields are part of the submit. -->
<t:hidden value="person.version"/>
<t:together.smallercomponentscrud.PersonEditor person="person"/>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-4">
<t:submit value="Save" />
<t:eventlink event="cancel" context="personId" class="btn btn-default" async="true">Cancel</t:eventlink>
</div>
</div>
</t:if>
<t:if test="!person">
Person ${personId} does not exist.<br/><br/>
</t:if>
</t:form>
</t:zone>
</t:content>
</html>
package jumpstart.web.components.together.ajaxcomponentscrud;
import javax.ejb.EJB;
import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.business.domain.person.iface.IPersonManagerServiceLocal;
import jumpstart.util.ExceptionUtil;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
/**
* This component will trigger the following events on its container (which in this example is the page):
* {@link PersonUpdate#CANCELED}(Long personId), {@link PersonUpdate#UPDATED}(Long personId).
*/
// @Events is applied to a component solely to document what events it may
// trigger. It is not checked at runtime.
@Events({ PersonUpdate.CANCELED, PersonUpdate.UPDATED })
public class PersonUpdate {
public static final String CANCELED = "canceled";
public static final String UPDATED = "updated";
// Parameters
@Parameter(required = true)
@Property
private Long personId;
// Screen fields
@Property
private Person person;
// Generally useful bits and pieces
@EJB
private IPersonFinderServiceLocal personFinderService;
@EJB
private IPersonManagerServiceLocal personManagerService;
@InjectComponent
private Form form;
@InjectComponent
private Zone formZone;
@Inject
private Request request;
@Inject
private ComponentResources componentResources;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
// The code
boolean onCancel(Long personId) {
this.personId = personId;
componentResources.triggerEvent(CANCELED, new Object[] { personId }, null);
// We don't want the event to bubble up, so we return true to say we've
// handled it.
return true;
}
void onPrepareForRender() {
// If fresh start, make sure there's a Person object available.
if (form.isValid()) {
person = personFinderService.findPerson(personId);
// Handle null person in the template.
}
}
void onPrepareForSubmit(Long personId) {
this.personId = personId;
// Get objects for the form fields to overlay.
person = personFinderService.findPerson(personId);
if (person == null) {
form.recordError("Person has been deleted by another process.");
// Instantiate an empty person to avoid NPE in the Form.
person = new Person();
}
}
boolean onValidateFromForm() {
if (form.getHasErrors()) {
return true;
}
try {
person = personManagerService.changePerson(person);
}
catch (Exception e) {
// Display the cause. In a real system we would try harder to get a
// user-friendly message.
form.recordError(ExceptionUtil.getRootCauseMessage(e));
}
return true;
}
boolean onSuccess() {
// We want to tell our containing page explicitly what person we've updated, so we trigger a new event with a
// parameter. It will bubble up because we don't have a handler method for it.
componentResources.triggerEvent(UPDATED, new Object[] { person }, null);
// We don't want the original event to bubble up, so we return true to
// say we've handled it.
return true;
}
boolean onFailure() {
if (request.isXHR()) {
ajaxResponseRenderer.addRender(formZone);
}
// We don't want the event to bubble up, so we return true to say we've
// handled it.
return true;
}
}