All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.richfaces.renderkit.ExtendedDataTableRenderer Maven / Gradle / Ivy

There is a newer version: 4.3.7.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright ${year}, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.renderkit;

import static org.richfaces.renderkit.RenderKitUtils.addToScriptHash;
import static org.richfaces.renderkit.RenderKitUtils.renderAttribute;
import static org.richfaces.renderkit.util.AjaxRendererUtils.buildAjaxFunction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialResponseWriter;
import javax.faces.context.PartialViewContext;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.model.DataVisitResult;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.SequenceRange;
import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.component.AbstractExtendedDataTable;
import org.richfaces.component.UIDataTableBase;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.context.OnOffResponseWriter;
import org.richfaces.model.SelectionMode;
import org.richfaces.renderkit.RenderKitUtils.ScriptHashVariableWrapper;

/**
 * @author Konstantin Mishin
 *
 */


@JsfRenderer(type = "org.richfaces.ExtendedDataTableRenderer", family = AbstractExtendedDataTable.COMPONENT_FAMILY)
@ResourceDependencies({
    @ResourceDependency(library="org.richfaces", name = "extendedDataTable.ecss"),
    @ResourceDependency(library="org.richfaces", name = "ajax.reslib"),
    @ResourceDependency(library="org.richfaces", name = "base-component.reslib"),
    @ResourceDependency(name = "jquery.position.js"),
    @ResourceDependency(library="org.richfaces", name = "extendedDataTable.js") 
})
public class ExtendedDataTableRenderer extends SelectionRenderer implements MetaComponentRenderer {

    private static final JSReference CLIENT_PARAMS = new JSReference("clientParams");

    private static enum PartName {
        
        frozen, normal;
        
        private String id;
        
        private PartName() {
            id = String.valueOf(this.toString().charAt(0));
        }
        
        public String getId() {
            return id;
        }
    }
        
    private class Part {
        private PartName name;
        private List columns;
        
        public Part(PartName name, List columns) {
            this.name = name;
            this.columns = columns;
        }
        
        public PartName getName() {
            return name;
        }

        public List getColumns() {
            return columns;
        }

    }
    
    private class RendererState extends RowHolderBase{

        private UIDataTableBase table;
        private List parts;
        private Part current;
        private Iterator partIterator;
        
        private EncoderVariance encoderVariance = EncoderVariance.full;
        
        public RendererState(FacesContext context, UIDataTableBase table) {
            super(context);
            this.table = table;
            List frozenColumns;
            List columns;
            columns = new ArrayList();
            Iterator iterator = table.columns();
            Map columnsMap = new LinkedHashMap();
            for (; iterator.hasNext();) {
                UIComponent component = iterator.next();
                if (component.isRendered()) {
                    columnsMap.put(component.getId(), component);
                }
            }            
            String[] columnsOrder = (String[]) table.getAttributes().get("columnsOrder");
            if (columnsOrder != null && columnsOrder.length > 0) {
                int i = 0;
                for (; i < columnsOrder.length && !columnsMap.isEmpty(); i++) {
                    columns.add(columnsMap.remove(columnsOrder[i]));
                }
            }
            iterator = columnsMap.values().iterator();
            for (; iterator.hasNext();) {
                columns.add(iterator.next());
            }
            
            int frozenColumnsAttribute = (Integer) table.getAttributes().get("frozenColumns");
            if (frozenColumnsAttribute < 0) {
                frozenColumnsAttribute = 0;
            }
            
            int count = Math.min(frozenColumnsAttribute, columns.size());
            frozenColumns = columns.subList(0, count);
            columns = columns.subList(count, columns.size());
            parts = new ArrayList(PartName.values().length);
            if (frozenColumns.size() > 0) {
                parts.add(new Part(PartName.frozen, frozenColumns));
            }
            if (columns.size() > 0) {
                parts.add(new Part(PartName.normal, columns));
            }
        }

        public UIDataTableBase getRow() {
            return table;
        }

        public void startIterate() {
            partIterator = parts.iterator();
        }

        public Part nextPart() {
            current = partIterator.next();
            return current;
        }

        public Part getPart() {
            return current;
        }
        
        public boolean hasNextPart() {
            return partIterator.hasNext();
        }

        public EncoderVariance getEncoderVariance() {
            return encoderVariance;
        }
        
        public void setEncoderVariance(EncoderVariance encoderVariance) {
            this.encoderVariance = encoderVariance;
        }
    }

    private enum EncoderVariance {
        full {
            public void encodeStartUpdate(FacesContext context, String targetId) throws IOException {
                //do nothing
            }
            
            public void encodeEndUpdate(FacesContext context) throws IOException {
                //do nothing
            }
        }, 
        
        partial {

            private void switchResponseWriter(FacesContext context, boolean writerState) {
                ResponseWriter writer = context.getResponseWriter();
                ((OnOffResponseWriter) writer).setSwitchedOn(writerState);
            }

            public void encodeStartUpdate(FacesContext context, String targetId) throws IOException {
                switchResponseWriter(context, true);
                
                context.getPartialViewContext().getPartialResponseWriter().startUpdate(targetId);
            }

            public void encodeEndUpdate(FacesContext context) throws IOException {
                context.getPartialViewContext().getPartialResponseWriter().endUpdate();
                
                switchResponseWriter(context, false);
            }
        };

        public abstract void encodeStartUpdate(FacesContext context, String targetId) throws IOException;
        
        public abstract void encodeEndUpdate(FacesContext context) throws IOException;
        
    }
    
    private static final Map EVENT_ATTRIBUTES
        = Collections.unmodifiableMap(ComponentAttribute.createMap(new ComponentAttribute("onselectionchange")
            .setEventNames(new String[] {"selectionchange"}), new ComponentAttribute("onbeforeselectionchange")
                .setEventNames(new String[] {"beforeselectionchange"})));

    private void encodeEmptyFooterCell(FacesContext context, ResponseWriter writer, UIComponent column)
        throws IOException {
        if (column.isRendered()) {
            writer.startElement(HtmlConstants.TD_ELEM, column);
            writer.startElement(HtmlConstants.DIV_ELEM, column);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-ftr-c-emp rf-edt-c-"
                + column.getId(), null);
            writer.endElement(HtmlConstants.DIV_ELEM);
            writer.endElement(HtmlConstants.TD_ELEM);
        }
    }

    private void encodeHeaderOrFooterCell(FacesContext context, ResponseWriter writer, UIComponent column,
        String facetName) throws IOException {
        if (column.isRendered()) {

            String classAttribute = facetName + "Class";
            writer.startElement(HtmlConstants.TD_ELEM, column);
            if ("header".equals(facetName)) {
                writer.startElement(HtmlConstants.DIV_ELEM, column);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-rsz-cntr rf-edt-c-"
                    + column.getId(), null);
                writer.startElement(HtmlConstants.DIV_ELEM, column);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-rsz", null);
                writer.endElement(HtmlConstants.DIV_ELEM);
                writer.endElement(HtmlConstants.DIV_ELEM);
            }
            writer.startElement(HtmlConstants.DIV_ELEM, column);
            writer.writeAttribute(
                HtmlConstants.CLASS_ATTRIBUTE,
                HtmlUtil.concatClasses("rf-edt-" + getFacetClassName(facetName) + "-c", "rf-edt-c-"
                    + column.getId(), (String) column.getAttributes().get(classAttribute)), null);
            writer.startElement(HtmlConstants.DIV_ELEM, column);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-" + getFacetClassName(facetName)
                + "-c-cnt", null);
            UIComponent facet = column.getFacet(facetName);
            if (facet != null && facet.isRendered()) {
                facet.encodeAll(context);
            }
            writer.endElement(HtmlConstants.DIV_ELEM);
            writer.endElement(HtmlConstants.DIV_ELEM);
            writer.endElement(HtmlConstants.TD_ELEM);
        }
    }

    private String getFacetClassName(String name) {
        if ("header".equals(name)) {
            return "hdr";
        } else if ("footer".equals(name)) {
            return "ftr";
        }
        
        throw new IllegalArgumentException(name);
    }
    
    private void encodeHeaderOrFooter(RendererState state, String name) throws IOException {
        FacesContext context = state.getContext();
        ResponseWriter writer = context.getResponseWriter();
        UIDataTableBase table = state.getRow();
        boolean columnFacetPresent = table.isColumnFacetPresent(name);
        if (columnFacetPresent || "footer".equals(name)) {
            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-" + getFacetClassName(name), null);
            writer.startElement(HtmlConstants.TABLE_ELEMENT, table);
            writer.writeAttribute(HtmlConstants.CELLPADDING_ATTRIBUTE, "0", null);
            writer.writeAttribute(HtmlConstants.CELLSPACING_ATTRIBUTE, "0", null);
            writer.startElement(HtmlConstants.TBODY_ELEMENT, table);
            writer.startElement(HtmlConstants.TR_ELEMENT, table);
            String clientId = table.getClientId(context);
            for (state.startIterate(); state.hasNextPart();) {
                Part part = state.nextPart();
                PartName partName = part.getName();
                Iterator columns = part.getColumns().iterator();
                if (columns.hasNext()) {
                    writer.startElement(HtmlConstants.TD_ELEM, table);
                    if (PartName.frozen.equals(partName) && "footer".equals(name)) {
                        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-ftr-fzn", null);
                    }
                    writer.startElement(HtmlConstants.DIV_ELEM, table);
                    if (PartName.frozen.equals(partName)) {
                        if ("header".equals(name)) {
                            writer
                            .writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":frozenHeader", null);
                        }
                    } else {
                        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":" + name, null);
                        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-cnt"
                            + ("footer".equals(name) ? " rf-edt-ftr-cnt" : ""), null);
                    }

                    String tableId = clientId + ":cf" + name.charAt(0) + partName.getId();
                    EncoderVariance encoderVariance = state.getEncoderVariance();
                    encoderVariance.encodeStartUpdate(context, tableId);

                    writer.startElement(HtmlConstants.TABLE_ELEMENT, table);
                    writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, tableId, null);
                    writer.writeAttribute(HtmlConstants.CELLPADDING_ATTRIBUTE, "0", null);
                    writer.writeAttribute(HtmlConstants.CELLSPACING_ATTRIBUTE, "0", null);
                    writer.startElement(HtmlConstants.TBODY_ELEMENT, table);
                    writer.startElement(HtmlConstants.TR_ELEMENT, table);
                    while (columns.hasNext()) {
                        if (columnFacetPresent) {
                            encodeHeaderOrFooterCell(context, writer, columns.next(), name);
                        } else {
                            encodeEmptyFooterCell(context, writer, columns.next());
                        }
                    }
                    writer.endElement(HtmlConstants.TR_ELEMENT);
                    writer.endElement(HtmlConstants.TBODY_ELEMENT);
                    writer.endElement(HtmlConstants.TABLE_ELEMENT);

                    encoderVariance.encodeEndUpdate(context);

                    writer.endElement(HtmlConstants.DIV_ELEM);
                    writer.endElement(HtmlConstants.TD_ELEM);
                }
            }
            writer.endElement(HtmlConstants.TR_ELEMENT);
            writer.endElement(HtmlConstants.TBODY_ELEMENT);
            writer.endElement(HtmlConstants.TABLE_ELEMENT);
            writer.endElement(HtmlConstants.DIV_ELEM);
        }
    }

    public void encodeHeader(RendererState state) throws IOException {
        FacesContext context = state.getContext();
        ResponseWriter writer = context.getResponseWriter();
        UIDataTableBase table = state.getRow();

        UIComponent header = table.getFacet("header");
        if (header != null && header.isRendered()) {
            String elementId = table.getClientId(context) + ":tfh";

            EncoderVariance encoderVariance = state.getEncoderVariance();
            encoderVariance.encodeStartUpdate(context, elementId);

            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, elementId, null);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE,
                HtmlUtil.concatClasses("rf-edt-tbl-hdr", table.getHeaderClass()), null);
            header.encodeAll(context);
            writer.endElement(HtmlConstants.DIV_ELEM);

            encoderVariance.encodeEndUpdate(context);
        }

        encodeHeaderOrFooter(state, "header");
    }

    public void encodeBody(RendererState state) throws IOException {
        FacesContext context = state.getContext();
        ResponseWriter writer = context.getResponseWriter();
        UIDataTableBase table = state.getRow();
        String clientId = table.getClientId(context);
        String tableBodyId = clientId + ":b";
        EncoderVariance encoderVariance = state.getEncoderVariance();
        encoderVariance.encodeStartUpdate(context, tableBodyId);
        writer.startElement(HtmlConstants.DIV_ELEM, table);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, tableBodyId, null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-b", null);
        if (table.getRowCount() == 0) {
            UIComponent facet = table.getFacet("noData");
            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-ndt", null);
            if (facet != null && facet.isRendered()) {
                facet.encodeAll(context);
            } else {
                Object noDataLabel = table.getAttributes().get("noDataLabel");
                if (noDataLabel != null) {
                    writer.writeText(noDataLabel, "noDataLabel");
                }
            }
            writer.endElement(HtmlConstants.DIV_ELEM);
        } else {
            table.getAttributes().put("clientFirst", 0);
            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-spcr", null);
            writer.endElement(HtmlConstants.DIV_ELEM);
            writer.startElement(HtmlConstants.TABLE_ELEMENT, table);
            writer.writeAttribute(HtmlConstants.CELLPADDING_ATTRIBUTE, "0", null);
            writer.writeAttribute(HtmlConstants.CELLSPACING_ATTRIBUTE, "0", null);
            writer.startElement(HtmlConstants.TBODY_ELEMENT, table);
            writer.startElement(HtmlConstants.TR_ELEMENT, table);
            for (state.startIterate(); state.hasNextPart();) {
                writer.startElement(HtmlConstants.TD_ELEM, table);
                writer.startElement(HtmlConstants.DIV_ELEM, table);
                PartName partName = state.nextPart().getName();
                if (PartName.normal.equals(partName)) {
                    writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":body", null);
                    writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-cnt", null);
                }
                String targetId = clientId + ":tbt" + partName.getId();
                writer.startElement(HtmlConstants.TABLE_ELEMENT, table);
                writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, targetId, null);
                writer.writeAttribute(HtmlConstants.CELLPADDING_ATTRIBUTE, "0", null);
                writer.writeAttribute(HtmlConstants.CELLSPACING_ATTRIBUTE, "0", null);
                writer.startElement(HtmlConstants.TBODY_ELEMENT, table);
                writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":tb"
                    + partName.getId(), null);
                encodeRows(context, state);
                writer.endElement(HtmlConstants.TBODY_ELEMENT);
                writer.endElement(HtmlConstants.TABLE_ELEMENT);

                writer.endElement(HtmlConstants.DIV_ELEM);
                writer.endElement(HtmlConstants.TD_ELEM);
            }
            writer.endElement(HtmlConstants.TR_ELEMENT);
            writer.endElement(HtmlConstants.TBODY_ELEMENT);
            writer.endElement(HtmlConstants.TABLE_ELEMENT);
            writer.endElement(HtmlConstants.DIV_ELEM);
        }
        writer.endElement(HtmlConstants.DIV_ELEM);
        encoderVariance.encodeEndUpdate(context);
    }

    public void encodeFooter(RendererState state) throws IOException {
        FacesContext context = state.getContext();
        ResponseWriter writer = context.getResponseWriter();
        UIDataTableBase table = state.getRow();

        encodeHeaderOrFooter(state, "footer");

        UIComponent footer = table.getFacet("footer");
        if (footer != null && footer.isRendered()) {
            String elementId = table.getClientId(context) + ":tff";

            EncoderVariance encoderVariance = state.getEncoderVariance();
            encoderVariance.encodeStartUpdate(context, elementId);

            writer.startElement(HtmlConstants.DIV_ELEM, table);
            writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, elementId, null);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE,
                HtmlUtil.concatClasses("rf-edt-tbl-ftr", table.getFooterClass()), null);
            footer.encodeAll(context);
            writer.endElement(HtmlConstants.DIV_ELEM);

            encoderVariance.encodeEndUpdate(context);
        }
    }
    
    @Override
    protected Class getComponentClass() {
        return AbstractExtendedDataTable.class;
    }

    public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) 
        throws IOException {
        AbstractExtendedDataTable table = (AbstractExtendedDataTable) component;
        if (AbstractExtendedDataTable.SCROLL.equals(metaComponentId)) {
            final PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();
            int clientFirst = table.getClientFirst();
            Integer oldClientFirst = (Integer) table.getAttributes().remove(AbstractExtendedDataTable.OLD_CLIENT_FIRST);
            if (oldClientFirst == null) {
                oldClientFirst = clientFirst;
            }
            int clientRows = ((SequenceRange) table.getComponentState().getRange()).getRows();
            int difference = clientFirst - oldClientFirst;
            SequenceRange addRange = null;
            SequenceRange removeRange = null;
            if (Math.abs(difference) >= clientRows) {
                difference = 0;
                addRange = new SequenceRange(clientFirst, clientRows);
                removeRange = new SequenceRange(oldClientFirst, clientRows);
            } else if (difference < 0) {
                clientFirst += table.getFirst();
                addRange = new SequenceRange(clientFirst, -difference);
                removeRange = new SequenceRange(clientFirst + clientRows, -difference);
            } else if (difference > 0) {
                oldClientFirst += table.getFirst();
                removeRange = new SequenceRange(oldClientFirst, difference);
                int last = oldClientFirst + clientRows;
                addRange = new SequenceRange(last, difference);
            }
            if (addRange != null) {
                Object key = table.getRowKey();
                table.captureOrigValue(context);
                table.setRowKey(context, null);
                final RendererState state = createRowHolder(context, table, null);
                String clientId = table.getClientId(context);
                // TODO 1. Encode fixed children
                for (state.startIterate(); state.hasNextPart();) {
                    String partId = state.nextPart().getName().getId();
                    final List ids = new LinkedList();
                    table.walk(context, new DataVisitor() {
                        public DataVisitResult process(FacesContext context, Object rowKey, Object argument) {
                            UIDataTableBase dataTable = state.getRow();
                            dataTable.setRowKey(context, rowKey);
                            ids.add(dataTable.getContainerClientId(context) + ":"
                                + state.getPart().getName().getId());
                            return DataVisitResult.CONTINUE;
                        }
                    }, removeRange, null);
                    table.walk(context, new DataVisitor() {
                        public DataVisitResult process(FacesContext context, Object rowKey, Object argument) {
                            UIDataTableBase dataTable = state.getRow();
                            dataTable.setRowKey(context, rowKey);
                            HashMap attributes = new HashMap(1);
                            String id = dataTable.getContainerClientId(context) + ":"
                                + state.getPart().getName().getId();
                            attributes.put("id", id);
                            try {
                                writer.updateAttributes(ids.remove(0), attributes);
                                writer.startUpdate(id);
                                encodeRow(writer, context, state);
                                writer.endUpdate();
                            } catch (IOException e) {
                                throw new FacesException(e);
                            }
                            return DataVisitResult.CONTINUE;
                        }
                    },  addRange, null);
                    writer.startEval();
                    if (difference < 0) {
                        difference += clientRows;
                    }
                    
                    //TODO nick - move this to external JavaScript file
                    writer.write("var richTBody = document.getElementById('" + clientId + ":tb"
                        + partId + "');");
                    writer.write("var richRows = richTBody.rows;");
                    writer.write("for (var i = 0; i < " + difference
                        + "; i++ ) richTBody.appendChild(richTBody.removeChild(richRows[0]));");
                    writer.endEval();
                }
                writer.startUpdate(clientId + ":si");
                encodeSelectionInput(writer, context, component);
                writer.endUpdate();
                writer.startEval();
                writer.write("jQuery(" + ScriptUtils.toScript('#' + ScriptUtils.escapeCSSMetachars(clientId))
                    + ").triggerHandler('rich:onajaxcomplete', {first: " + table.getClientFirst() + "});");
                writer.endEval();
                table.setRowKey(context, key);
                table.restoreOrigValue(context);
            }
        } else {

            ResponseWriter initialWriter = context.getResponseWriter();
            assert !(initialWriter instanceof OnOffResponseWriter);

            try {
                context.setResponseWriter(new OnOffResponseWriter(initialWriter));

                RendererState state = createRowHolder(context, component, null);
                state.setEncoderVariance(EncoderVariance.partial);
                
                PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();
                
                if (UIDataTableBase.HEADER.equals(metaComponentId)) {
                    encodeHeader(state);
                    writer.startEval();
                    writer.write("jQuery(" + ScriptUtils.toScript('#' + ScriptUtils.escapeCSSMetachars(table.getClientId(context)))
                        + ").triggerHandler('rich:onajaxcomplete', {reinitializeHeader: true});");
                    writer.endEval();
                } else if (UIDataTableBase.FOOTER.equals(metaComponentId)) {
                    encodeFooter(state);
                } else if (UIDataTableBase.BODY.equals(metaComponentId)) {
                    encodeBody(state);
                    String clientId = table.getClientId(context);
                    writer.startUpdate(clientId + ":si");
                    encodeSelectionInput(writer, context, component);
                    writer.endUpdate();
                    writer.startEval();
                    writer.write("jQuery(" + ScriptUtils.toScript('#' + ScriptUtils.escapeCSSMetachars(clientId))
                        + ").triggerHandler('rich:onajaxcomplete', {first: " + table.getClientFirst() + ", rowCount: "
                        + getRowCount(component) + ", reinitializeBody: true});");
                    writer.endEval();
                } else {
                    throw new IllegalArgumentException("Unsupported metaComponentIdentifier: " + metaComponentId);
                }
            } finally {
                context.setResponseWriter(initialWriter);
            }
        }
    }
    
    public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
        throw new UnsupportedOperationException();
    }

    protected void doEncodeBegin(ResponseWriter writer, FacesContext context, UIComponent component)
        throws IOException {
        Map attributes = component.getAttributes();
        writer.startElement(HtmlConstants.DIV_ELEM, component);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, component.getClientId(context), null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, HtmlUtil.concatClasses("rf-edt",
            (String) attributes.get("styleClass")), null);
        renderAttribute(context, HtmlConstants.STYLE_ATTRIBUTE, attributes.get("style"));
    }

    public RendererState createRowHolder(FacesContext context, UIComponent component, Object[] options) {
        return new RendererState(context, (UIDataTableBase) component);
    }

    protected void doEncodeChildren(ResponseWriter writer, FacesContext context, UIComponent component)
        throws IOException {

        UIDataTableBase table = (UIDataTableBase) component;
        Object key = table.getRowKey();
        table.captureOrigValue(context);
        table.setRowKey(context, null);
        RendererState state = createRowHolder(context, table, null);
        encodeStyle(state);
        encodeHeader(state);
        encodeBody(state);
        encodeFooter(state);
        table.setRowKey(context, key);
        table.restoreOrigValue(context);
    }

    protected void doEncodeEnd(ResponseWriter writer, FacesContext context, UIComponent component)
        throws IOException {
        writer.startElement(HtmlConstants.TABLE_ELEMENT, component);
        String clientId = component.getClientId(context);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":r", null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-rord", null);
        writer.writeAttribute(HtmlConstants.CELLPADDING_ATTRIBUTE, "0", null);
        writer.writeAttribute(HtmlConstants.CELLSPACING_ATTRIBUTE, "0", null);
        writer.startElement(HtmlConstants.TR_ELEMENT, component);
        writer.startElement(HtmlConstants.TH_ELEM, component);
        writer.write(" ");
        writer.endElement(HtmlConstants.TH_ELEM);
        writer.endElement(HtmlConstants.TR_ELEMENT);
        for (int i = 0; i < 6; i++) {
            writer.startElement(HtmlConstants.TR_ELEMENT, component);
            writer.startElement(HtmlConstants.TD_ELEM, component);
            writer.write(" ");
            writer.endElement(HtmlConstants.TD_ELEM);
            writer.endElement(HtmlConstants.TR_ELEMENT);
        }
        writer.endElement(HtmlConstants.TABLE_ELEMENT);
        writer.startElement(HtmlConstants.DIV_ELEM, component);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":d", null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-rsz-mkr", null);
        writer.endElement(HtmlConstants.DIV_ELEM);
        writer.startElement(HtmlConstants.DIV_ELEM, component);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":rm", null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-rord-mkr", null);
        writer.endElement(HtmlConstants.DIV_ELEM);
        writer.startElement(HtmlConstants.INPUT_ELEM, component);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId + ":wi", null);
        writer.writeAttribute(HtmlConstants.NAME_ATTRIBUTE, clientId + ":wi", null);
        writer.writeAttribute(HtmlConstants.TYPE_ATTR, HtmlConstants.INPUT_TYPE_HIDDEN, null);
        writer.endElement(HtmlConstants.INPUT_ELEM);
        encodeSelectionInput(writer, context, component);
        AjaxFunction ajaxFunction = buildAjaxFunction(context, component);
        ajaxFunction.getOptions().setClientParameters(CLIENT_PARAMS);
        
        Map attributes = component.getAttributes();
        Map options = new HashMap();
        addToScriptHash(options, "selectionMode", attributes.get("selectionMode"),
            SelectionMode.multiple);
        addToScriptHash(options, "onbeforeselectionchange", RenderKitUtils.getAttributeAndBehaviorsValue(context,
            component, EVENT_ATTRIBUTES.get("onbeforeselectionchange")), null, ScriptHashVariableWrapper.eventHandler);
        addToScriptHash(options, "onselectionchange", RenderKitUtils.getAttributeAndBehaviorsValue(context,
            component, EVENT_ATTRIBUTES.get("onselectionchange")), null, ScriptHashVariableWrapper.eventHandler);
        StringBuilder builder = new StringBuilder("new RichFaces.ui.ExtendedDataTable('");
        builder.append(clientId).append("', ").append(getRowCount(component))
            .append(", function(event, clientParams) {").append(ajaxFunction.toScript()).append(";}");
        if (!options.isEmpty()) {
            builder.append(",").append(ScriptUtils.toScript(options));
        }
        builder.append(");");
        getUtils().writeScript(context, component, builder.toString());
        writer.endElement(HtmlConstants.DIV_ELEM);
    }
    
    private int getRowCount(UIComponent component) {
        UIDataTableBase table = (UIDataTableBase) component;
        int rows = table.getRows();
        int rowCount = table.getRowCount() - table.getFirst();
        if (rows > 0) {
            rows = Math.min(rows, rowCount);
        } else {
            rows = rowCount;
        }

        return rows;
    }

    private void encodeStyle(RendererState state) throws IOException {
        FacesContext context = state.getContext();
        ResponseWriter writer = context.getResponseWriter();
        UIDataTableBase table = state.getRow();
        
        PartialViewContext pvc = context.getPartialViewContext();
        if (!pvc.isAjaxRequest()) {
            writer.startElement("style", table);
            writer.writeAttribute(HtmlConstants.TYPE_ATTR, "text/css", null);
            writer.writeText(getCSSText(context, table), null);
            writer.endElement("style");
        } else {
            writer.startElement(HtmlConstants.SCRIPT_ELEM, table);
            writer.writeAttribute(HtmlConstants.TYPE_ATTR, HtmlConstants.TEXT_JAVASCRIPT_TYPE, null);
            
            String cssText = getCSSText(context, table);
            JSFunction function = new JSFunction("RichFaces.utils.addCSSText", cssText, table.getClientId(context) 
                + ":st");
            
            writer.writeText(function.toScript(), null);
            
            writer.endElement(HtmlConstants.SCRIPT_ELEM);
        }
        
    }

    protected String getCSSText(FacesContext context, UIDataTableBase table) throws IOException {
        StringBuilder sb = new StringBuilder();
        
        sb.append("div.rf-edt-cnt { width: 100%; }"); // TODO getNormalizedId(context, state.getGrid())

        Iterator columns = table.columns();
        while (columns.hasNext()) {
            UIComponent column = (UIComponent) columns.next();
            String id = column.getId();
            if (id == null) {
                column.getClientId(context); // hack initialize id
                id = column.getId();
            }
            String width = getColumnWidth(column);
            sb.append(".rf-edt-c-" + id + " {"); // TODO getNormalizedId(context,
            sb.append("width: " + width + ";");
            sb.append("}");
        }
        
        return sb.toString();
    }

    public void encodeRow(ResponseWriter writer, FacesContext facesContext, RowHolderBase rowHolder)
        throws IOException {
        RendererState state = (RendererState) rowHolder;
        AbstractExtendedDataTable table = (AbstractExtendedDataTable) state.getRow();
        writer.startElement(HtmlConstants.TR_ELEMENT, table);
        
        if (rowHolder.getRow() instanceof UIDataTableBase) {
            renderRowHandlers(facesContext, (UIDataTableBase)rowHolder.getRow());
        }
        
        String rowClass = getRowClass(rowHolder);
        
        StringBuilder builder = new StringBuilder();
        Collection selection = table.getSelection();
        if (selection != null && selection.contains(table.getRowKey())) {
            builder.append("rf-edt-r-sel");
        }
        if (table.getRowKey().equals(table.getAttributes().get("activeRowKey"))) {
            if (builder.length() > 0) {
                builder.append(' ');
            }
            builder.append("rf-edt-r-act");
        }
        if (table.getRowKey().equals(table.getAttributes().get("shiftRowKey"))) {
            if (builder.length() > 0) {
                builder.append(' ');
            }
            builder.append("rf-edt-r-sht");
        }
        rowClass = concatClasses(builder.toString(), rowClass); 
        if (rowClass.length() > 0) {
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, rowClass, null);
        }
        Iterator columns = null;
        Part part = state.getPart();
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE,
            table.getContainerClientId(facesContext) + ":" + part.getName().getId(), null);
        columns = part.getColumns().iterator();
        int columnNumber = 0;
        while (columns.hasNext()) {
            UIComponent column = (UIComponent) columns.next();
            if (column.isRendered()) {
                writer.startElement(HtmlConstants.TD_ELEM, table);
                
                String columnClass = concatClasses(getColumnClass(rowHolder, columnNumber), 
                    column.getAttributes().get(HtmlConstants.STYLE_CLASS_ATTR));
                if (!"".equals(columnClass)) {
                    writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, columnClass, null);
                }
                
                String columnStyle = (String)column.getAttributes().get(HtmlConstants.STYLE_ATTRIBUTE);
                if (!"".equals(columnStyle)) {
                    writer.writeAttribute(HtmlConstants.STYLE_ATTRIBUTE, columnStyle, null);
                }
                
                columnNumber++;
                
                writer.startElement(HtmlConstants.DIV_ELEM, table);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-c rf-edt-c-"
                    + column.getId(), null);
                writer.startElement(HtmlConstants.DIV_ELEM, column);
                writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-edt-c-cnt", null);
                renderChildren(facesContext, column);
                writer.endElement(HtmlConstants.DIV_ELEM);
                writer.endElement(HtmlConstants.DIV_ELEM);
                writer.endElement(HtmlConstants.TD_ELEM);
            }
        }
        writer.endElement(HtmlConstants.TR_ELEMENT);
    }

    protected void doDecode(FacesContext context, UIComponent component) {
        super.doDecode(context, component);
        Map map = context.getExternalContext().getRequestParameterMap();
        String clientId = component.getClientId(context);
        updateWidthOfColumns(context, component, map.get(clientId + ":wi"));
        if (map.get(clientId) != null) {
            updateColumnsOrder(context, component, map.get("rich:columnsOrder"));
        }
        if (map.get(clientId) != null) {
            updateClientFirst(context, component, map.get("rich:clientFirst"));
        }
        
        decodeSortingFiltering(context, component);
    }

    private void updateWidthOfColumns(FacesContext context, UIComponent component, String widthString) {
        if (widthString != null && widthString.length() > 0) {
            String[] widthArray = widthString.split(",");
            for (int i = 0; i < widthArray.length; i++) {
                String[] widthEntry = widthArray[i].split(":");
                UIComponent column = component.findComponent(widthEntry[0]);
                updateAttribute(context, column, "width", widthEntry[1]);
            }
        }
    }
    
    private void updateColumnsOrder(FacesContext context, UIComponent component, String columnsOrderString) {
        if (columnsOrderString != null && columnsOrderString.length() > 0) {
            String[] columnsOrder = columnsOrderString.split(",");
            updateAttribute(context, component, "columnsOrder", columnsOrder);
            context.getPartialViewContext().getRenderIds().add(component.getClientId(context)); //TODO Use partial re-rendering here.
        }
    }
    
    private void updateClientFirst(FacesContext context, UIComponent component, String clientFirst) {
        if (clientFirst != null && clientFirst.length() > 0) {
            Integer value = Integer.valueOf(clientFirst);
            Map attributes = component.getAttributes();
            if (!value.equals(attributes.get("clientFirst"))) {
                attributes.put(AbstractExtendedDataTable.SUBMITTED_CLIENT_FIRST, value);
                context.getPartialViewContext().getRenderIds().add(
                    component.getClientId(context) + "@" + AbstractExtendedDataTable.SCROLL);
            }
        }
    }
        
    /**
     * @deprecated
     * TODO Remove this method when width in relative units in columns will be implemented.
     * @param column
     * @return width
     */
    private String getColumnWidth(UIComponent column) {
        String width = (String) column.getAttributes().get("width");
        if (width == null || width.length() == 0 || width.indexOf("%") != -1) {
            width = "100px";
        }
        return width;
    }
}