Package org.apache.cocoon.forms.formmodel

Source Code of org.apache.cocoon.forms.formmodel.Repeater$RepeaterRow

/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* 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.
*/
package org.apache.cocoon.forms.formmodel;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.apache.cocoon.environment.Request;
import org.apache.cocoon.forms.FormContext;
import org.apache.cocoon.forms.FormsConstants;
import org.apache.cocoon.forms.FormsRuntimeException;
import org.apache.cocoon.forms.event.WidgetEvent;
import org.apache.cocoon.forms.util.I18nMessage;
import org.apache.cocoon.forms.validation.ValidationError;
import org.apache.cocoon.forms.validation.ValidationErrorAware;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.XMLUtils;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
* A repeater is a widget that repeats a number of other widgets.
*
* <p>Technically, the Repeater widget is a ContainerWidget whose children are
* {@link RepeaterRow}s, and the RepeaterRows in turn are ContainerWidgets
* containing the actual repeated widgets. However, in practice, you won't need
* to use the RepeaterRow widget directly.
*
* <p>Using the methods {@link #getSize()} and {@link #getWidget(int, java.lang.String)}
* you can access all of the repeated widget instances.
*
* @version $Id: Repeater.java 385330 2006-03-12 18:25:06Z sylvain $
*/
public class Repeater extends AbstractWidget
                      implements ValidationErrorAware {

    private static final String REPEATER_EL = "repeater";
    private static final String HEADINGS_EL = "headings";
    private static final String HEADING_EL = "heading";
    private static final String LABEL_EL = "label";
    private static final String REPEATER_SIZE_EL = "repeater-size";

    private final RepeaterDefinition definition;
    protected final List rows = new ArrayList();
    protected ValidationError validationError;
    private boolean orderable = false;

    public Repeater(RepeaterDefinition repeaterDefinition) {
        super(repeaterDefinition);
        this.definition = repeaterDefinition;
        // Setup initial size. Do not call addRow() as it will call initialize()
        // on the newly created rows, which is not what we want here.
        for (int i = 0; i < this.definition.getInitialSize(); i++) {
            rows.add(new RepeaterRow(definition));
        }
       
        this.orderable = this.definition.getOrderable();
    }

    public WidgetDefinition getDefinition() {
        return definition;
    }

    public void initialize() {
        for (int i = 0; i < this.rows.size(); i++) {
            ((RepeaterRow)rows.get(i)).initialize();
        }
        super.initialize();
    }

    public int getSize() {
        return rows.size();
    }

    public int getMinSize() {
        return this.definition.getMinSize();
    }

    public int getMaxSize() {
        return this.definition.getMaxSize();
    }

    public RepeaterRow addRow() {
        RepeaterRow repeaterRow = new RepeaterRow(definition);
        rows.add(repeaterRow);
        repeaterRow.initialize();
        getForm().addWidgetUpdate(this);
        return repeaterRow;
    }

    public RepeaterRow addRow(int index) {
        RepeaterRow repeaterRow = new RepeaterRow(definition);
        if (index >= this.rows.size()) {
            rows.add(repeaterRow);
        } else {
            rows.add(index, repeaterRow);
        }
        repeaterRow.initialize();
        getForm().addWidgetUpdate(this);
        return repeaterRow;
    }

    public RepeaterRow getRow(int index) {
        return (RepeaterRow)rows.get(index);
    }

    /**
     * Overrides {@link AbstractWidget#getChild(String)} to return the
     * repeater-row indicated by the index in 'id'
     *
     * @param id index of the row as a string-id
     * @return the repeater-row at the specified index
     */
    public Widget getChild(String id) {
        int rowIndex;
        try {
            rowIndex = Integer.parseInt(id);
        } catch (NumberFormatException nfe) {
            // Not a number
            return null;
        }

        if (rowIndex < 0 || rowIndex >= getSize()) {
            return null;
        }

        return getRow(rowIndex);
    }

    /**
     * Crawls up the parents of a widget up to finding a repeater row.
     *
     * @param widget the widget whose row is to be found
     * @return the repeater row
     */
    public static RepeaterRow getParentRow(Widget widget) {
        Widget result = widget;
        while(result != null && ! (result instanceof Repeater.RepeaterRow)) {
            result = result.getParent();
        }

        if (result == null) {
            throw new RuntimeException("Could not find a parent row for widget " + widget);

        }
        return (Repeater.RepeaterRow)result;
    }

    /**
     * Get the position of a row in this repeater.
     * @param row the row which we search the index for
     * @return the row position or -1 if this row is not in this repeater
     */
    public int indexOf(RepeaterRow row) {
        return this.rows.indexOf(row);
    }

    /**
     * @throws IndexOutOfBoundsException if the the index is outside the range of existing rows.
     */
    public void removeRow(int index) {
        rows.remove(index);
        getForm().addWidgetUpdate(this);
    }

    /**
     * Move a row from one place to another
     * @param from the existing row position
     * @param to the target position. The "from" item will be moved before that position.
     */
    public void moveRow(int from, int to) {
        int size = this.rows.size();
       
        if (from < 0 || from >= size || to < 0 || to > size) {
            throw new IllegalArgumentException("Cannot move from " + from + " to " + to +
                    " on repeater with " + size + " rows");
        }
       
        if (from == to) {
            return;
        }
       
        Object fromRow = this.rows.remove(from);
        if (to == size) {
            // Move at the end
            this.rows.add(fromRow);
           
        } else if (to > from) {
            // Index of "to" was moved by removing
            this.rows.add(to - 1, fromRow);
           
        } else {
            this.rows.add(to, fromRow);
        }

        getForm().addWidgetUpdate(this);
    }

    public void moveRowLeft(int index) {
        if (index == 0 || index >= this.rows.size()) {
            // do nothing
        } else {
            Object temp = this.rows.get(index-1);
            this.rows.set(index-1, this.rows.get(index));
            this.rows.set(index, temp);
        }
        getForm().addWidgetUpdate(this);
    }

    public void moveRowRight(int index) {
        if (index < 0 || index >= this.rows.size() - 1) {
            // do nothing
        } else {
            Object temp = this.rows.get(index+1);
            this.rows.set(index+1, this.rows.get(index));
            this.rows.set(index, temp);
        }
        getForm().addWidgetUpdate(this);
    }

    /**
     * @deprecated {@see #clear()}
     *
     */
    public void removeRows() {
        clear();
    }

    /**
     * Clears all rows from the repeater and go back to the initial size
     */
    public void clear() {
        rows.clear();

        // and reset to initial size
        for (int i = 0; i < this.definition.getInitialSize(); i++) {
            addRow();
        }
        getForm().addWidgetUpdate(this);
    }

    /**
     * Gets a widget on a certain row.
     * @param rowIndex startin from 0
     * @param id a widget id
     * @return null if there's no such widget
     */
    public Widget getWidget(int rowIndex, String id) {
        RepeaterRow row = (RepeaterRow)rows.get(rowIndex);
        return row.getChild(id);
    }

    public void readFromRequest(FormContext formContext) {
        if (!getCombinedState().isAcceptingInputs())
            return;

        // read number of rows from request, and make an according number of rows
        Request req = formContext.getRequest();
        String paramName = getRequestParameterName();
       
        String sizeParameter = req.getParameter(paramName + ".size");
        if (sizeParameter != null) {
            int size = 0;
            try {
                size = Integer.parseInt(sizeParameter);
            } catch (NumberFormatException exc) {
                // do nothing
            }

            // some protection against people who might try to exhaust the server by supplying very large
            // size parameters
            if (size > 500)
                throw new RuntimeException("Client is not allowed to specify a repeater size larger than 500.");

            int currentSize = getSize();
            if (currentSize < size) {
                for (int i = currentSize; i < size; i++) {
                    addRow();
                }
            } else if (currentSize > size) {
                for (int i = currentSize - 1; i >= size; i--) {
                    removeRow(i);
                }
            }
        }

        // let the rows read their data from the request
        Iterator rowIt = rows.iterator();
        while (rowIt.hasNext()) {
            RepeaterRow row = (RepeaterRow)rowIt.next();
            row.readFromRequest(formContext);
        }
       
        // Handle repeater-level actions
        String action = req.getParameter(paramName + ".action");
        if (action == null) {
            return;
        }

        // Handle row move. It's important for this to happen *after* row.readFromRequest,
        // as reordering rows changes their IDs and therefore their child widget's ID too.
        if (action.equals("move")) {
            if (!this.orderable) {
                throw new FormsRuntimeException(this + " is not orderable", getLocation());
            }
            int from = Integer.parseInt(req.getParameter(paramName + ".from"));
            int before = Integer.parseInt(req.getParameter(paramName + ".before"));
           
            Object row = this.rows.get(from);
            // Add to the new location
            this.rows.add(before, row);
            // Remove from the previous one, taking into account potential location change
            // because of the previous add()
            if (before < from) from++;
            this.rows.remove(from);
           
            // Needs refresh
            getForm().addWidgetUpdate(this);
           
        } else {
            throw new FormsRuntimeException("Unknown action " + action + " for " + this, getLocation());
        }
    }

    /**
     * @see org.apache.cocoon.forms.formmodel.Widget#validate()
     */
    public boolean validate() {
        if (!getCombinedState().isValidatingValues()) {
            this.wasValid = true;
            return true;
        }

        boolean valid = true;
        Iterator rowIt = rows.iterator();
        while (rowIt.hasNext()) {
            RepeaterRow row = (RepeaterRow)rowIt.next();
            valid = valid & row.validate();
        }

        if (rows.size() > getMaxSize() || rows.size() < getMinSize()) {
            String [] boundaries = new String[2];
            boundaries[0] = String.valueOf(getMinSize());
            boundaries[1] = String.valueOf(getMaxSize());
            this.validationError = new ValidationError(new I18nMessage("repeater.cardinality", boundaries, FormsConstants.I18N_CATALOGUE));
            valid=false;
        }


        if (valid) {
            valid = super.validate();
        }

        this.wasValid = valid && this.validationError == null;
        return this.wasValid;
    }



    /**
     * @return "repeater"
     */
    public String getXMLElementName() {
        return REPEATER_EL;
    }



  /**
   * Adds @size attribute
   */
  public AttributesImpl getXMLElementAttributes() {
        AttributesImpl attrs = super.getXMLElementAttributes();
        attrs.addCDATAAttribute("size", String.valueOf(getSize()));
        // Generate the min and max sizes if they don't have the default value
        int size = getMinSize();
        if (size > 0) {
            attrs.addCDATAAttribute("min-size", String.valueOf(size));
        }
        size = getMaxSize();
        if (size != Integer.MAX_VALUE) {
            attrs.addCDATAAttribute("max-size", String.valueOf(size));
        }
    return attrs;
  }


  public void generateDisplayData(ContentHandler contentHandler)
      throws SAXException {
        // the repeater's label
        contentHandler.startElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL, XMLUtils.EMPTY_ATTRIBUTES);
        generateLabel(contentHandler);
        contentHandler.endElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL);

        // heading element -- currently contains the labels of each widget in the repeater
        contentHandler.startElement(FormsConstants.INSTANCE_NS, HEADINGS_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADINGS_EL, XMLUtils.EMPTY_ATTRIBUTES);
        Iterator widgetDefinitionIt = definition.getWidgetDefinitions().iterator();
        while (widgetDefinitionIt.hasNext()) {
            WidgetDefinition widgetDefinition = (WidgetDefinition)widgetDefinitionIt.next();
            contentHandler.startElement(FormsConstants.INSTANCE_NS, HEADING_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADING_EL, XMLUtils.EMPTY_ATTRIBUTES);
            widgetDefinition.generateLabel(contentHandler);
            contentHandler.endElement(FormsConstants.INSTANCE_NS, HEADING_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADING_EL);
        }
        contentHandler.endElement(FormsConstants.INSTANCE_NS, HEADINGS_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADINGS_EL);
  }


    public void generateItemSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException {
        // the actual rows in the repeater
        Iterator rowIt = rows.iterator();
        while (rowIt.hasNext()) {
            RepeaterRow row = (RepeaterRow)rowIt.next();
            row.generateSaxFragment(contentHandler, locale);
        }
    }

    /**
     * Generates the label of a certain widget in this repeater.
     */
    public void generateWidgetLabel(String widgetId, ContentHandler contentHandler) throws SAXException {
        WidgetDefinition widgetDefinition = definition.getWidgetDefinition(widgetId);
        if (widgetDefinition == null)
            throw new SAXException("Repeater \"" + getRequestParameterName() + "\" at " + this.getLocation()
                                   + " contains no widget with id \"" + widgetId + "\".");
        widgetDefinition.generateLabel(contentHandler);
    }

    /**
     * Generates a repeater-size element with a size attribute indicating the size of this repeater.
     */
    public void generateSize(ContentHandler contentHandler) throws SAXException {
        AttributesImpl attrs = getXMLElementAttributes();
        contentHandler.startElement(FormsConstants.INSTANCE_NS, REPEATER_SIZE_EL, FormsConstants.INSTANCE_PREFIX_COLON + REPEATER_SIZE_EL, attrs);
        contentHandler.endElement(FormsConstants.INSTANCE_NS, REPEATER_SIZE_EL, FormsConstants.INSTANCE_PREFIX_COLON + REPEATER_SIZE_EL);
    }

    /**
     * Set a validation error on this field. This allows repeaters be externally marked as invalid by
     * application logic.
     *
     * @return the validation error
     */
    public ValidationError getValidationError() {
        return this.validationError;
    }

    /**
     * set a validation error
     */
    public void setValidationError(ValidationError error) {
        this.validationError = error;
    }

    public class RepeaterRow extends AbstractContainerWidget {

        private static final String ROW_EL = "repeater-row";

        public RepeaterRow(RepeaterDefinition definition) {
            super(definition);
            setParent(Repeater.this);
            definition.createWidgets(this);
        }

        public WidgetDefinition getDefinition() {
            return Repeater.this.getDefinition();
        }

        private int cachedPosition = -100;
        private String cachedId = "--undefined--";

        public String getId() {
            int pos = rows.indexOf(this);
            if (pos == -1) {
                throw new IllegalStateException("Row has currently no position");
            }
            if (pos != this.cachedPosition) {
                this.cachedPosition = pos;
                // id of a RepeaterRow is the position of the row in the list of rows.
                this.cachedId = String.valueOf(pos);
                widgetNameChanged();
            }
            return this.cachedId;
        }
       
        public String getRequestParameterName() {
            // Get the id to check potential position change
            getId();

            return super.getRequestParameterName();
        }

        public Form getForm() {
            return Repeater.this.getForm();
        }

        public void initialize() {
            // Initialize children but don't call super.initialize() that would call the repeater's
            // on-create handlers for each row.
            // FIXME(SW): add an 'on-create-row' handler?
            Iterator it = this.getChildren();
            while(it.hasNext()) {
              ((Widget)it.next()).initialize();
            }
        }
       
        public boolean validate() {
            // Validate only child widtgets, as the definition's validators are those of the parent repeater
            return widgets.validate();
        }

        /**
         * @return "repeater-row"
         */
        public String getXMLElementName() {
            return ROW_EL;
        }

        public void generateLabel(ContentHandler contentHandler) throws SAXException {
            // this widget has its label generated in the context of the repeater
        }

        public void generateDisplayData(ContentHandler contentHandler)
                throws SAXException {
            // this widget has its display-data generated in the context of the repeater
        }

        public void broadcastEvent(WidgetEvent event) {
            throw new UnsupportedOperationException("Widget " + this.getRequestParameterName() + " doesn't handle events.");
        }
    }
}
TOP

Related Classes of org.apache.cocoon.forms.formmodel.Repeater$RepeaterRow

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.