package javango.forms;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javango.forms.fields.BooleanField;
import javango.forms.fields.BoundField;
import javango.forms.fields.FieldFactory;
import javango.forms.fields.IntegerField;
import javango.forms.widgets.HiddenInputWidget;
public class FormSet<T extends Form> implements Collection<T> {
// TODO make this changable.
private final String formCountFieldName = "TOTAL_FORMS";
private final String initialFormCountFieldName = "INITIAL_FORMS";
private final String deletionFieldName = "DELETE";
protected final Forms forms;
protected final FieldFactory fieldFactory;
protected final Class<? extends T> formClass;
protected final String prefix;
protected final Integer extra;
protected final List<T> formList = new ArrayList<T>();
protected final Form controlForm;
protected final List<?> initial;
protected final Map<String, String[]> data;
protected final boolean canDelete;
private int getTotalFormCount() throws ValidationException {
if (this.data != null) {
Integer count = (Integer)controlForm.getCleanedData().get(formCountFieldName);
if (count == null) {
throw new ValidationException("ManagementForm data is missing or has been tampered with" );
}
return count;
} else {
return getInitialFormCount() + extra;
}
}
private int getInitialFormCount() throws ValidationException {
if (this.data != null) {
Integer count = (Integer)controlForm.getCleanedData().get(initialFormCountFieldName);
if (count == null) {
throw new ValidationException("ManagementForm data is missing or has been tampered with" );
}
return count;
} else {
return initial == null ? 0 : initial.size();
}
}
public FormSet(
Forms forms,
FieldFactory fieldFactory,
Class<? extends T> formClass,
boolean canDelete,
List<?> initial,
Map<String, String[]> data,
String prefix,
Integer extra) throws ValidationException {
this.forms = forms;
this.fieldFactory = fieldFactory;
this.formClass = formClass;
this.canDelete = canDelete;
this.prefix = prefix == null ? "form" : prefix;
this.extra = extra == null ? 1 : extra;
this.initial = initial;
this.data = data;
controlForm = createControlForm();
for(int i=0; i<getTotalFormCount(); i++) {
formList.add(constructForm(i));
}
}
/**
* Experimental method which clones this formset into a new formset, the new formset can optionally have deleted forms removed.
*
* The resulting formset's forms initial data are set to the values of their cleanedData and are not bound. The new formset is
* really only useful for displaying in a template.
*
* @param includeDeleted Should deleted forms be included in the new FormSet
* @return
* @throws ValidationException
*/
public FormSet<T> clone(boolean includeDeleted) throws ValidationException {
List<Map<String, Object>> newData = new ArrayList<Map<String,Object>>();
for(T form : formList) {
if (includeDeleted || !shouldDeleteForm(form)) {
newData.add(form.getCleanedData());
}
}
return new FormSet<T>(forms, fieldFactory, formClass, canDelete, newData, null, prefix, 0);
}
/**
* Add any exta fields that are needed (ie delete)
* @param form
*/
private void addExtraFields(T form) {
if (canDelete) {
final BooleanField deleteField = fieldFactory.newField(BooleanField.class);
deleteField.setName(deletionFieldName)
.setRequired(false)
.setAllowNull(false);
form.getFields().put(deletionFieldName, deleteField);
}
}
private T constructForm(int i) {
Object initialValues = initial == null ? null : i < initial.size() ? initial.get(i) : null;
T form = forms.newForm(formClass);
String formPrefix = String.format("%s-%d", this.prefix, i++);
form.setPrefix(formPrefix);
form.setInitial(initialValues);
form.bind(data);
addExtraFields(form);
return form;
}
private Form createControlForm() throws ValidationException {
Form controlForm = forms.newForm(AbstractForm.class);
controlForm.setPrefix(String.format("%s_CTRL", prefix));
final IntegerField countField = fieldFactory.newField(IntegerField.class);
countField.setName(formCountFieldName)
.setWidget(HiddenInputWidget.class)
.setRequired(true)
.setAllowNull(false);
controlForm.getFields().put(formCountFieldName, countField);
final IntegerField initialFormCount = fieldFactory.newField(IntegerField.class);
initialFormCount.setName(initialFormCountFieldName)
.setWidget(HiddenInputWidget.class)
.setRequired(true)
.setAllowNull(false);
controlForm.getFields().put(initialFormCountFieldName, initialFormCount);
if (data != null) {
controlForm.bind(data);
if (!controlForm.isValid()) {
throw new UnsupportedOperationException("Missing control fields");
}
} else {
controlForm.getInitial().put(formCountFieldName, getTotalFormCount());
controlForm.getInitial().put(initialFormCountFieldName, getInitialFormCount());
}
return controlForm;
}
private boolean shouldDeleteForm(T form) {
if (!form.isBound()) {
return false;
}
BooleanField field = (BooleanField) form.getFields().get(deletionFieldName);
String[] value = form.getData().get(String.format("%s-%s", form.getPrefix(), deletionFieldName));
if (value == null || value.length == 0) {
return false;
}
try {
return field.clean(value, new HashMap<String, String>());
} catch (ValidationException e) {
return false;
}
}
/**
* Returns true is all forms are valid, does not short circuit, isValid will be called on all forms.
* @return
*/
public boolean isValid() {
boolean isvalid = true;
for (T form : formList) {
if (!shouldDeleteForm(form)) {
if (!form.isValid()) isvalid = false;
}
}
return isvalid;
}
/**
* Returns the HTML for the management form fields.
* @return
*/
public String getManagementForm() {
StringBuilder builder = new StringBuilder();
for(BoundField bf: controlForm) {
builder.append(bf.toString());
}
return builder.toString();
}
List<T> nonDeletedForms;
public List<T> getNonDeletedForms() {
if (nonDeletedForms == null) {
nonDeletedForms = new ArrayList<T>();
for(T form : formList) {
if (!shouldDeleteForm(form)) {
nonDeletedForms.add(form);
}
}
}
return nonDeletedForms;
}
List<T> deletedForms;
public List<T> getDeletedForms() {
if (deletedForms == null) {
deletedForms = new ArrayList<T>();
for(T form : formList) {
if (shouldDeleteForm(form)) {
deletedForms.add(form);
}
}
}
return deletedForms;
}
public Iterator<T> iterator() {
return formList.iterator();
}
/**
* Return an empty form with a custom prefix for use in javascript
*/
public T getEmptyForm(String prefix) {
T form = forms.newForm(formClass);
String formPrefix = String.format("%s-%s", this.prefix, prefix);
form.setPrefix(formPrefix);
addExtraFields(form);
return form;
}
/**
* Return an empty form with a prefix of "__prefix__" for use in javascript
*/
public T getEmptyForm() {
return getEmptyForm("__prefix__");
}
// collectin interface crap...
public boolean add(T arg0) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends T> arg0) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
public boolean contains(Object arg0) {
return formList.contains(arg0);
}
public boolean containsAll(Collection<?> arg0) {
return formList.containsAll(arg0);
}
public boolean isEmpty() {
return formList.isEmpty();
}
public boolean remove(Object arg0) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> arg0) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> arg0) {
throw new UnsupportedOperationException();
}
public int size() {
return formList.size();
}
public Object[] toArray() {
return formList.toArray();
}
public <T> T[] toArray(T[] arg0) {
return formList.toArray(arg0);
}
}