/* * This file is part of LibrePlan * * Copyright (C) 2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.web.common; import static org.libreplan.web.I18nHelper._; import org.apache.commons.lang3.StringUtils; import org.libreplan.business.common.IHumanIdentifiable; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.ValidationException; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.util.Clients; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Caption; import org.zkoss.zul.Window; /** * Abstract class defining common behavior for controllers of CRUD screens. * <br /> * * Those screens must define the following components: * <ul> * <li>{@link #messagesContainer}: A {@link Component} to show the different messages to users.</li> * <li>{@link #listWindow}: A {@link Window} where the list of elements is shown.</li> * <li>{@link #editWindow}: A {@link Window} with creation/edition form.</li> * </ul> * * @author Manuel Rego Casasnovas <rego@igalia.com> */ public abstract class BaseCRUDController<T extends IHumanIdentifiable> extends GenericForwardComposer<Component> { private OnlyOneVisible visibility; protected IMessagesForUser messagesForUser; private Component messagesContainer; protected Window listWindow; protected Window editWindow; public enum CRUDControllerState { LIST, CREATE, EDIT }; protected CRUDControllerState state = CRUDControllerState.LIST; /** * Call to super and do some extra stuff: * <br /> * <ul> * <li>Set "controller" variable to be used in .zul files.</li> * <li>Initialize {@link #messagesForUser}.</li> * <li>Show list view.</li> * </ul> * * @see GenericForwardComposer#doAfterCompose(org.zkoss.zk.ui.Component) */ @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); comp.setAttribute("controller", this); messagesForUser = new MessagesForUser(messagesContainer); listWindow.setTitle(_("{0} List", getPluralEntityType())); showListWindow(); } private OnlyOneVisible getVisibility() { if (visibility == null) visibility = new OnlyOneVisible(listWindow, editWindow); return visibility; } /** * Show list window and reload bindings. */ protected void showListWindow() { getVisibility().showOnly(listWindow); Util.reloadBindings(listWindow); } /** * Show edit form with different title depending on controller state and reload bindings. */ protected void showEditWindow() { getVisibility().showOnly(editWindow); updateWindowTitle(); Util.reloadBindings(editWindow); Clients.evalJavaScript("$('.focus-element').focus();"); } public final void updateWindowTitle() { T entityBeingEdited = getEntityBeingEdited(); if (entityBeingEdited == null) { throw new IllegalStateException("You should be editing one entity in order to use this method"); } String title; String humanId = entityBeingEdited.getHumanId(); switch (state) { case CREATE: if (StringUtils.isEmpty(humanId)) title = _("Create {0}", getEntityType()); else title = _("Create {0}: {1}", getEntityType(), humanId); break; case EDIT: title = _("Edit {0}: {1}", getEntityType(), humanId); break; default: throw new IllegalStateException("You should be in creation or edition mode to use this method"); } ((Caption) editWindow.getFellow("caption")).setLabel(title); } /** * Returns the translated text to represent one entity type. * * @return Text representing one entity */ protected abstract String getEntityType(); /** * Returns the translated text to represent multiple entity types. * * @return Text representing several entities */ protected abstract String getPluralEntityType(); /** * Show list window and reload bindings there. */ public final void goToList() { state = CRUDControllerState.LIST; showListWindow(); } /** * Show create form. * Delegate in {@link #initCreate()} that should be implemented in subclasses. */ public final void goToCreateForm() { state = CRUDControllerState.CREATE; initCreate(); showEditWindow(); } /** * Performs needed operations to initialize the creation of a new entity. */ protected abstract void initCreate(); /** * Show edit form for entity passed as parameter. * Delegate in initEdit(entity) that should be implemented in subclasses. * * @param entity * Entity to be edited */ public final void goToEditForm(T entity) { state = CRUDControllerState.EDIT; initEdit(entity); showEditWindow(); } /** * Performs needed operations to initialize the edition of a new entity. * * @param entity * Entity to be edited */ protected abstract void initEdit(T entity); /** * Save current form and go to list view. * Delegate in {@link #save()} that should be implemented in subclasses. */ public final void saveAndExit() { try { saveCommonActions(); goToList(); } catch (ValidationException e) { messagesForUser.showInvalidValues(e); } } /** * Common save actions: * <br /> * <ul> * <li>Delegate in {@link #beforeSaving()} that could be implemented if needed in subclasses.</li> * <li>Use {@link ConstraintChecker} to validate form.</li> * <li>Delegate in {@link #save()} that should be implemented in subclasses.</li> * <li>Show message to user.</li> * </ul> * * @throws ValidationException * If form is not valid or save has any validation problem */ private void saveCommonActions() throws ValidationException { beforeSaving(); messagesForUser.clearMessages(); save(); messagesForUser.showMessage(Level.INFO, _("{0} \"{1}\" saved", getEntityType(), getEntityBeingEdited().getHumanId())); } /** * Save current form and continue in edition view. * Delegate in {@link #save()} that should be implemented in subclasses. */ public final void saveAndContinue() { try { saveCommonActions(); goToEditForm(getEntityBeingEdited()); } catch (ValidationException e) { messagesForUser.showInvalidValues(e); } } /** * Performs additional operations before saving (usually do some checks or generate codes of related entities). * * Default behavior use {@link ConstraintChecker} to see if * {@link #editWindow} is valid, however it could be overridden if needed. */ protected void beforeSaving() throws ValidationException { ConstraintChecker.isValid(editWindow); } /** * Performs actions to save current form. * * @throws ValidationException * If entity is not valid */ protected abstract void save() throws ValidationException; /** * Returns entity being edited in the form. * * @return Current entity being edited */ protected abstract T getEntityBeingEdited(); /** * Close form and go to list view. * Delegate in {@link #cancel()} that could be implemented in subclasses if needed. */ public final void cancelForm() { cancel(); goToList(); } /** * Performs needed actions to cancel edition. * * Default behavior do nothing, however it could be overridden if needed. */ protected void cancel() { // Do nothing } /** * First call beforeDeleting(entity) in order to perform some checks before trying to delete if needed. * Then show a dialog asking for confirmation to user and if ok remove entity passed as parameter. * Delegate in delete(entity) that should be implemented in subclasses. * * @param entity * Entity to be removed */ public final void confirmDelete(T entity) { if (!beforeDeleting(entity)) return; try { if (Messagebox.show( _("Delete {0} \"{1}\". Are you sure?", getEntityType(), entity.getHumanId()), _("Confirm"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION) == Messagebox.OK) { delete(entity); messagesForUser.showMessage( Level.INFO, _("{0} \"{1}\" deleted", getEntityType(), entity.getHumanId())); Util.reloadBindings(listWindow); } } catch (InstanceNotFoundException ie) { messagesForUser.showMessage( Level.ERROR, _("{0} \"{1}\" could not be deleted, it was already removed", getEntityType(), entity.getHumanId())); } } /** * Performs additional operations before deleting (usually check some wrong conditions before deleting). * * Default behavior do nothing, however it could be overridden if needed. * * @param entity * Entity to be removed * @return Return true if deletion can carry on */ protected boolean beforeDeleting(T entity) { return true; } /** * Performs actions needed to remove entity passed as parameter. * * @param entity * Entity to be removed */ protected abstract void delete(T entity) throws InstanceNotFoundException; }