/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.renderkit;

import java.io.IOException;
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 java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.component.AjaxActionComponent;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.Column;
import org.richfaces.component.Row;
import org.richfaces.component.UIColumn;
import org.richfaces.component.UIDataTable;
import org.richfaces.component.UIExtendedDataTable;
import org.richfaces.component.nsutils.NSUtils;
import org.richfaces.event.extdt.ChangeColumnVisibilityEvent;
import org.richfaces.event.extdt.ColumnResizeEvent;
import org.richfaces.event.extdt.DragDropEvent;
import org.richfaces.event.extdt.ExtTableFilterEvent;
import org.richfaces.event.extdt.ExtTableSortEvent;
import org.richfaces.model.Ordering;
import org.richfaces.model.SortField2;
import org.richfaces.model.impl.expressive.JavaBeanWrapper;
import org.richfaces.model.impl.expressive.ObjectWrapperFactory;
import org.richfaces.model.impl.expressive.WrappedBeanComparator2;
import org.richfaces.renderkit.html.HTMLEncodingContributor;
import org.richfaces.renderkit.html.RichTableMenuRenderer;
import org.richfaces.renderkit.html.TableDragDropRenderer;
import org.richfaces.renderkit.html.TableMenuRenderer;
import org.richfaces.renderkit.html.iconimages.DataTableIconSortNone;
import org.richfaces.renderkit.html.images.TriangleIconDown;
import org.richfaces.renderkit.html.images.TriangleIconUp;

/**
 * @author shura
 * 
 */
public abstract class AbstractExtendedTableRenderer extends
        AbstractExtendedRowsRenderer {

    private static final String SORT_FILTER_PARAMETER = "fsp";

    private static final String SORT_DIR_PARAMETER = "sortDir";

    private static final String SORT_DIR_PARAMETER_ASC = "asc";

    private static final String SORT_DIR_PARAMETER_DESC = "desc";

    private static final String FILTER_INPUT_FACET_NAME = "filterValueInput";

    private static final String COL_RESIZE_ACTION_NAME = "columnResizeAction";

    private final static String CHANGE_COL_VISIBILITY = "change_col_v";

    private static final String SORT_FUNCTION = "sortFunction";
    
    private static final String GROUP_FUNCTION = "groupFunction";

    private static final String SHOW_MENU_FUNCTION = "showMenuFunction";

    private static final String ON_RESIZE_FUNCTION = "onColumnResize";

    private static final String GROUP_FILTER_PARAMETER = "groupParam";

    private static final String ON_GROUP_TOGGLE_FUNCTION = "onGroupToggleFunction";

    private static final String GROUP_TOGGLE_ACTION_NAME = "groupToggleAction";
    
    private static final String MIN_COLUMN_WIDTH = "20";
    
    private final Log log = LogFactory.getLog(UIExtendedDataTable.class);

    /**
     * Encode all table structure - colgroups definitions, caption, header,
     * footer
     * 
     * @param context
     * @param table
     * @throws IOException
     */

    public void encodeColumns(FacesContext context,
            UIExtendedDataTable table) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        Iterator<UIColumn> cols = table.getSortedColumns();
        while (cols.hasNext()) {
            UIColumn col = cols.next();
            if (col.isRendered()) {
                writer.startElement("col", table);
                String colWidth = table.getColumnSize(col);
                writer.writeAttribute("width", colWidth, null);
                writer.endElement("col");
            }
        }
        writer.startElement("col", table);
        writer.endElement("col");
    }

    public void encodeHeader(FacesContext context, UIExtendedDataTable table)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        UIComponent header = table.getHeader();
        boolean columnFacetHeaderPresent = isColumnFacetPresent(table, "header");
        Iterator<UIColumn> colums = table.getSortedColumns();
        // int numberOfColumns = getColumnsCount(table);
        int numberOfColumns = table.getVisibleColumnsCount() + 1;
        String headerClass = (String) table.getAttributes().get("headerClass");
        writer.startElement("thead", table);
        writer.writeAttribute(HTML.id_ATTRIBUTE, table.getBaseClientId(context) + ":header", null);
        writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-thead", null);
        //add special fake IE row
    	writer.startElement("tr", header);
        encodeStyleClass(writer, null,
                "extdt-header rich-extdt-header", headerClass, null);
        writer.writeAttribute(HTML.id_ATTRIBUTE, table
                .getBaseClientId(context)
                + ":fakeIeRow", null);
        for (int i = 0; i < numberOfColumns; i++) {
            writer.startElement("th", header);
            encodeStyleClass(writer, null,
                    "extdt-headercell extdt-fakeierow rich-extdt-headercell",
                    headerClass, null);
            writer.endElement("th");
        }
        writer.endElement("tr");
        if (header != null) {
        	
            encodeTableHeaderFacet(context, numberOfColumns, writer,
                    header, "extdt-header rich-extdt-header",
                    "extdt-header-continue rich-extdt-header-continue",
                    "extdt-headercell rich-extdt-headercell",
                    headerClass, "th", "header");
        }
        
        writer.startElement("tr", table);
        writer.writeAttribute(HTML.id_ATTRIBUTE, table
                .getBaseClientId(context)
                + ":headerRow", null);
        encodeStyleClass(writer, null,
                "extdt-subheader rich-extdt-subheader", null,
                headerClass);
        if (columnFacetHeaderPresent) {
        	encodeHeaderFacets(
                context,
                writer,
                table,
                colums,
                "extdt-menucell extdt-subheadercell rich-extdt-subheadercell",
                headerClass, "header", "th", numberOfColumns);
            /* encoding additional empty column used in resizing columns */
            writer.startElement("th", table);
            encodeStyleClass(writer, null,
                    "extdt-empty-cell rich-extdt-subheadercell", null,
                    null);
            encodeNBSP(writer);
            writer.endElement("th");
        }
        else{
        	writer.writeAttribute(HTML.style_ATTRIBUTE, "height: 0px", null);
        	for (int i = 0; i < numberOfColumns; i++) {
                writer.startElement("th", header);
                encodeNBSP(writer);
                writer.endElement("th");
            }
        }
        writer.endElement("tr");
    	encodeFilterRow(context, writer, table, table
                .getSortedColumns(),
                "extdt-subheadercell rich-extdt-subheadercell",
                headerClass, "filter", HTML.th_ELEM);
        writer.endElement("thead");
    }
    
    private void encodeNBSP(ResponseWriter writer) throws IOException {
    	writer.write("&nbsp;");
    }

    private void encodeFilterRow(FacesContext context, ResponseWriter writer,
            UIExtendedDataTable table, Iterator<UIColumn> headers,
            String skinCellClass, String headerClass, String facetName,
            String element) throws IOException {
        
        if (filteringEnabled(table)) {
            writer.startElement(HTML.TR_ELEMENT, table);
            writer.writeAttribute(HTML.id_ATTRIBUTE, table
                    .getBaseClientId(context)
                    + ":filterRow", null);
            encodeStyleClass(writer, null,
                    "extdt-table-filterrow rich-extdt-subheader", null,
                    headerClass);
            encodeFilterInputs(context, writer, table, headers, skinCellClass,
                    headerClass, facetName, element);
            writer.startElement(HTML.th_ELEM, table);
            encodeStyleClass(writer, null,
                    "extdt-empty-cell rich-extdt-subheadercell", null, null);
            encodeNBSP(writer);
            writer.endElement(HTML.th_ELEM);
            writer.endElement(HTML.TR_ELEMENT);
        }
    }

    /**
     * Checks if column has filtering enabled
     * 
     * @param dataColumn column to inspect
     * @return true if filtering is enabled, false otherwise
     * @author pbuda
     */
    private boolean internalFilterEnabledColumn(UIColumn dataColumn) {
        return dataColumn.getFilterMethod() == null
                && dataColumn.getValueExpression("filterExpression") == null
                && dataColumn.getValueExpression("filterBy") != null;
    }

    /**
     * Checks whether filtering is enabled in this table
     * 
     * @param table table to inspect
     * @return true if filtering is enabled, false otherwise
     * @author pbuda
     */
    private boolean filteringEnabled(UIExtendedDataTable table) {
        Iterator<UIColumn> columns = table.getSortedColumns();
        boolean enabled = false;
        while (columns.hasNext()) {
            UIColumn col = columns.next();
            if ((internalFilterEnabledColumn(col) || (col.getFacet("filter") != null)) && 
            		col.isRendered()
				) {
				enabled = true;
				break;
			}
        }
        return enabled;
    }

    /**
     * Encodes a new row of the table and places filter inputs for corresponding
     * columns in that new row
     * 
     * @param context
     *            current FacesContext instance
     * @param writer
     *            ResponseWriter for this context
     * @param table
     *            table instance
     * @param headers
     *            iterator over headers in table
     * @param skinCellClass
     *            css class of skin
     * @param headerClass
     *            css class of header
     * @param facetName
     *            facet to encode
     * @param element
     *            element to encode
     * @throws IOException
     *             if ResponseWriter fails it's operation
     * @author pbuda
     */
    private void encodeFilterInputs(FacesContext context,
            ResponseWriter writer, UIDataTable table,
            Iterator<UIColumn> headers, String skinCellClass,
            String headerClass, String facetName, String element)
            throws IOException {
        while (headers.hasNext()) {
            UIColumn column = headers.next();
            if (column instanceof UIColumn) {
                if (column.isRendered()) {
                    writer.startElement(element, table);
                    String classAttribute = facetName + "Class";
                    String columnHeaderClass = (String) column.getAttributes()
                            .get(classAttribute);
                    encodeStyleClass(writer, null, skinCellClass, headerClass,
                            columnHeaderClass);
                    UIColumn dataColumn = (UIColumn) column;
                    if (internalFilterEnabledColumn(dataColumn)) {
                        writer.startElement(HTML.DIV_ELEM, column);
                        addInplaceInput(context, column);
                        writer.endElement(HTML.DIV_ELEM);
                    }
                    else if (dataColumn.getFacet("filter") != null) {
                    	writer.startElement(HTML.DIV_ELEM, column);
                    	dataColumn.getFacet("filter").encodeAll(context);
                    	writer.endElement(HTML.DIV_ELEM);
                    }
                    else {
                    	encodeNBSP(writer);
                    }
                    writer.endElement(element);
                }
            }
        }
    }
    
    private JSFunctionDefinition buildSetFocusFunctionDef(String elementId){
    	JSFunctionDefinition function = new JSFunctionDefinition("event");
		function.addToBody("" + "var element = document.getElementById('"
				+ elementId + "');" + "if (element) {" + "	element.focus();"
				+ "}");
		return function;
    }

    public boolean isColumnFacetPresent(UIDataTable table, String facetName) {
        Iterator<UIComponent> columns = table.columns();
        boolean result = false;
        while (columns.hasNext() && !result) {
            UIComponent component = columns.next();
            if (isColumnRendered(component)) {
                if (null != component.getFacet(facetName)) {
                    result = true;
                } /*
                 * else if(component instanceof Column) { Column column =
                 * (Column)component; result = column.isSelfSorted(); }
                 */
            }
        }
        return result;
    }

    /**
     * @param component
     * @return
     */
    protected boolean isColumnRendered(UIComponent component) {
        boolean rendered = true;
        try {
            rendered = component.isRendered();
        } catch (Exception e) {
            // DO nothing, rendered binded to row variable;
        }
        return rendered;
    }

    protected void encodeHeaderFacets(FacesContext context,
            ResponseWriter writer, UIDataTable table,
            Iterator<UIColumn> headers, String skinCellClass,
            String headerClass, String facetName, String element, int colCount)
            throws IOException {
        int t_colCount = 0;

        // HeaderEncodeStrategy richEncodeStrategy = new
        // RichHeaderEncodeStrategy();
        // HeaderEncodeStrategy simpleEncodeStrategy = new
        // SimpleHeaderEncodeStrategy();

        while (headers.hasNext()) {
            UIColumn column = headers.next();
            if (isColumnRendered(column)) {
                if ((Integer) column.getAttributes().get("colspan") != null) {
                    t_colCount = t_colCount
                            + ((Integer) column.getAttributes().get("colspan"))
                                    .intValue();
                } else {
                    t_colCount++;
                }
                if (t_colCount > colCount) {
                    break;
                }

                String classAttribute = facetName + "Class";
                String columnHeaderClass = (String) column.getAttributes().get(
                        classAttribute);

                writer.startElement(element, column);
                encodeStyleClass(writer, null, skinCellClass, headerClass,
                        columnHeaderClass);
                writer.writeAttribute("scope", "col", null);
                getUtils().encodeAttribute(context, column, "colspan");

                boolean sortableColumn = column
                        .getValueExpression("comparator") != null
                        || column.getValueExpression("sortBy") != null;
                column.getAttributes().put("sortable",
                        Boolean.valueOf(sortableColumn));

                HeaderEncodeStrategy strategy = (column instanceof UIColumn && "header"
                        .equals(facetName)) ? new G3HeaderEncodeStrategy()
                        : new SimpleHeaderEncodeStrategy();

                strategy.encodeBegin(context, writer, column, facetName,
                        sortableColumn);

                UIComponent facet = column.getFacet(facetName);
                if (facet != null && isColumnRendered(facet)) {
                    renderChild(context, facet);
                }

                strategy.encodeEnd(context, writer, column, facetName,
                        sortableColumn);

                writer.endElement(element);

            }

        }
    }

    public void encodeFooter(FacesContext context, UIExtendedDataTable table)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        UIComponent footer = table.getFooter();
        boolean columnFacetPresent = isColumnFacetPresent(table, "footer");
        Iterator<UIColumn> tableColumns = table.getSortedColumns();
        // int columns = getColumnsCount(table);
        int columns = table.getVisibleColumnsCount() + 1;
        if (footer != null || columnFacetPresent) {
            writer.startElement("tfoot", table);
            writer.writeAttribute(HTML.id_ATTRIBUTE, table.getBaseClientId(context)
                    + ":footer", null);
            String footerClass = (String) table.getAttributes().get(
                    "footerClass");

            if (columnFacetPresent) {
                writer.startElement("tr", table);
                encodeStyleClass(writer, null,
                        "extdt-subfooter rich-extdt-subfooter", null,
                        footerClass);

                encodeHeaderFacets(context, writer, table, tableColumns,
                        "extdt-subfootercell rich-extdt-subfootercell",
                        footerClass, "footer", "td", columns);
                writer.startElement("td", table);
                encodeStyleClass(writer, null,
                        "extdt-subfootercell rich-extdt-subfootercell", null,
                        null);
                writer.endElement("td");
                writer.endElement("tr");
            }
            if (footer != null) {
                encodeTableHeaderFacet(context, columns, writer, footer,
                        "extdt-footer rich-extdt-footer",
                        "extdt-footer-continue rich-extdt-footer-continue",
                        "extdt-footercell rich-extdt-footercell",
                        footerClass, "td", "footer");
            }
            writer.endElement("tfoot");
        }

    }

    /**
     * Encodes the false row to enable proper rendering in IE when rows grouping
     * is on (IE won't render the table properly if it has first row with
     * colspan).
     * 
     * @param table
     *            to render false row to,
     * @param context
     *            of the table.
     * @throws IOException
     */

    private void encodeFakeIeRow(FacesContext context,
            UIExtendedDataTable table, ExtendedTableHolder holder)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        int numberOfColumns = getColumnsCount(table) + 1;

        writer.startElement("tr", table);
        encodeStyleClass(writer, null, "extdt-fakeierow ", null, null);
        writer.writeAttribute(HTML.id_ATTRIBUTE, table.getBaseClientId(context)
                + ":body:fakeIeRow", null);
        for (int i = 0; i < numberOfColumns; i++) {
            writer.startElement("td", table);
            encodeStyleClass(writer, null, "extdt-fakeierow " + getCellSkinClass(), null, null);
            writer.endElement("td");
        }
        writer.endElement("tr");

    }

    /**
     * Encodes the grouping row.
     * 
     * @param table
     *            to render group row to,
     * @param context
     *            of the table.
     * @throws IOException
     */
    private void encodeGroupRow(FacesContext context,
            UIExtendedDataTable table, ExtendedTableHolder holder)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        int numberOfColumns = getColumnsCount(table) + 1;
        int actGroupRow = holder.getGroupRowCounter();
        writer.startElement(HTML.TR_ELEMENT, table);
        writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-group-row", null);
        writer.writeAttribute(HTML.id_ATTRIBUTE, table.getBaseClientId(context)
                + ":group-row:" + actGroupRow, null);

        writer.writeAttribute("expanded",
                table.groupIsExpanded(actGroupRow) ? "true" : "false", null);
        writer.writeAttribute("groupindex", String.valueOf(actGroupRow), null);

        writer.startElement(HTML.td_ELEM, table);
        writer.writeAttribute(HTML.class_ATTRIBUTE,
                "extdt-group-cell", null);
        writer.writeAttribute("colspan", numberOfColumns, null);
        writer.startElement(HTML.SPAN_ELEM, table);
        writer.startElement(HTML.IMG_ELEMENT, table);

        String imagePlusUri = getResource(
                "/org/richfaces/renderkit/html/images/plusIcon.gif").getUri(
                context, null);
        String imageMinusUri = getResource(
                "/org/richfaces/renderkit/html/images/minusIcon.gif").getUri(
                context, null);
        if (table.groupIsExpanded(actGroupRow)) {
            writer.writeAttribute(HTML.src_ATTRIBUTE, imageMinusUri, null);
            writer.writeAttribute("alternatesrc", imagePlusUri, null);
        } else {
            writer.writeAttribute(HTML.src_ATTRIBUTE, imagePlusUri, null);
            writer.writeAttribute("alternatesrc", imageMinusUri, null);
        }
        writer.endElement(HTML.IMG_ELEMENT);
        writer.endElement(HTML.SPAN_ELEM);
        writer.startElement(HTML.SPAN_ELEM, table);
        writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-group-text", null);
        writer.writeAttribute(HTML.style_ATTRIBUTE, "font-weight: bold;", null);
        String label = holder.getGroupingColumnLabel();
        writer.writeText((label == null) ? "" : label + ": ", null);
        writer.endElement(HTML.SPAN_ELEM);
        renderChildren(context, holder.getGroupingColumn());
        writer.startElement(HTML.SPAN_ELEM, table);
        writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-group-text", null);
        writer.endElement(HTML.SPAN_ELEM);
        writer.endElement(HTML.td_ELEM);
        writer.endElement(HTML.TR_ELEMENT);
        holder.nextGroupRow();
    }

    protected boolean rowGroupChanged(FacesContext context,
            ExtendedTableHolder holder) {
        UIExtendedDataTable table = holder.getTable();
        if (holder.getLastData() == null)
            return true;

        // get sort fields
        List<SortField2> sortFields = table.getSortFields();
        // get group field which is actually the first from sort fields
        List<SortField2> groupFields = new ArrayList<SortField2>();
        if (!sortFields.isEmpty())
            groupFields.add(sortFields.get(0));
        // create wrapper factory
        ObjectWrapperFactory wrapperFactory = new ObjectWrapperFactory(context,
                table.getVar(), groupFields);
        // create wrapper for last data
        JavaBeanWrapper wrappedLstD = wrapperFactory.wrapObject(holder
                .getLastData());
        // create wrapper for current data
        JavaBeanWrapper wrappedActD = wrapperFactory.wrapObject(table
                .getRowData());
        // create comparator
        WrappedBeanComparator2 wrappedBeanComparator = new WrappedBeanComparator2(
                (groupFields));
        // compare last and current data
        return (wrappedBeanComparator.compare(wrappedLstD, wrappedActD) != 0);
    }// rowGroupChanged

    public void encodeOneRow(FacesContext context, ExtendedTableHolder holder)
            throws IOException {
        UIExtendedDataTable table = holder.getTable();
        ResponseWriter writer = context.getResponseWriter();
        Iterator<UIColumn> iter = table.getSortedColumns();
        boolean first = true;
        int currentColumn = 0;
        UIColumn column = null;
        if (holder.isFirstRow()) {
            encodeFakeIeRow(context, table, holder);
        }
        if (holder.isGroupingOn() && (rowGroupChanged(context, holder))) {
            encodeGroupRow(context, table, holder);
        }
        holder.setFirstRow(false);
        
        while (iter.hasNext()) {
            column = iter.next();
            // Start new row for first column - expect a case of the detail
            // table, wich will be insert own row.
            boolean isRow = (column instanceof Row);
            if (first && !isRow) {
                encodeRowStart(context, getFirstRowSkinClass(), holder
                        .getRowClass(), table, holder, writer);
            }

            // TODO PKA CHANGE COLUMN RENDERER TO GET RID OF && false
//            if (false && (column instanceof Column)) {
//                boolean breakBefore = ((Column) column).isBreakBefore()
//                        || isRow;
//                if (breakBefore && !first) {
//                    // close current row
//                    writer.endElement(HTML.TR_ELEMENT);
//                    // reset columns counter.
//                    currentColumn = 0;
//                    // Start new row, expect a case of the detail table, wich
//                    // will be insert own row.
//                    if (!isRow) {
//                        holder.nextRow();
//                        encodeRowStart(context, holder.getRowClass(), table,
//                                holder, writer);
//                    }
//                }
//                encodeCellChildren(context, column,
//                        first ? getFirstRowSkinClass() : null,
//                        getRowSkinClass(), holder.getRowClass(),
//                        getCellSkinClass(), holder
//                                .getColumnClass(currentColumn));
//                // renderChild(context, column);
//                if (isRow && iter.hasNext()) {
//                    // Start new row for remained columns.
//                    holder.nextRow();
//                    encodeRowStart(context, holder.getRowClass(), table,
//                            holder, writer);
//                    // reset columns counter.
//                    currentColumn = -1;
//                }
//            } else 
            if (column.isRendered()) {
                // UIColumn don't have own renderer
                writer.startElement(HTML.td_ELEM, table);
                getUtils().encodeId(context, column);
                encodeStyleClass(writer, null, getCellSkinClass(), holder.getColumnClass(currentColumn),
                        column.getAttributes().get("styleClass"));
                encodeStyle(writer, null, null, null, column.getAttributes().get("style"));
                // TODO - encode column attributes.
                writer.startElement(HTML.DIV_ELEM, table);
                writer.writeAttribute("class", "extdt-cell-div", null);
                // IE
                renderChildren(context, column);
                writer.endElement(HTML.DIV_ELEM);
                writer.endElement(HTML.td_ELEM);
            }
            currentColumn++;
            first = false;
        }
        // encode additional empty row for resizing
        writer.startElement(HTML.td_ELEM, table);
        String columnClass = holder.getColumnClass(currentColumn);
        encodeStyleClass(writer, null, "extdt-empty-cell rich-extdt-cell", null, columnClass);
        writer.endElement(HTML.td_ELEM);
        // Close row if then is open.
        if (!first && !(column instanceof Row)) {
            writer.endElement(HTML.TR_ELEMENT);
        }
    }

    protected void encodeRowStart(FacesContext context, String rowClass,
            UIDataTable table, TableHolder holder, ResponseWriter writer)
            throws IOException {
        encodeRowStart(context, getRowSkinClass(), rowClass, table, holder,
                writer);
    }

    /**
     * @return
     */
    protected String getRowSkinClass() {
        return "extdt-row rich-extdt-row";
    }

    /**
     * @return
     */
    protected String getFirstRowSkinClass() {
        return "extdt-firstrow rich-extdt-firstrow";
    }

    /**
     * @return
     */
    protected String getCellSkinClass() {
        return "extdt-cell rich-extdt-cell";

    }

    protected void encodeRowStart(FacesContext context, String skinClass,
            String rowClass, UIDataTable table, TableHolder holder,
            ResponseWriter writer) throws IOException {
        writer.startElement(HTML.TR_ELEMENT, table);
        encodeRowId(context, writer, table, holder.getRowCounter());
        encodeStyleClass(writer, null, skinClass, null, rowClass);
        encodeRowEvents(context, table);
    }

    /**
     * Row ID generator
     * 
     * @param context
     *            current FacesContext
     * @param writer
     *            ResponseWriter for FacesContext
     * @param table
     *            table for which this row is being encoded
     * @param rowId
     *            new rowId to encode
     * @throws IOException
     *             if ResponseWriter fails
     */
    protected void encodeRowId(FacesContext context, ResponseWriter writer,
            UIDataTable table, int rowId) throws IOException {
        String ownerId = table.getBaseClientId(context);
        getUtils().writeAttribute(writer, "id", ownerId + ":n:" + rowId);
    }

    /**
     * Calculate total number of columns in table.
     * 
     * @param context
     * @param table
     * @return
     */
    protected int getColumnsCount(UIDataTable table) {
        int count = 0;
        // check for exact value in component
        Integer span = (Integer) table.getAttributes().get("columns");
        if (null != span && span.intValue() != Integer.MIN_VALUE) {
            count = span.intValue();
        } else {
            // calculate max html columns count for all columns/rows children.
            Iterator<UIComponent> col = table.columns();
            count = calculateRowColumns(col);
        }
        return count;
    }

    /**
     * Calculate max number of columns per row. For rows, recursive calculate
     * max length.
     * 
     * @param col -
     *            Iterator other all columns in table.
     * @return
     */
    protected int calculateRowColumns(Iterator<UIComponent> col) {
        int count = 0;
        int currentLength = 0;
        while (col.hasNext()) {
            UIComponent column = (UIComponent) col.next();
            if (column.isRendered()) {
                if (column instanceof Row) {
                    // Store max calculated value of previsous rows.
                    if (currentLength > count) {
                        count = currentLength;
                    }
                    // Calculate number of columns in row.
                    currentLength = calculateRowColumns(((Row) column)
                            .columns());
                    // Store max calculated value
                    if (currentLength > count) {
                        count = currentLength;
                    }
                    currentLength = 0;
                } else if (column instanceof Column) {
                    Column tableColumn = (Column) column;
                    // For new row, save length of previsous.
                    if (tableColumn.isBreakBefore()) {
                        if (currentLength > count) {
                            count = currentLength;
                        }
                        currentLength = 0;
                    }
                    Integer colspan = (Integer) column.getAttributes().get(
                            "colspan");
                    // Append colspan of this column
                    if (null != colspan
                            && colspan.intValue() != Integer.MIN_VALUE) {
                        currentLength += colspan.intValue();
                    } else {
                        currentLength++;
                    }
                } else if (column instanceof javax.faces.component.UIColumn) {
                    // UIColumn always have colspan == 1.
                    currentLength++;
                }

            }
        }
        if (currentLength > count) {
            count = currentLength;
        }
        return count;
    }

    public void encodeScriptIfNecessary(FacesContext context,
            UIExtendedDataTable table) throws IOException {
        boolean shouldRender = false;
        Iterator<UIColumn> columns = table.getSortedColumns();
        while (columns.hasNext() && !shouldRender) {
            UIColumn next = columns.next();
            shouldRender = next.isSortable();
            shouldRender = true;// shouldRender || (next instanceof
            // HtmlDataColumn);
        }
        shouldRender = true;
        if (shouldRender) {
            getUtils().writeScript(context, table,
                    createClientDataTable(context, table));
        }
    }

    public String createClientDataTable(FacesContext context,
            UIExtendedDataTable table) {
        JSFunction function = new JSFunction("new ExtendedDataTable.DataTable");
        function.addParameter(table.getBaseClientId(context));
        ScriptOptions scriptOptions = new ScriptOptions(table);
        // add on resize column AJAX function
        scriptOptions.addOption(ON_RESIZE_FUNCTION, getOnResizeFunctionDef(
                context, table));
        scriptOptions.addOption(SORT_FUNCTION, getSortFunctionDef(context,
                table));
        scriptOptions.addOption(GROUP_FUNCTION, getGroupFunctionDef(context,
                table));
        scriptOptions.addOption(SHOW_MENU_FUNCTION, getShowMenuFunction(
                context, table));
        /* Not needed if we do not save open/close state */
        scriptOptions.addOption(ON_GROUP_TOGGLE_FUNCTION,
                getOnGroupToggleFunctionDef(context, table));
        scriptOptions.addOption("minColumnWidth", MIN_COLUMN_WIDTH);
        composite.mergeScriptOptions(scriptOptions, context, table);
        function.addParameter(scriptOptions);
        return function.toScript();
    }

    protected JSFunctionDefinition getShowMenuFunction(FacesContext context,
            UIDataTable table) {
        return new RichTableMenuRenderer().createShowMenuEventFunction();
    }

    protected JSFunctionDefinition getSortFunctionDef(FacesContext context,
            UIDataTable table) {
        return getSortFunctionDef(context, table, null);
    }

    protected JSFunctionDefinition getSortFunctionDef(FacesContext context,
            UIDataTable table, Boolean asc) {
        JSFunctionDefinition definition = new JSFunctionDefinition();
        definition.addParameter("event");
        definition.addParameter("columnId");
        definition.addParameter("ascending");
        String id = table.getClientId(context);
        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) eventOptions
                .get("parameters");
        parameters.put(id, SORT_FILTER_PARAMETER);
//        if (asc != null) {
//            parameters.put(SORT_DIR_PARAMETER, asc ? SORT_DIR_PARAMETER_ASC
//                    : SORT_DIR_PARAMETER_DESC);
//        }
        // parameters.put(SORT_FILTER_PARAMETER, column.getClientId(context));
        JSFunctionDefinition onAjaxCompleteFunction = getOnAjaxCompleteFunction(
                context, table);
        if (onAjaxCompleteFunction != null) {
            eventOptions.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);
        }
        definition.addToBody("var options = ").addToBody(
                ScriptUtils.toScript(eventOptions)).addToBody(";\n");
        definition.addToBody("options.parameters['" + SORT_FILTER_PARAMETER
                + "'] = columnId;\n");
        definition.addToBody("" +
        		"if (ascending != null){"+
        		"	options.parameters['" + SORT_DIR_PARAMETER + "'] = (ascending ? '"+SORT_DIR_PARAMETER_ASC+"':'"+SORT_DIR_PARAMETER_DESC+"');"+
        		"}"
        );
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(new JSReference("options"));
        definition.addToBody(ajaxFunction.toScript()).addToBody(";\n");
        return definition;
    }// getSortFunctionDef

    protected JSFunction getSortFunction(FacesContext context, UIDataTable table) {
        String id = table.getClientId(context);
        Map<String, Object> requestOpts = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) requestOpts
                .get("parameters");
        parameters.put(id, SORT_FILTER_PARAMETER);
        parameters.put(SORT_DIR_PARAMETER, "{sortDirection}");
        parameters.put(SORT_FILTER_PARAMETER, "{columnId}");
        JSFunctionDefinition onAjaxCompleteFunction = getOnAjaxCompleteFunction(
                context, table);
        if (onAjaxCompleteFunction != null) {
            requestOpts.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);
        }
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(requestOpts);
        return ajaxFunction;
    }// getSortFunction  

    protected JSFunction getGroupFunction(FacesContext context,
            UIDataTable table) {
        String id = table.getClientId(context);
        Map<String, Object> requestOpts = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) requestOpts
                .get("parameters");
        parameters.put(id, GROUP_FILTER_PARAMETER);
        parameters.put(GROUP_FILTER_PARAMETER, "{columnId}");
        JSFunctionDefinition onAjaxCompleteFunction = getOnAjaxCompleteFunction(
                context, table);
        if (onAjaxCompleteFunction != null) {
            requestOpts.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);
        }
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(requestOpts);
        return ajaxFunction;
    }// getGroupFunction
    
    protected JSFunctionDefinition getGroupFunctionDef(FacesContext context,
            UIDataTable table) {
    	JSFunctionDefinition definition = new JSFunctionDefinition();
        definition.addParameter("event");
        definition.addParameter("columnId");
        String id = table.getClientId(context);
        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) eventOptions
                .get("parameters");
        parameters.put(id, GROUP_FILTER_PARAMETER);
        
        JSFunctionDefinition onAjaxCompleteFunction = getOnAjaxCompleteFunction(
                context, table);
        if (onAjaxCompleteFunction != null) {
            eventOptions.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);
        }
        definition.addToBody("var options = ").addToBody(
                ScriptUtils.toScript(eventOptions)).addToBody(";\n");
        definition.addToBody("options.parameters['" + GROUP_FILTER_PARAMETER
                + "'] = columnId;\n");
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(new JSReference("options"));
        definition.addToBody(ajaxFunction.toScript()).addToBody(";\n");
        return definition;
    }// getGroupFunctionDef
    
    protected JSFunctionDefinition getOnGroupToggleFunctionDef(
            FacesContext context, UIDataTable table) {
        JSFunctionDefinition definition = new JSFunctionDefinition();
        definition.addParameter("event");
        definition.addParameter("groupIndex");
        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) eventOptions
                .get("parameters");
        parameters.put(GROUP_TOGGLE_ACTION_NAME, GROUP_TOGGLE_ACTION_NAME);
        definition.addToBody("var options = ").addToBody(
                ScriptUtils.toScript(eventOptions)).addToBody(";\n");
        definition
                .addToBody("options.parameters['groupIndex'] = groupIndex;\n");
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(new JSReference("options"));
        definition.addToBody(ajaxFunction.toScript()).addToBody(";\n");
        return definition;
    }// getOnGroupToggleFunctionDef

    protected JSFunctionDefinition getOnResizeFunctionDef(FacesContext context,
            UIDataTable table) {
        JSFunctionDefinition definition = new JSFunctionDefinition();
        definition.addParameter("event");
        definition.addParameter("columnWidths");

        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) eventOptions
                .get("parameters");
        parameters.put(COL_RESIZE_ACTION_NAME, COL_RESIZE_ACTION_NAME);
        definition.addToBody("var options = ").addToBody(
                ScriptUtils.toScript(eventOptions)).addToBody(";\n");
        definition
                .addToBody("options.parameters['columnWidths'] = columnWidths;\n");

        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(new JSReference("options"));
        definition.addToBody(ajaxFunction.toScript()).addToBody(";\n");
        return definition;
    }

    protected JSFunction getChangeColumnVisibilityFunction(
            FacesContext context, UIDataTable table) {
        boolean ajaxSingle = true;
        Map<String, Object> requestOpts = AjaxRendererUtils.buildEventOptions(
                context, table);

        JSFunctionDefinition onAjaxCompleteFunction = getOnAjaxCompleteFunction(
                context, table);
        if (onAjaxCompleteFunction != null)
            requestOpts.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);

        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) requestOpts
                .get("parameters");
        if (parameters == null) {
            parameters = new HashMap<String, Object>();
            requestOpts.put("parameters", parameters);
        }
        if (ajaxSingle) {
            if (!parameters
                    .containsKey(AjaxRendererUtils.AJAX_SINGLE_PARAMETER_NAME))
                parameters.put(AjaxRendererUtils.AJAX_SINGLE_PARAMETER_NAME,
                        table.getClientId(context));
            if (!requestOpts.containsKey("control"))
                requestOpts.put("control", JSReference.THIS);
        }
        parameters.put(
                table.getClientId(context) + ":" + CHANGE_COL_VISIBILITY,
                "{columnId}");

        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        ajaxFunction.addParameter(requestOpts);
        return ajaxFunction;
    }

    protected JSFunction getPreSendAjaxRequestFunction(FacesContext context,
            UIDataTable table) {
        return new JSFunction(getJavaScriptVarName(context, table)
                + ".preSendAjaxRequest");
    }

    protected void doDecode(FacesContext context, UIComponent component) {
        super.doDecode(context, component);
        composite.decode(context, component);

        if (component instanceof UIExtendedDataTable) {
            UIExtendedDataTable table = (UIExtendedDataTable) component;
            Map<String, String> map = context.getExternalContext()
                    .getRequestParameterMap();
            String clientId = component.getClientId(context);

            if (SORT_FILTER_PARAMETER.equals(map.get(clientId))) {
                String sortColumnId = map.get(SORT_FILTER_PARAMETER);
                boolean isSingleSortMode = !"multi".equals(table.getSortMode());
                
                boolean sorting = false;
                boolean filtering = false;

                boolean isGroupingOn = table.isGroupingOn();
                boolean sortByGroupingColumn = false;
                // String groupingColumnId = null;
                UIColumn groupingColumn = null;
                if (isGroupingOn) {
                    groupingColumn = table.getGroupByColumn();
                    sortByGroupingColumn = (groupingColumn == null ? false
                            : groupingColumn.getClientId(context).equals(
                                    sortColumnId));
                    // groupingColumnId = table.getGroupingColumnId();
                }
                for (Iterator<UIColumn> columns = table.getChildColumns(); columns
                        .hasNext();) {
                    UIColumn column = columns.next();
                    String id = column.getId();
                    column.setId(id);

                    if (sortColumnId != null) {
                    	sorting = true;
                        boolean isGroupingColumn = (isGroupingOn && column
                                .equals(groupingColumn));
                        if (sortColumnId.equals(column.getClientId(context))) {
                            // set sort order
                            if (map.containsKey(SORT_DIR_PARAMETER)) {
                                String sortDir = (String) map
                                        .get(SORT_DIR_PARAMETER);
                                column
                                        .setSortOrder((sortDir
                                                .equals(SORT_DIR_PARAMETER_ASC) ? Ordering.ASCENDING
                                                : (sortDir
                                                        .equals(SORT_DIR_PARAMETER_DESC) ? Ordering.DESCENDING
                                                        : Ordering.UNSORTED)));
                            } else {
                                column.toggleSortOrder();
                            }

                            Collection<Object> sortPriority = table
                                    .getSortPriority();
                            // clear sort priority in case of single sort mode
                            if (isSingleSortMode) {
                                sortPriority.clear();
                            }
                            // add column to sort priority if is not added yet
                            if (!sortPriority.contains(id)) {
                                sortPriority.add(id);
                            }
                            if (isGroupingColumn) {
                                // set as grouping column to mark that grouping
                                // order has changed
                                table.setGroupByColumn(column);
                            }
                        } else if (isSingleSortMode) { // in case of single
                            // sort mode
                            if (!isGroupingColumn) { // grouping is not by
                                // this column
                                if (!sortByGroupingColumn) {// sort not by
                                    // grouping column
                                    // disable sort by this column
                                    column.setSortOrder(Ordering.UNSORTED);
                                }
                            }
                        }
                    }

                    UIInput filterValueInput = (UIInput) column
                            .getFacet(FILTER_INPUT_FACET_NAME);
                    if (null != filterValueInput) {
                        filterValueInput.decode(context);
                        String oldFilterValue = column.getFilterValue();
                        Object submittedValue = filterValueInput
                                .getSubmittedValue();
                        String newFilterValue = null;
                        if (null != submittedValue) {
                            newFilterValue = filterValueInput
                                    .getSubmittedValue().toString();
                            if ((newFilterValue != null)
                                    && (newFilterValue.length() == 0)) {
                                newFilterValue = null;
                            }
                            column.setFilterValue(newFilterValue);
                        }
                        boolean filterChanged = (newFilterValue == null ? (oldFilterValue != null)
                                : !newFilterValue.equals(oldFilterValue));
//                        if (filterChanged) {
//                        	//set focus on changed field
//                        	AjaxContext.getCurrentInstance().getResponseDataMap().put(AjaxActionComponent.FOCUS_DATA_ID, filterValueInput.getClientId(context));
//                        }
                        filtering = (filtering || filterChanged);
                    }
                }

                // AjaxContext.getCurrentInstance().addComponentToAjaxRender(component);
                if (sorting){
                	new ExtTableSortEvent(component).queue();
                }
                if (filtering){
                	new ExtTableFilterEvent(component).queue();
                }
                
            }

            // GROUP COLUMNS
            if (GROUP_FILTER_PARAMETER.equals(map.get(clientId))) {
                String groupColumnId = map.get(GROUP_FILTER_PARAMETER);
                // turn off grouping
                table.disableGrouping();
                if (groupColumnId != null) {
                    // turn off sorting by all columns
                    table.getSortPriority().clear();
                    for (Iterator<UIColumn> columns = table.getChildColumns(); columns
                            .hasNext();) {
                        UIColumn column = columns.next();
                        // child.setId(child.getId());
                        if (groupColumnId.equals(column.getClientId(context))) { // group
                            // by
                            // this
                            // column
                            // set sort order if is not set
                            if (column.getSortOrder().equals(Ordering.UNSORTED)) {
                                column.setSortOrder(Ordering.ASCENDING);
                            }
                            // set as grouping column
                            table.setGroupByColumn(column);
                        } else { // grouping is not by this column
                            // turn off sorting by this column
                            column.setSortOrder(Ordering.UNSORTED);
                        }
                    }// for columns
                }// if

                // AjaxContext.getCurrentInstance().addComponentToAjaxRender(component);
                // AjaxContext.getCurrentInstance().addRenderedArea(clientId +
                // ":tu");
                new ExtTableSortEvent(component).queue();
            }// group columns

            // CHANGE COLUMN ORDER - DRAG AND DROP
            String dragSourceId = (String) map
                    .get(org.richfaces.renderkit.DraggableRendererContributor.DRAG_SOURCE_ID);
            String dropTargetId = (String) map
                    .get(org.richfaces.renderkit.DropzoneRendererContributor.DROP_TARGET_ID);
            if ((dragSourceId != null) && (dropTargetId != null)) {
                Pattern sourcePattern = Pattern.compile(clientId + ":(\\w*):"
                        + TableDragDropRenderer.DRAG_SOURCE_SCRIPT_ID);
                Pattern targetPattern = Pattern.compile(clientId + ":(\\w*):"
                        + TableDragDropRenderer.DROP_TARGET_SCRIPT_ID + "("
                        + TableDragDropRenderer.DROP_TARGET_BEFORE + "|"
                        + TableDragDropRenderer.DROP_TARGET_AFTER + ")");
                Matcher sourceMatcher = sourcePattern.matcher(dragSourceId);
                Matcher targetMatcher = targetPattern.matcher(dropTargetId);
                if (sourceMatcher.find() && targetMatcher.find()) {
                    String sourceColumnId = sourceMatcher.group(1);
                    String targetColumnId = targetMatcher.group(1);
                    String kind = targetMatcher.group(2);

                    DragDropEvent dragDropEvent = new DragDropEvent(component);
                    dragDropEvent.setDragValue(sourceColumnId);
                    dragDropEvent.setDropValue(targetColumnId);
                    dragDropEvent.setDropBefore(kind
                            .equals(TableDragDropRenderer.DROP_TARGET_BEFORE));

                    dragDropEvent.queue();

                    // AjaxContext.getCurrentInstance().addComponentToAjaxRender(component);
                    // AjaxContext ajaxContext =
                    // AjaxContext.getCurrentInstance();
                    // ajaxContext.addComponentToAjaxRender(component);
                    // ajaxContext.addRenderedArea(clientId + ":tb");// body
                    // ajaxContext.addRenderedArea(clientId + ":tu");
                    // ajaxContext.addRenderedArea(clientId + ":tm");// menu
                    // AjaxContext.getCurrentInstance().addRenderedArea(clientId);
                }
            }// change column order

            // CHANGE COLUMN VISIBILITY
            String columnToChange = (String) map.get(clientId + ":"
                    + TableMenuRenderer.CHANGE_COL_VISIBILITY);
            if (columnToChange != null) {
                ChangeColumnVisibilityEvent event = new ChangeColumnVisibilityEvent(
                        component, columnToChange);

                event.queue();

                // AjaxContext.getCurrentInstance().addComponentToAjaxRender(component);
                // AjaxContext ajaxContext = AjaxContext.getCurrentInstance();
                // ajaxContext.addComponentToAjaxRender(component);
                // ajaxContext.addRenderedArea(clientId + ":tb");// body
                // ajaxContext.addRenderedArea(clientId + ":tu");
                // ajaxContext.addRenderedArea(clientId + ":tm");// menu
            }// change column visibility

            // COLUMN RESIZE
            if (COL_RESIZE_ACTION_NAME.equals(map.get(COL_RESIZE_ACTION_NAME))) {
                String colWidths = (String) map.get("columnWidths");
                ColumnResizeEvent event = new ColumnResizeEvent(component,
                        colWidths);
                event.queue();
            }

            // TOGGLE ROW GROUP
            if (GROUP_TOGGLE_ACTION_NAME.equals(map
                    .get(GROUP_TOGGLE_ACTION_NAME))) {
                String group = (String) map.get("groupIndex");
                if (group != null) {
                    try {
                        table.toggleGroup(Integer.valueOf(group));
                    } catch (NumberFormatException _) {
                    }
                }// if
                context.renderResponse();
            }

        }
    }

    public void encodeBegin(FacesContext context, UIComponent component)throws IOException {
    //FIXME need to check if EDT is enclosed in a form!
//        UIComponent c = component;
//        boolean inForm = false;
//        while((c = c.getParent()) != null) {
//            if(c instanceof UIForm) {
//                inForm = true;
//                break;
//            }
//        }
//        if(inForm && log.isWarnEnabled()) {
//            log.warn("Extended Data Table must be enclosed in a Form component");
//        }
    	super.encodeBegin(context, component);
    	//component.getAttributes().put(AjaxRendererUtils.AJAX_SINGLE_ATTR, Boolean.TRUE);
    }
    
    public void encodeEnd(FacesContext context, UIComponent component)
            throws IOException {
        super.encodeEnd(context, component);
        String clientId = component.getClientId(context);
        Set<String> ajaxRenderedAreas = AjaxContext.getCurrentInstance()
                .getAjaxRenderedAreas();
        // if (ajaxRenderedAreas.contains(clientId + ":tb")) {
        // ajaxRenderedAreas.remove(clientId);
        // }
        if (ajaxRenderedAreas.contains(clientId)) {
            // remove all child elements
            for (Iterator<String> iter = ajaxRenderedAreas.iterator(); iter
                    .hasNext();) {
                String area = iter.next();
                if (area.startsWith(clientId) && (!area.equals(clientId))) {
                    iter.remove();
                }
            }
        }
    }

    protected void addInplaceInput(FacesContext context, UIComponent column) throws IOException {
        UIInput filterValueInput = (UIInput) column
                .getFacet(FILTER_INPUT_FACET_NAME);
        if (null == filterValueInput) {
            filterValueInput = (UIInput) context.getApplication()
                    .createComponent(UIInput.COMPONENT_TYPE);
            filterValueInput.setId(column.getId() + SORT_FILTER_PARAMETER);
            filterValueInput.setImmediate(true);
            filterValueInput.getAttributes().put(HTML.STYLE_CLASS_ATTR, "rich-filter-input");
            column.getFacets().put(FILTER_INPUT_FACET_NAME, filterValueInput);
            filterValueInput.getAttributes().put(HTML.onclick_ATTRIBUTE,
                    "Event.stop(event);");
        }
        String filterEvent = (String) column.getAttributes().get("filterEvent");
        if (null == filterEvent || "".equals(filterEvent)) {
            filterEvent = "onchange";
        }

        String buffer = buildAjaxFunction(
        		context, 
        		column, 
        		false, 
        		buildSetFocusFunctionDef(filterValueInput.getClientId(context)));
        filterValueInput.getAttributes().put(filterEvent, buffer);
        filterValueInput.setValue(column.getAttributes().get("filterValue"));

        getUtils().encodeBeginFormIfNessesary(context, column);
        renderChild(context, filterValueInput);
        getUtils().encodeEndFormIfNessesary(context, column);
    }

    protected String buildAjaxFunction(FacesContext context,
            UIComponent column, boolean sortable,
            JSFunctionDefinition onAjaxCompleteFunction) {
        UIComponent table = column.getParent();
        String id = table.getClientId(context);
        JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table,
                context);
        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(
                context, table);
        @SuppressWarnings("unchecked")
        Map<String, Object> parameters = (Map<String, Object>) eventOptions
                .get("parameters");

        parameters.put(id, SORT_FILTER_PARAMETER);
        if (sortable) {
            parameters.put(SORT_FILTER_PARAMETER, column.getClientId(context));
        }
        if (onAjaxCompleteFunction != null)
            eventOptions.put(AjaxRendererUtils.ONCOMPLETE_ATTR_NAME,
                    onAjaxCompleteFunction);
        ajaxFunction.addParameter(eventOptions);
        StringBuffer buffer = new StringBuffer();
        ajaxFunction.appendScript(buffer);

        return buffer.toString();
    }

    protected class SimpleHeaderEncodeStrategy implements HeaderEncodeStrategy {

        public void encodeBegin(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {

        }

        public void encodeEnd(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {

        }

    }

    protected class RichHeaderEncodeStrategy implements HeaderEncodeStrategy {

        public void encodeBegin(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {
            UIColumn col = (UIColumn) column;
            String clientId = col.getClientId(context) + facetName;
            writer.writeAttribute("id", clientId, null);

            if (sortableColumn && col.isSelfSorted()) {
                writer.writeAttribute(HTML.onclick_ATTRIBUTE,
                        buildAjaxFunction(context, column, true, null)
                                .toString(), null);
                writer.writeAttribute(HTML.style_ATTRIBUTE, "cursor: pointer;",
                        null);
            }

            writer.startElement(HTML.DIV_ELEM, column);
            writer.writeAttribute(HTML.id_ATTRIBUTE, clientId + ":sortDiv",
                    null);
            AjaxContext.getCurrentInstance().addRenderedArea(
                    clientId + ":sortDiv");

            if (sortableColumn) {
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                        "extdt-sortable-header", null);
            }
        }

        public void encodeEnd(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {
            UIColumn col = (UIColumn) column;
            if (sortableColumn) {
                String imageUrl = null;
                if (Ordering.ASCENDING.equals(col.getSortOrder())) {
                    if (null != col.getSortIconAscending()) {
                        imageUrl = col.getSortIconAscending();
                    } else {
                        imageUrl = getResource(TriangleIconUp.class.getName())
                                .getUri(context, null);
                    }
                } else if (Ordering.DESCENDING.equals(col.getSortOrder())) {
                    if (null != col.getSortIconDescending()) {
                        imageUrl = col.getSortIconDescending();
                    } else {
                        imageUrl = getResource(TriangleIconDown.class.getName())
                                .getUri(context, null);
                    }
                } else if (col.isSelfSorted()) {
                    if (null != col.getSortIcon()) {
                        imageUrl = col.getSortIcon();
                    } else {
                        imageUrl = getResource(
                                DataTableIconSortNone.class.getName()).getUri(
                                context, null);
                    }
                }

                if (imageUrl != null) {
                    writer.startElement(HTML.IMG_ELEMENT, column);
                    writer.writeAttribute(HTML.src_ATTRIBUTE, imageUrl, null);
                    writer.writeAttribute(HTML.width_ATTRIBUTE, "15", null);
                    writer.writeAttribute(HTML.height_ATTRIBUTE, "15", null);
                    writer.writeAttribute(HTML.class_ATTRIBUTE,
                            "extdt-header-sort-img", null);
                    writer.endElement(HTML.IMG_ELEMENT);
                }
                writer.endElement(HTML.SPAN_ELEM);
            }

            writer.endElement(HTML.DIV_ELEM);

            if (col.getFilterMethod() == null
                    && col.getValueExpression("filterExpression") == null
                    && col.getValueExpression("filterBy") != null) {

                writer.startElement(HTML.DIV_ELEM, column);
                addInplaceInput(context, column);
                writer.endElement(HTML.DIV_ELEM);
            }
        }
    }

    protected class G3HeaderEncodeStrategy implements HeaderEncodeStrategy {

        public void encodeBegin(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {
            if (column instanceof UIColumn) {
                UIColumn dataColumn = (UIColumn) column;
                String clientId = dataColumn.getClientId(context);// +
                // facetName;
                writer.writeAttribute("id", clientId, null);
                column.getAttributes().put("columnClientId", clientId);
                boolean sortable = sortableColumn && dataColumn.isSelfSorted();
                if (sortable) {
                    /*
                     * writer.writeAttribute(HTML.onclick_ATTRIBUTE,
                     * buildAjaxFunction( context, column, true,
                     * getOnAjaxCompleteFunction(context, (UIDataTable)
                     * column.getParent())) .toString(), null);
                     */
                    writer.writeAttribute(HTML.style_ATTRIBUTE,
                            "cursor: pointer;", null);
                }
                writer.writeAttribute("sortable", String.valueOf(sortable),
                        null);
                // column.getAttributes().put("sortable",Boolean.valueOf(sortable));
                // drag source area
                writer.startElement(HTML.DIV_ELEM, dataColumn);
                writer.writeAttribute(HTML.id_ATTRIBUTE, dataColumn.getParent()
                        .getClientId(context)
                        + "_hdrag_" + dataColumn.getId(), null);

                writer.startElement(HTML.DIV_ELEM, dataColumn);
                writer.writeAttribute(HTML.id_ATTRIBUTE, clientId + ":sortDiv",
                        null);
                AjaxContext.getCurrentInstance().addRenderedArea(
                        clientId + ":sortDiv");

                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                            "extdt-sortable-header", null);
            }
        }

        public void encodeEnd(FacesContext context, ResponseWriter writer,
                UIComponent column, String facetName, boolean sortableColumn)
                throws IOException {
            if (column instanceof UIColumn) {
                UIColumn dataColumn = (UIColumn) column;
                String clientId = dataColumn.getClientId(context) + facetName;
                String tableId = dataColumn.getParent().getClientId(context);

                String imageUrl = null;
                if (sortableColumn) {
                    if (Ordering.ASCENDING.equals(dataColumn.getSortOrder())) {
                        if (null != dataColumn.getSortIconAscending()) {
                            imageUrl = dataColumn.getSortIconAscending();
                        } else {
                            imageUrl = getResource(
                                    TriangleIconUp.class.getName()).getUri(
                                    context, null);
                        }
                    } else if (Ordering.DESCENDING.equals(dataColumn
                            .getSortOrder())) {
                        if (null != dataColumn.getSortIconDescending()) {
                            imageUrl = dataColumn.getSortIconDescending();
                        } else {
                            imageUrl = getResource(
                                    TriangleIconDown.class.getName()).getUri(
                                    context, null);
                        }
                    } else if (dataColumn.isSelfSorted()) {
                        if (null != dataColumn.getSortIcon()) {
                            imageUrl = dataColumn.getSortIcon();
                        } else {
                            imageUrl = getResource(
                                    DataTableIconSortNone.class.getName())
                                    .getUri(context, null);
                        }
                    }
                }
                else{
                	//set empty image
                	imageUrl = getResource("/org/richfaces/renderkit/html/images/s.gif").getUri(context, null);
                }
                if (imageUrl != null) {
                    writer.startElement(HTML.IMG_ELEMENT, column);
                    writer.writeAttribute(HTML.src_ATTRIBUTE, imageUrl,
                            null);
                    writer.writeAttribute(HTML.class_ATTRIBUTE,
                            "extdt-header-sort-img", null);
                    writer.endElement(HTML.IMG_ELEMENT);
                }
                writer.endElement(HTML.SPAN_ELEM);

                writer.endElement(HTML.DIV_ELEM);

                // drag source area
                writer.endElement(HTML.DIV_ELEM);
                String dragSourceId = tableId + "_hdrag_" + dataColumn.getId();
                String indicatorId = tableId + ":dataTable_indicator";
                renderDragSupport(context, dataColumn, dragSourceId,
                        indicatorId, (String)dataColumn.getAttributes().get("label"));

                // separator area
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.id_ATTRIBUTE, clientId + ":sepSpan",
                        null);
                writer
                        .writeAttribute(HTML.class_ATTRIBUTE, "extdt-hsep",
                                null);
                writer.endElement(HTML.SPAN_ELEM);

                // drop target area LEFT
                String spanId = tableId + "_hdrop_" + dataColumn.getId()
                        + "left";
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.id_ATTRIBUTE, spanId, null);
                writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-hdrop",
                        null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                        "extdt-hdrop-top extdt-hdrop-top-left", null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.endElement(HTML.SPAN_ELEM);
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                        "extdt-hdrop-bottom extdt-hdrop-bottom-left", null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.endElement(HTML.SPAN_ELEM);
                writer.endElement(HTML.SPAN_ELEM);
                renderDropSupport(context, dataColumn, spanId, true);

                // drop target area RIGHT
                spanId = tableId + "_hdrop_" + dataColumn.getId() + "right";
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.id_ATTRIBUTE, spanId, null);
                writer.writeAttribute(HTML.class_ATTRIBUTE, "extdt-hdrop",
                        null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                        "extdt-hdrop-top extdt-hdrop-top-right", null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.endElement(HTML.SPAN_ELEM);
                writer.startElement(HTML.SPAN_ELEM, column);
                writer.writeAttribute(HTML.class_ATTRIBUTE,
                        "extdt-hdrop-bottom extdt-hdrop-bottom-right", null);
                writer.writeAttribute(HTML.style_ATTRIBUTE,
                        "visibility: hidden;", null);
                writer.endElement(HTML.SPAN_ELEM);
                writer.endElement(HTML.SPAN_ELEM);
                renderDropSupport(context, dataColumn, spanId, false);

                // menu
                if ("header".equals(facetName)) {
                    writer.startElement(HTML.DIV_ELEM, column);
                    String menuDivId = clientId + ":menuDiv";
                    writer.writeAttribute(HTML.id_ATTRIBUTE, menuDivId, null);
                    writer.writeAttribute(HTML.class_ATTRIBUTE,
                            "extdt-menu-div-out", null);
                    writer.endElement(HTML.DIV_ELEM);
                }

                // if (dataColumn.getFilterMethod() == null
                // && dataColumn.getValueExpression("filterExpression") == null
                // && dataColumn.getValueExpression("filterBy") != null) {
                //
                // writer.startElement(HTML.DIV_ELEM, column);
                // addInplaceInput(context, column, buildAjaxFunction(context,
                // column, false, getOnAjaxCompleteFunction(context,
                // (UIDataTable) column.getParent())));
                // writer.endElement(HTML.DIV_ELEM);
                // }
            }
        }
    }

    public String encodeDragDropChildScripts(FacesContext context,
            UIDataTable component) throws IOException {
        TableDragDropRenderer.getInstance(context).encodeChildScripts(context,
                component);
        return "";
    }

    public void encodeNamespace(FacesContext context, UIComponent component)
            throws IOException {
        NSUtils.writeNameSpace(context, component);
    }

    public void renderDragSupport(FacesContext context, UIColumn column,
            String dragSourceId, String indicatorId, String dragLabel)
            throws IOException {
        TableDragDropRenderer.getInstance(context).renderDragSupport(column,
                dragSourceId, indicatorId, dragLabel);
    }// renderDragSupport

    public void renderDropSupport(FacesContext context, UIColumn column,
            String dropTargetId, boolean before) throws IOException {
        TableDragDropRenderer renderer = TableDragDropRenderer
                .getInstance(context);
        renderer.setOnAjaxCompleteFunctionDef(getOnAjaxCompleteFunction(
                context, (UIDataTable) column.getParent()));
        renderer.setPreSendAjaxRequestFunction(getPreSendAjaxRequestFunction(
                context, (UIDataTable) column.getParent()));
        renderer.renderDropSupport(column, dropTargetId, before);
    }// renderDropSupport

    public static String getJavaScriptVarName(FacesContext context,
            UIDataTable grid) {
        String id = grid.getBaseClientId(context);
        String name = "ExtendedDataTable.DataTable_"
                + id.replaceAll("[^A-Za-z0-9_]", "_");
        // String name = "Richfaces_ScrollableGrid";
        return "window." + name;
    }

    protected String getScriptContributions(FacesContext context,
            UIDataTable grid) {
        return composite.getScriptContributions(getJavaScriptVarName(context,
                grid), context, grid);
    }

    protected JSFunctionDefinition getOnAjaxCompleteFunction(
            FacesContext context, UIDataTable table) {

        return null;
        // JSFunctionDefinition function = new JSFunctionDefinition("request",
        // "event", "data");
        // String varName = getJavaScriptVarName(context, table);
        // function.addToBody(varName + ".update();");
        // return function;
    }

    public void encodeTableMenu(FacesContext context, UIExtendedDataTable table)
            throws IOException {
        AjaxContext ajaxContext = AjaxContext.getCurrentInstance();
        Object key = table.getRowKey();
        table.setRowKey(null);
        TableMenuRenderer menuRenderer = new RichTableMenuRenderer();
        menuRenderer.setSortFunction(getSortFunction(context, table));
        menuRenderer.setGroupFunction(getGroupFunction(context, table));
        menuRenderer
                .setChangeColumnVisibilityFunction(getChangeColumnVisibilityFunction(
                        context, table));
        menuRenderer.setPrepareFunction(getPreSendAjaxRequestFunction(context,
                table));
        for (Iterator<UIColumn> colums = table.getSortedColumns(); colums
                .hasNext();) {
            UIColumn col = colums.next();
            if (col.isRendered()) {
				String menuId = menuRenderer.renderMenu(context, table, col);
				ajaxContext.addRenderedArea(menuId);
			}
        }// for
        table.setRowKey(key);
    }

    public void contributorsEncodeHere(FacesContext context, UIDataTable table)
            throws IOException {
        RendererContributor[] contribs = composite.getContributors();

        if (contribs != null) {
            for (int i = 0; i < contribs.length; i++) {
                RendererContributor rendererContributor = contribs[i];

                if (rendererContributor instanceof HTMLEncodingContributor) {
                    ((HTMLEncodingContributor) rendererContributor).encode(
                            context, table);
                }
            }
        }
    }
}