/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.server.headlessclient.dataui;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.Model;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.persistence.AbstractBase;
import com.servoy.j2db.persistence.Bean;
import com.servoy.j2db.persistence.Field;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.GraphicalComponent;
import com.servoy.j2db.persistence.IAnchorConstants;
import com.servoy.j2db.persistence.IPersist;
import com.servoy.j2db.persistence.ISupportAnchors;
import com.servoy.j2db.persistence.ISupportName;
import com.servoy.j2db.persistence.Portal;
import com.servoy.j2db.server.headlessclient.yui.YUILoader;
import com.servoy.j2db.ui.IProviderStylePropertyChanges;
import com.servoy.j2db.ui.IStylePropertyChanges;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.Utils;
/**
* A component that renders the header of a {@link WebCellBasedView}
*
* @author jcompagner,jblok
*/
public class SortableCellViewHeaders extends WebMarkupContainer implements IProviderStylePropertyChanges
{
private static final long serialVersionUID = 1L;
/** Each SortableTableHeader (without 's) must be attached to a group. */
final private SortableCellViewHeaderGroup group;
private final AbstractBase cellview;
private final WebCellBasedView view;
private final IApplication application;
private final IHeaders headerManager;
private final Form form;
/**
* Construct.
*
* @param view
* @param id The component's id; must not be null
* @param listView the underlying ListView
* @param cellview
* @param application
* @param headerManager the object who's headers will be populated with (IPersist, Component) pairs according to the header components that represent each
* column - column given by the IPersist.
*/
public SortableCellViewHeaders(Form form, WebCellBasedView view, String id, final ServoyListView listView, AbstractBase cellview, IApplication application,
Map<String, Boolean> initialSortMap, IHeaders headerManager)
{
super(id);//id is normally 'header'
this.setOutputMarkupId(true);
this.form = form;
this.view = view;
this.cellview = cellview;
this.application = application;
this.headerManager = headerManager;
group = new SortableCellViewHeaderGroup(form, listView, cellview);
if (initialSortMap != null) group.recordSort(initialSortMap);
if (view.isScrollMode())
{
add(new StyleAppendingModifier(new Model<String>()
{
@Override
public String getObject()
{
return "position: absolute; overflow: hidden; left: 0px; top: 0px; border-spacing: 0px;"; //$NON-NLS-1$
}
}));
}
}
@Override
public void renderHead(HtmlHeaderContainer headerContainer)
{
super.renderHead(headerContainer);
if (isReorderableOrResizable()) YUILoader.renderDragNDrop(headerContainer.getHeaderResponse());
}
private boolean isReorderableOrResizable()
{
if (Utils.getAsBoolean(application.getRuntimeProperties().get("useAJAX"))) //$NON-NLS-1$
{
boolean isReorderable = false;
boolean isResizable = false;
Iterator<IPersist> iter = cellview.getAllObjects();
while (iter.hasNext())
{
IPersist element = iter.next();
if (element instanceof ISupportAnchors)
{
int anchors = ((ISupportAnchors)element).getAnchors();
isResizable = ((anchors & IAnchorConstants.EAST) == IAnchorConstants.EAST) && ((anchors & IAnchorConstants.WEST) == IAnchorConstants.WEST);
isResizable = isResizable && (!(cellview instanceof Portal) || ((Portal)cellview).getResizable());
if (isResizable) return true;
isReorderable = !(((anchors & IAnchorConstants.NORTH) == IAnchorConstants.NORTH) && ((anchors & IAnchorConstants.SOUTH) == IAnchorConstants.SOUTH));
isReorderable = isReorderable && (!(cellview instanceof Portal) || ((Portal)cellview).getReorderable());
if (isReorderable) return true;
}
}
}
return false;
}
private boolean resolve(MarkupStream markupStream, ComponentTag tag, String id)
{
if (tag.getName().equalsIgnoreCase("th")) //$NON-NLS-1$
{
// Get component name
final String th_component_id = id;
if ((th_component_id != null) && (get(th_component_id) == null))
{
final boolean useAJAX = Utils.getAsBoolean(application.getRuntimeProperties().get("useAJAX")); //$NON-NLS-1$
SortableCellViewHeader headerComponent = new SortableCellViewHeader(form, view, th_component_id, group, cellview, useAJAX, application);
headerComponent.setOutputMarkupPlaceholderTag(true); // prepare for invisibility
registerHeaderComponent(headerComponent);
autoAdd(headerComponent, markupStream);
headerComponent.resetAutoAdd();
return true;
}
}
return false;
}
private void registerHeaderComponent(SortableCellViewHeader headerComponent)
{
String id = headerComponent.getId();
IPersist matchingElement = null;
try
{
Iterator<IPersist> allElements = cellview.getAllObjects();
while (allElements.hasNext())
{
IPersist someElement = allElements.next();
if ((someElement instanceof Field || someElement instanceof GraphicalComponent || someElement instanceof Bean) &&
id.equals(ComponentFactory.getWebID(form, someElement)) && (someElement instanceof ISupportName))
{
// // column headers cannot be changed from JS if the according element is not named
// // so no need to link them
// String name = ((ISupportName)someElement).getName();
// if (name != null && name.trim().length() != 0)
// {
matchingElement = someElement;
break;
// }
}
}
}
catch (Exception e)
{
Debug.log("Cannot link a header component to it's element", e); //$NON-NLS-1$
}
if (matchingElement != null)
{
headerManager.registerHeader(matchingElement, headerComponent);
}
}
/**
* Scan the related markup and attach a SortableListViewHeader to each <th> tag found.
*/
@Override
protected void onRender(final MarkupStream markupStream)
{
// Must be <thead> tag
ComponentTag tag = markupStream.getTag();
checkComponentTag(tag, "thead"); //$NON-NLS-1$
// Continue with default behaviour
super.onRender(markupStream);
getStylePropertyChanges().setRendered();
}
@Override
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
renderSortableCellViewHeaderTagBody(markupStream, openTag);
}
/**
* Renders markup for the body of a ComponentTag from the current position in the given markup stream. If the open tag passed in does not require a close
* tag, nothing happens. Markup is rendered until the closing tag for openTag is reached.
*
* @param markupStream The markup stream
* @param openTag The open tag
*/
private int renderColumnIdx;
private int headerMarkupStartIdx;
private ArrayList<Component> orderedHeaders;
private ArrayList<String> orderedHeaderIds;
private void renderSortableCellViewHeaderTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
renderColumnIdx = 0;
headerMarkupStartIdx = markupStream.getCurrentIndex();
orderedHeaders = view.getOrderedHeaders();
orderedHeaderIds = view.getOrderedHeaderIds();
if ((markupStream != null) && (markupStream.getCurrentIndex() > 0))
{
// If the original tag has been changed from open-close to open-body-close,
// than historically renderComponentTagBody gets called, but actually
// it shouldn't do anything since there is no body for that tag.
ComponentTag origOpenTag = (ComponentTag)markupStream.get(markupStream.getCurrentIndex() - 1);
if (origOpenTag.isOpenClose())
{
return;
}
}
// If the open tag requires a close tag
boolean render = openTag.requiresCloseTag();
if (render == false)
{
// Tags like <p> do not require a close tag, but they may have.
render = !openTag.hasNoCloseTag();
}
if (render == true)
{
// Loop through the markup in this container
while (markupStream.hasMore() && !markupStream.get().closes(openTag))
{
// Render markup element. Doing so must advance the markup
// stream
final int index = markupStream.getCurrentIndex();
_renderNext(markupStream);
if (index == markupStream.getCurrentIndex())
{
markupStream.throwMarkupException("Markup element at index " + index + " failed to advance the markup stream"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
/**
* Renders the next element of markup in the given markup stream.
*
* @param markupStream The markup stream
*/
private final void _renderNext(final MarkupStream markupStream)
{
// Get the current markup element
final MarkupElement element = markupStream.get();
// If it a tag like <wicket..> or <span wicket:id="..." >
if ((element instanceof ComponentTag) && !markupStream.atCloseTag())
{
// Get element as tag
final ComponentTag tag = (ComponentTag)element;
// Get component id
final String id = tag.getId();
// Get the component for the id from the given container
final Component component = get(id);
// Failed to find it?
if (component != null && orderedHeaders.get(renderColumnIdx) != null)
{
if (component instanceof SortableCellViewHeader)
{
int currentIdx = markupStream.getCurrentIndex();
renderHeader(renderColumnIdx, markupStream);
renderColumnIdx++;
markupStream.setCurrentIndex(currentIdx);
markupStream.skipComponent();
}
}
else
{
// 2rd try: Components like Border and Panel might implement
// the ComponentResolver interface as well.
MarkupContainer container = this;
while (container != null)
{
if (container instanceof SortableCellViewHeaders)
{
// we should created the corect header, use the id from the orderedHeaders
String headerId = orderedHeaderIds.get(renderColumnIdx);
renderColumnIdx++;
if (resolve(markupStream, tag, headerId))
{
return;
}
}
if (container instanceof IComponentResolver)
{
if (((IComponentResolver)container).resolve(this, markupStream, tag))
{
return;
}
}
container = container.findParent(MarkupContainer.class);
}
// 3rd try: Try application's component resolvers
final List<IComponentResolver> componentResolvers = getApplication().getPageSettings().getComponentResolvers();
final Iterator<IComponentResolver> iterator = componentResolvers.iterator();
while (iterator.hasNext())
{
final IComponentResolver resolver = iterator.next();
if (resolver.resolve(this, markupStream, tag))
{
return;
}
}
if (tag instanceof WicketTag)
{
if (((WicketTag)tag).isChildTag())
{
markupStream.throwMarkupException("Found " + tag.toString() + " but no <wicket:extend>"); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
markupStream.throwMarkupException("Failed to handle: " + tag.toString()); //$NON-NLS-1$
}
}
// No one was able to handle the component id
markupStream.throwMarkupException("Unable to find component with id '" + id + "' in " + this + ". This means that you declared wicket:id=" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
id + " in your markup, but that you either did not add the " + "component to your page at all, or that the hierarchy does not match."); //$NON-NLS-1$ //$NON-NLS-2$
}
}
else
{
getResponse().write(element.toCharSequence());
markupStream.next();
}
}
private void renderHeader(int headerIdx, final MarkupStream markupStream)
{
Component header = orderedHeaders.get(headerIdx);
markupStream.setCurrentIndex(headerMarkupStartIdx);
boolean found = false;
MarkupElement element;
while (!found)
{
element = markupStream.next();
if ((element instanceof ComponentTag) && !markupStream.atCloseTag())
{
// Get element as tag
final ComponentTag tag = (ComponentTag)element;
// Get component id
final String id = tag.getId();
// Get the component for the id from the given container
final Component component = get(id);
// Failed to find it?
if (component != null)
{
if (component.equals(header))
{
component.render(markupStream);
found = true;
}
}
}
}
}
protected ChangesRecorder jsChangeRecorder = new ChangesRecorder(null, null);
public IStylePropertyChanges getStylePropertyChanges()
{
return jsChangeRecorder;
}
public void recordSort(Map<String, Boolean> sortMap)
{
group.recordSort(sortMap);
}
}