/*
* #%L
* carewebframework
* %%
* Copyright (C) 2008 - 2016 Regenstrief Institute, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This Source Code Form is also subject to the terms of the Health-Related
* Additional Disclaimer of Warranty and Limitation of Liability available at
*
* http://www.carewebframework.org/licensing/disclaimer.
*
* #L%
*/
package org.carewebframework.ui;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.carewebframework.common.StrUtil;
import org.carewebframework.ui.zk.PopupDialog;
import org.carewebframework.ui.zk.PromptDialog;
import org.carewebframework.ui.zk.ZKUtil;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Button;
/**
* Base controller for creating/editing a domain object.
* <p>
* Some basic requirements:
* <ul>
* <li>Button with an id of <b>btnOK</b> for committing changes.</li>
* <li>Button with an id of <b>btnCancel</b> for canceling changes.</li>
* <li>Implement <b>populateControls</b> to populate input elements from domain object.</li>
* <li>Implement <b>populateDomainObject</b> to populate the domain object from input elements.</li>
* <li>Implement <b>commit</b> to commit changes to domain object.</li>
* <li>Optionally implement <b>hasRequired</b> to signal if all required inputs have been provided.
* </li>
* <li>Optionally implement <b>initControls</b> to provide custom initialization of input elements.
* </li>
* </ul>
*
* @param <T> Class of the domain object.
*/
public abstract class FormController<T> extends FrameworkController {
private static final long serialVersionUID = 1L;
private T domainObject;
private Component wrongValueTarget;
private final Set<Component> changeSet = new HashSet<>();
private String label_cancel_title = "@cwf.formcontroller.cancel.title";
private String label_cancel_message = "@cwf.formcontroller.cancel.message";
private String label_required_message = "@cwf.formcontroller.required.message";
// Start of auto-wired members
private Button btnOK;
// End of auto-wired members.
/**
* Creates and displays the form.
*
* @param form Url of the form.
* @param domainObject The domain object to be modified.
* @return True if changes were committed. False if canceled.
*/
protected static boolean execute(String form, Object domainObject) {
Map<Object, Object> args = new HashMap<>();
args.put("domainObject", domainObject);
Component dlg = PopupDialog.popup(form, args, true, false, true);
return !ZKUtil.getAttributeBoolean(dlg, "cancelled");
}
/**
* Populates the goal types and initializes the input elements with values from the step or
* goal. Also, wires change events for all input elements.
*/
@SuppressWarnings("unchecked")
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
domainObject = (T) arg.get("domainObject");
btnOK.setDisabled(true);
initControls();
Events.postEvent("onDeferredInit", root, null);
}
/**
* Deferred initialization (allows any model-based controls to fully render).
*/
public void onDeferredInit() {
populateControls(domainObject);
ZKUtil.focusFirst(root, true);
ZKUtil.wireChangeEvents(root, root, Events.ON_CHANGE);
}
/**
* Handler for change events from input elements.
*
* @param event Change event.
*/
public void onChange(Event event) {
changed(ZKUtil.getEventOrigin(event).getTarget());
}
/**
* Captures form closure from close icon.
*/
public void onClose() {
close(true);
}
/**
* Registers a component as changed.
*
* @param target The component whose input state changed.
*/
protected void changed(Component target) {
changeSet.add(target);
wrongValue(null, null);
btnOK.setDisabled(false);
}
/**
* Displays the validation error for a required element.
*
* @param target The target input element.
* @return Always false.
*/
protected boolean isMissing(Component target) {
wrongValue(target, label_required_message);
return false;
}
/**
* Clears any current validation error and displays a new validation error for the specified
* input element.
*
* @param target The target input element.
* @param message The validation error message.
*/
protected void wrongValue(Component target, String message) {
if (wrongValueTarget != null) {
Clients.clearWrongValue(wrongValueTarget);
}
wrongValueTarget = target;
if (target != null && message != null) {
Clients.wrongValue(target, StrUtil.formatMessage(message));
}
}
/**
* Returns the text or "@"-prefixed label reference for the title of the cancel warning dialog.
*
* @return Text or label reference.
*/
public String getCancelTitleLabel() {
return label_cancel_title;
}
/**
* Sets the text or label reference for the title of the cancel warning dialog.
*
* @param value Text or "@"-prefixed label reference.
*/
public void setCancelTitleLabel(String value) {
label_cancel_title = value;
}
/**
* Returns the text or "@"-prefixed label reference for the message of the cancel warning
* dialog.
*
* @return Text or label reference.
*/
public String getCancelMessageLabel() {
return label_cancel_message;
}
/**
* Sets the text or label reference for the message of the cancel warning dialog.
*
* @param value Text or "@"-prefixed label reference.
*/
public void setCancelMessageLabel(String value) {
label_cancel_message = value;
}
/**
* Returns the text or "@"-prefixed label reference of the required input message.
*
* @return Text or label reference.
*/
public String getRequiredMessageLabel() {
return label_required_message;
}
/**
* Sets the text or label reference for the title of the required input message.
*
* @param value Text or "@"-prefixed label reference.
*/
public void setRequiredMessageLabel(String value) {
label_required_message = value;
}
/**
* Commit changes and close the form when OK button is clicked.
*/
public void onClick$btnOK() {
close(false);
}
/**
* Close the form when Cancel button is clicked, ignoring any changes.
*/
public void onClick$btnCancel() {
close(true);
}
/**
* Commits all changes.
*
* @return True if the operation was successful.
*/
private boolean doCommit() {
if (!hasRequired()) {
return false;
}
populateDomainObject(domainObject);
try {
commit(domainObject);
changeSet.clear();
return true;
} catch (Exception e) {
PromptDialog.showError(e);
return false;
}
}
private void close(boolean cancel) {
if (cancel && !changeSet.isEmpty() && !PromptDialog.confirm(label_cancel_message, label_cancel_title)) {
return;
}
if (!cancel && !doCommit()) {
return;
}
root.setAttribute("cancelled", cancel);
root.detach();
}
/**
* Perform any necessary post-composition initialization of controls here.
*/
protected void initControls() {
}
/**
* Returns true if all required inputs are present.
*
* @return True if all required inputs are present.
*/
protected boolean hasRequired() {
return true;
}
/**
* Populates input elements from the domain object.
*
* @param domainObject The domain object.
*/
protected abstract void populateControls(T domainObject);
/**
* Populates the domain object from the input elements.
*
* @param domainObject The domain object.
*/
protected abstract void populateDomainObject(T domainObject);
/**
* Commits changes to the the domain object.
*
* @param domainObject The domain object.
*/
protected abstract void commit(T domainObject);
}