blob: c88cce130b937a1b1c9b14e52d8de45f57b4fa38 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidadinternal.renderkit.core.xhtml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.model.SelectItem;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.component.UIXPanel;
import org.apache.myfaces.trinidad.component.UIXSelectItem;
import org.apache.myfaces.trinidad.component.UIXSelectRange;
import org.apache.myfaces.trinidad.component.core.data.CoreSelectRangeChoiceBar;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidad.context.FormData;
import org.apache.myfaces.trinidad.context.PartialPageContext;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.event.RangeChangeEvent;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.render.XhtmlConstants;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidad.util.IntegerUtils;
import org.apache.myfaces.trinidadinternal.util.Range;
public class SelectRangeChoiceBarRenderer extends XhtmlRenderer
{
public SelectRangeChoiceBarRenderer()
{
this(CoreSelectRangeChoiceBar.TYPE);
}
public SelectRangeChoiceBarRenderer(FacesBean.Type type)
{
super(type);
}
@Override
protected void findTypeConstants(FacesBean.Type type)
{
super.findTypeConstants(type);
_rowsKey = type.findKey("rows");
_firstKey = type.findKey("first");
_immediateKey = type.findKey("immediate");
_showAllKey = type.findKey("showAll");
_varKey = type.findKey("var");
}
@SuppressWarnings("unchecked")
@Override
public void decode(FacesContext context, UIComponent component)
{
Map<String, String> parameters =
context.getExternalContext().getRequestParameterMap();
Object event = parameters.get(TrinidadRenderingConstants.EVENT_PARAM);
// get the goto event parameter values and queue a RangeChangeEvent.
if (TrinidadRenderingConstants.GOTO_EVENT.equals(event))
{
Object source = parameters.get(TrinidadRenderingConstants.SOURCE_PARAM);
String id = component.getClientId(context);
if (id.equals(source))
{
UIXSelectRange choiceBar = (UIXSelectRange)component;
Object valueParam = parameters.get(TrinidadRenderingConstants.VALUE_PARAM);
RangeChangeEvent rce = _createRangeChangeEvent(choiceBar, valueParam);
rce.queue();
if (choiceBar.isImmediate())
context.renderResponse();
RequestContext.getCurrentInstance().addPartialTarget(component);
}
}
}
private RangeChangeEvent _createRangeChangeEvent(
UIXSelectRange choiceBar,
Object valueParam)
{
// get the variables needed to calculate oldStart, oldEnd,
// newStart, newEnd.
int rowCount = choiceBar.getRowCount();
int rows = choiceBar.getRows();
FacesBean bean = getFacesBean(choiceBar);
boolean isShowAll = getShowAll(bean);
// calculate oldStart and oldEnd
int increment = (isShowAll && rowCount > -1) ? rowCount : rows;
int oldStart = choiceBar.getFirst();
int oldEnd = oldStart + increment;
// calculate newStart and newEnd
// initialize showAll to its default state. We will change
// this later if the event's value is "all".
if (isShowAll)
bean.setProperty(_showAllKey, Boolean.FALSE);
int newStart = -1;
int newEnd = -1;
if (valueParam != null)
{
String newStartString = valueParam.toString();
// We get "all" if the user selected the "Show All" option.
// If so, set showAll to true and set newStart and newEnd to
// be the entire range.
if (newStartString.equals(TrinidadRenderingConstants.VALUE_SHOW_ALL))
{
bean.setProperty(_showAllKey, Boolean.TRUE);
newStart = 0;
newEnd = rowCount;
}
else
{
try
{
newStart = Integer.parseInt(newStartString) - 1;
newEnd = newStart + rows;
}
catch (NumberFormatException nfe)
{
// Shouldn't happen with a legit request
_LOG.severe(nfe);
}
}
}
return new RangeChangeEvent(choiceBar, oldStart, oldEnd,
newStart, newEnd);
}
@Override
public boolean getRendersChildren()
{
return true;
}
/**
* Always render an ID, needed for proper PPR.
*/
@Override
protected boolean shouldRenderId(
FacesContext context,
UIComponent component)
{
return true;
}
protected int getRows(UIComponent component, FacesBean bean)
{
Object o = bean.getProperty(_rowsKey);
if (o == null)
o = _rowsKey.getDefault();
return toInt(o);
}
protected int getFirst(UIComponent component, FacesBean bean)
{
Object o = bean.getProperty(_firstKey);
if (o == null)
o = _firstKey.getDefault();
return toInt(o);
}
protected boolean getShowAll(FacesBean bean)
{
Object o = bean.getProperty(_showAllKey);
if (o == null)
o = _showAllKey.getDefault();
return Boolean.TRUE.equals(o);
}
protected boolean getImmediate(FacesBean bean)
{
Object o = bean.getProperty(_immediateKey);
if (o == null)
o = _immediateKey.getDefault();
return Boolean.TRUE.equals(o);
}
protected String getVar(FacesBean bean)
{
return toString(bean.getProperty(_varKey));
}
protected UIComponent getRangeLabel(UIComponent component)
{
return getFacet(component, CoreSelectRangeChoiceBar.RANGE_LABEL_FACET);
}
//
// HOOKS FOR SUBCLASSES
// These methods exist entirely for subclasses to override behavior.
//
protected int getRowCount(UIComponent component)
{
return ((UIXSelectRange) component).getRowCount();
}
protected int getRowIndex(UIComponent component)
{
return ((UIXSelectRange) component).getRowIndex();
}
protected void setRowIndex(UIComponent component, int index)
{
((UIXSelectRange) component).setRowIndex(index);
}
protected boolean isRowAvailable(UIComponent component)
{
return ((UIXSelectRange) component).isRowAvailable();
}
protected boolean isRowAvailable(UIComponent component, int rowIndex)
{
return ((UIXSelectRange) component).isRowAvailable(rowIndex);
}
protected Object getRowData(UIComponent component)
{
return ((UIXSelectRange) component).getRowData();
}
protected String getSource()
{
return null;
}
protected boolean showAllSupported()
{
return true;
}
//
// END OF HOOKS
//
/**
*/
@Override
protected void encodeAll(
FacesContext context,
RenderingContext arc,
UIComponent component,
FacesBean bean) throws IOException
{
int rowIndex = getRowIndex(component);
try
{
int blockSize = getRows(component, bean);
if (blockSize < 0)
blockSize = toInt(_rowsKey.getDefault());
// =-=AEW The old rendering code was written with "value" as one-indexed;
// "first" is zero-indexed. Rewrite the rendering code to deal.
long currentValue = getFirst(component, bean) + 1;
if (currentValue < 1)
currentValue = 1;
long minValue = 1;
// @todo: =-=jmw ... get maxValue from the model. If no model, then use the
// maximum attribute. Not sure we want to implement this feature.
long maxValue = getRowCount(component);
if (maxValue <= 0)
maxValue = TrinidadRenderingConstants.MAX_VALUE_UNKNOWN;
// get name
String id = getClientId(context, component);
// For the source, just pass the ID as long as this is being
// used on its own - but give a hook for subclasses to their thing.
String source = getSource();
if (source == null)
source = id;
if (arc.getFormData() == null)
return;
String formName = arc.getFormData().getName();
if (formName == null)
return;
int nextRecords = 0;
int prevRecords = 0;
long backValue = 0;
long nextValue = 0;
if (blockSize > 0)
{
// determine how many records user can go forward
long lNextRecords = blockSize;
if (maxValue != TrinidadRenderingConstants.MAX_VALUE_UNKNOWN)
{
// if we know the total records, align the current value to the
// start of its block. This makes the choice-rendering style
// not show extra back navigation records on the min block,
// which it would if not aligned.
// =-=AEW Revisit bug 3052637 for JSF
/** no don't do this. see bug: 3052637
currentValue -= minValue;
currentValue /= blockSize;
currentValue *= blockSize;
currentValue += minValue;
*/
lNextRecords = maxValue - (currentValue + blockSize - 1);
}
// determine how many records user can go back
long lPrevRecords = currentValue - minValue;
// trim
nextRecords = (lNextRecords > blockSize)
? blockSize
: (int) lNextRecords;
prevRecords = (lPrevRecords > blockSize)
? blockSize
: (int) lPrevRecords;
backValue = currentValue - prevRecords;
nextValue = currentValue + blockSize;
}
boolean validate = !getImmediate(bean);
boolean showDisabledNavigation = disabledNavigationShown();
boolean hasBackRecords = (prevRecords > 0);
boolean hasNextRecords = (nextRecords > 0);
if (hasNextRecords && (maxValue == TrinidadRenderingConstants.MAX_VALUE_UNKNOWN))
{
// make sure the next range exists in the data model.
hasNextRecords = isRowAvailable(component, (int)nextValue-1);
}
if (!hasNextRecords)
{
nextRecords = 0;
}
boolean showBackButton = hasBackRecords || showDisabledNavigation;
boolean showNextButton = hasNextRecords || showDisabledNavigation;
if (!supportsNavigation(arc))
{
showBackButton = false;
showNextButton = false;
}
boolean showAllActive = getShowAll(bean);
if (showAllActive)
{
prevRecords = 0;
nextRecords = 0;
}
String prevOnClick = null;
String nextOnClick = null;
if (hasBackRecords || hasNextRecords)
{
addHiddenFields(arc);
// Render script submission code.
ProcessUtils.renderNavSubmitScript(context, arc);
ProcessUtils.renderNavChoiceSubmitScript(context, arc);
}
// use form submit
if (supportsScripting(arc))
{
if (hasBackRecords && !showAllActive)
{
prevOnClick = ProcessUtils.getSubmitScriptCall(formName,
source,
backValue,
validate);
}
if (hasNextRecords && !showAllActive)
{
nextOnClick = ProcessUtils.getSubmitScriptCall(formName,
source,
nextValue,
validate);
}
}
// ready to render
ResponseWriter writer = context.getResponseWriter();
boolean renderAsTable = __renderAsTable(component);
// The following strange code is part of the work around for
// bug 2275703. IE has problems re-laying out a TableBean
// after a partial page replacement. In particular, pieces
// of the table's top navigation bar, such as the previous link
// or icon, or sometimes the entire navigation bar, may shift to
// the left. In some cases, pieces of the navigation bar (the previous
// icon) may disappear during re-layout! There doesn't seem to be
// any clean way to avoid this apparent IE bug. However, we explicitly
// perform a partial replacement of the navigation bar's previous icon
// *after* the entire table has been replaced, everything seems to lay
// out just fine.
//
// So, if we are rendering a TableBean's navigation bar with
// PPR enabled on IE, then we generate an ID for the nav bar's
// previous icon, and we add this to the list of rendered partial
// targets during the partial page render. This forces the icon
// to be replaced as part of the partial page update, and fixes
// our layout problems.
String iconID = null;
if (PartialPageUtils.isPPRActive(context) &&
isIE(arc))
{
iconID = id + "-i";
}
// we only want to render the baseID, if needed, once. Then we
// render the subIDs. So we need to keep track of this.
boolean renderedId = false;
// If the request is from a desktop browser we don't need to wrap up
// with a div tag
boolean isDesktop = false;
// if we need to render standalone, create a table and table row...
if (renderAsTable)
{
isDesktop = (arc.getAgent().getType().equals(Agent.TYPE_DESKTOP));
// Few mobile browsers doesn't support PPR for Table element
// so lets wrap it up with a div tag
if(!isDesktop )
{
writer.startElement("div", component);
writer.writeAttribute("id", id, "id");
}
writer.startElement("table", component);
OutputUtils.renderLayoutTableAttributes(context, arc, "0", null);
// =-=AEW Where do these attrs get written out when
// we're inside a PanelPageButton?
renderAllAttributes(context, arc, bean);
// We should always render the ID, but we particularly need
// to make sure that the ID is rendered if the NavBar is being
// used to navigate a TableBean, since we explicitly target
// TableBean NavBars when using PPR to re-render TableBeans...
if(isDesktop )
{
writer.writeAttribute("id", id, "id");
}
renderedId = true;
writer.startElement("tr", null);
}
boolean narrowScreen = supportsNarrowScreen(arc);
// skip rendering back button for narrow-screen PDAs to reduce the
// overall width of selectRangeChoiceBar.
if (showBackButton && !narrowScreen)
{
Icon prevIcon = getIcon(arc, false, (prevOnClick != null));
if (!prevIcon.isNull())
{
// We assign an id to the left arrow so that we can target it as
// a partial target to work around bug 2275703 - see note above.
if (iconID != null)
{
writer.startElement("td", component);
writer.writeAttribute("id", iconID, null);
// If the navigation bar that we are currently rendering
// is included in a partial page response, add the icon
// id to the list of partial targets.
// =-=AEW Not sure this is still necessary
PartialPageContext pprContext = arc.getPartialPageContext();
if ((pprContext != null) &&
pprContext.isInsidePartialTarget())
{
pprContext.addRenderedPartialTarget(iconID);
}
}
else
{
// not in PPR mode, so just render the td (and id if not in a table)
_renderStartTableCell(writer, id, renderedId);
renderedId = true;
}
writer.writeAttribute("valign", "middle", null);
_renderArrow(context, arc, prevIcon, false, prevOnClick);
writer.endElement("td");
_renderSpacerCell(context, arc);
}
_renderStartTableCell(writer, id, renderedId);
renderedId = true;
writer.writeAttribute("valign", "middle", null);
writer.writeAttribute("nowrap", Boolean.TRUE, null);
_renderLink(context,
arc,
false,
prevOnClick,
prevRecords,
id,
source,
backValue);
writer.endElement("td");
_renderSpacerCell(context, arc);
}
_renderStartTableCell(writer, id, renderedId);
renderedId = true;
writer.writeAttribute("valign", "middle", null);
writer.writeAttribute("nowrap", Boolean.TRUE, null);
_renderChoice(context,
arc,
component,
id,
source,
formName,
minValue,
currentValue,
blockSize,
maxValue,
validate);
writer.endElement("td");
// skip rendering back button for narrow-screen PDAs to reduce the
// overall width of selectRangeChoiceBar.
if (showNextButton && !narrowScreen)
{
_renderSpacerCell(context, arc);
_renderStartTableCell(writer, id, true);
writer.writeAttribute("valign", "middle", null);
writer.writeAttribute("nowrap", Boolean.TRUE, null);
_renderLink(context,
arc,
true,
nextOnClick,
nextRecords,
id,
source,
nextValue);
writer.endElement("td");
Icon nextIcon = getIcon(arc, true, (nextOnClick != null));
if (!nextIcon.isNull())
{
_renderSpacerCell(context, arc);
_renderStartTableCell(writer, id, true);
writer.writeAttribute("valign", "middle", null);
_renderArrow(context, arc, nextIcon, true, nextOnClick);
writer.endElement("td");
}
}
if (renderAsTable)
{
writer.endElement("tr");
writer.endElement("table");
}
if (renderAsTable && !isDesktop )
{
writer.endElement("div");
}
}
// Make sure we always restore the row index correctly
finally
{
setRowIndex(component, rowIndex);
}
}
/**
* render form value needed values and javascript code.
*/
public static void addHiddenFields(RenderingContext arc)
{
FormData fData = arc.getFormData();
fData.addNeededValue(TrinidadRenderingConstants.EVENT_PARAM);
fData.addNeededValue(TrinidadRenderingConstants.SOURCE_PARAM);
fData.addNeededValue(TrinidadRenderingConstants.PARTIAL_PARAM);
fData.addNeededValue(TrinidadRenderingConstants.VALUE_PARAM);
}
private void _renderChoice(
FacesContext context,
RenderingContext arc,
UIComponent component,
String id,
String source,
String form,
long minValue,
long currentValue,
int blockSize,
long maxValue,
boolean validate) throws IOException
{
UIComponent rangeLabel = getRangeLabel(component);
boolean firstRowAvailable = isRowAvailable(component, 0);
ResponseWriter writer = context.getResponseWriter();
// if there is no blockSize to step by, or there are no items in the
// table, then we don't render a choice
if ((blockSize <= 0) || (!firstRowAvailable) ||
((maxValue < minValue) &&
(maxValue != TrinidadRenderingConstants.MAX_VALUE_UNKNOWN)))
{
writer.writeText(XhtmlConstants.NBSP_STRING, null);
}
else
{
List<SelectItem> items =
new ArrayList<SelectItem>((int) _MAX_VISIBLE_OPTIONS);
int selectedIndex = _getItems(context, arc, component, items,
minValue, maxValue, currentValue,
blockSize, rangeLabel);
int count = items.size();
if (count > 1)
{
String choiceTip = arc.getTranslatedString(_CHOICE_TIP_KEY);
String choiceId = XhtmlUtils.getCompositeId(id, _CHOICE_ID_SUFFIX);
String onChange = ProcessUtils.getChoiceOnChangeFormSubmitted(
form, source, validate);
boolean javaScriptSupport = supportsScripting(arc);
writer.startElement("select", null);
writer.writeAttribute("title", choiceTip, null);
renderStyleClass(context, arc,
SkinSelectors.AF_FIELD_TEXT_STYLE_CLASS);
if (onChange != null && javaScriptSupport)
{
// set the onchange handler
writer.writeAttribute("onchange", onChange, null);
// set the onfocus handler to save the initial value
writer.writeAttribute("onfocus", _CHOICE_FORM_ON_FOCUS, null);
}
writer.writeAttribute("id", choiceId, null);
// For Non-JavaScript browsers, render the name attribute thus it
// would enable the browsers to include the name and value of this
// element in its payLoad.
if (!javaScriptSupport)
{
writer.writeAttribute("name", choiceId, null);
}
_writeSelectItems(context, items, selectedIndex);
writer.endElement("select");
if (HiddenLabelUtils.supportsHiddenLabels(arc))
{
HiddenLabelUtils.outputHiddenLabelIfNeeded(context,
arc,
choiceId,
choiceTip,
null);
}
// For Non-JavaScript browsers, render a input element(type= submit) to
// submit the page. Encode the name attribute with the parameter name
// and value thus it would enable the browsers to include the name of
// this element in its payLoad if it submits the page.
if (!javaScriptSupport)
{
String nameAttri = XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.MULTIPLE_VALUE_PARAM)
+ XhtmlUtils.getEncodedParameter(choiceId)
+ XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.SOURCE_PARAM)
+ XhtmlUtils.getEncodedParameter(source)
+ XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.EVENT_PARAM)
+ TrinidadRenderingConstants.GOTO_EVENT;
renderSubmitButtonNonJSBrowser(context,
arc,
TrinidadRenderingConstants.
NO_JS_PARAMETER_KEY_BUTTON,
nameAttri);
}
else
{
writer.startElement("script", null);
renderScriptDeferAttribute(context, arc);
renderScriptTypeAttribute(context, arc);
writer.writeText("_setSelectIndexById(\"", null);
writer.writeText(choiceId, null);
writer.writeText("\",", null);
writer.writeText(IntegerUtils.getString(selectedIndex), null);
writer.writeText(")", null);
writer.endElement("script");
}
}
else if (count == 1)
{
writer.startElement("span", null);
renderStyleClass(context, arc,
SkinSelectors.AF_FIELD_TEXT_STYLE_CLASS);
writer.writeText(items.get(0).getLabel(), null);
writer.endElement("span");
}
}
}
private void _writeSelectItems(
FacesContext context,
List<SelectItem> items,
int selectedIndex) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
int count = items.size();
for (int i = 0; i < count; i++)
{
SelectItem item = items.get(i);
writer.startElement("option", null);
writer.writeAttribute("value", item.getValue(), null);
if (i == selectedIndex)
writer.writeAttribute("selected", Boolean.TRUE, null);
writer.writeText(item.getLabel(), null);
writer.endElement("option");
}
}
/**
* create each of the choice options and add them onto the List.
* @return the number of options added
*/
private int _getItems(
FacesContext context,
RenderingContext arc,
UIComponent component,
List<SelectItem> items,
long minValue, long maxValue, long value,
int blockSize,
UIComponent rangeLabel)
{
int selectedIndex = -1;
boolean maxUnknown = (maxValue == TrinidadRenderingConstants.MAX_VALUE_UNKNOWN);
// Zero-indexed block index.
long blockIndex = (value - minValue + blockSize - 1L) / blockSize;
// sometimes a record set won't start on a multiple of blockSize. So
// remember to add any offset:
// this can safely be an int because it is an index into the blockSize,
// which is itself an int:
int offset = (int) (value - (minValue + (blockIndex * blockSize)));
if (offset < 0)
offset = offset + blockSize;
// Total number of blocks (again, zero-indexed)
long maxBlockIndex;
if (maxUnknown)
maxBlockIndex = blockIndex + 1;
else
{
maxBlockIndex = (maxValue - minValue - offset) / blockSize;
if (offset > 0)
maxBlockIndex++;
}
// Calculate the first block that should be shown. The order goes:
// Group 0: 0-28 + More
// Group 1:Previous + 29-56 + More
// Group 2:Previous + 57-84 + More
// etc..
long firstBlockIndex;
// If everything is visible, or we're in the first group, start at zero.
if ((maxBlockIndex <= (_MAX_VISIBLE_OPTIONS - 1L)) ||
(blockIndex <= (_MAX_VISIBLE_OPTIONS - 2L)))
firstBlockIndex = 0;
else
firstBlockIndex = ((blockIndex - 1L) / (_MAX_VISIBLE_OPTIONS - 2L)) *
(_MAX_VISIBLE_OPTIONS - 2L);
// And we always show a total of 30 groups (or straight to the end)
long lastBlockIndex = firstBlockIndex + (_MAX_VISIBLE_OPTIONS - 1L);
if (lastBlockIndex > maxBlockIndex)
lastBlockIndex = maxBlockIndex;
boolean showAllActive = getShowAll(getFacesBean(component));
// Add "Show All" option if showAll was set to true OR
// when there are less than 30 groups (maxBlockIndex
// start as zero, hence "29") and only allow it when there's
// more than 1 visible item!
if (showAllActive ||
(!maxUnknown && (lastBlockIndex > firstBlockIndex) &&
(maxBlockIndex <= (_MAX_VISIBLE_OPTIONS - 1L))
))
{
// Omit show all if it's not supported
if (showAllSupported())
{
items.add(_createShowAllSelectItem(arc,
maxValue));
if (showAllActive)
selectedIndex = 0;
}
}
for (blockIndex = firstBlockIndex;
blockIndex <= lastBlockIndex;
blockIndex++)
{
long blockStart = minValue + (blockIndex * blockSize);
// if there is an offset, then adjust accordingly. for example, if the
// offset is 7 (and the blockSize is 10), then the new blockStarts are:
// 1-7, 8-17, 18-27, etc ...
if (offset > 0)
blockStart += (offset - blockSize);
final int currentRecordSize;
// check to see if this is the very first record set in a table using an
// offset:
if (blockStart < minValue)
{
// treat this specially. this is the 1-7 case from the example above:
blockStart = minValue;
currentRecordSize = offset;
}
else
{
currentRecordSize = blockSize;
}
// return immediately if the start of the next range is not available.
if (maxUnknown)
{
if (!isRowAvailable(component, (int)blockStart - 1))
return selectedIndex;
}
String text;
// Need "Previous..."
if ((blockIndex == firstBlockIndex) &&
(blockIndex != 0))
{
text = arc.getTranslatedString(_PREVIOUS_TEXT_KEY);
}
// Need "More..." (on the last block, either 'cause
// the total number of blocks is unknown or we've shown enough blocks
// However, don't show More... if the total number of blocks is unknown,
// and we checked and found out that the start of the next block doesn't
// exist.
else if ((blockIndex == lastBlockIndex) &&
(maxUnknown || (lastBlockIndex < maxBlockIndex)))
{
text = arc.getTranslatedString(_MORE_TEXT_KEY);
}
else
{
text = null;
}
// =-=AEW I don't understand this next line...
long currValue = showAllActive ? minValue - 1 : value;// Don't select
SelectItem item = _createNavigationItem(context,
arc,
component,
blockStart,
currentRecordSize,
maxValue,
text,
rangeLabel);
if ((currValue >= blockStart) &&
(currValue < (blockStart + currentRecordSize)))
{
selectedIndex = items.size();
}
items.add(item);
}
return selectedIndex;
}
private SelectItem _createShowAllSelectItem(
RenderingContext arc,
long maxValue)
{
String[] parameters = new String[]{IntegerUtils.getString(maxValue)};
String showAllText = XhtmlUtils.getFormattedString(
arc.getTranslatedString(_SHOW_ALL_KEY),
parameters);
return new SelectItem(TrinidadRenderingConstants.VALUE_SHOW_ALL,
showAllText);
}
// create a choice option when max value is known
private SelectItem _createNavigationItem(
FacesContext context,
RenderingContext arc,
UIComponent component,
long blockStart,
int blockSize,
long maxValue,
String text,
UIComponent rangeLabel
)
{
// if text isn't null, it is More or Previous.
if (text == null)
text = _getRangeString(context,
arc,
component,
blockStart,
blockSize,
maxValue,
rangeLabel);
return new SelectItem(IntegerUtils.getString(blockStart),
text);
}
/**
* Returns true if disabled navigation items should be shown
*/
protected boolean disabledNavigationShown()
{
return true;
}
// create one of the text links for navigation
private void _renderLink(
FacesContext context,
RenderingContext arc,
boolean isNext,
String onclick,
int records,
String id,
String source,
long value ) throws IOException
{
String text = getBlockString(arc, isNext, records);
boolean isEnabled = ((onclick != null) && (records > 0));
ResponseWriter writer = context.getResponseWriter();
// if we have more than one record and browser is js capable then
// render as a link
if (isEnabled)
{
writer.startElement("a", null);
writer.writeURIAttribute("href", "#", null);
writer.writeAttribute("onclick", onclick, null);
// The navBar needs its initial focus to be on the Next button,
// according to the BLAF. Render a special id on the Next button
// if this navBar is to have the initial focus. (unless it needs
// initial focus, the Next button does not have an id on it)
if (isNext)
{
String linkID = _getIDForFocus(arc, id);
writer.writeAttribute("id", linkID, null);
}
renderStyleClass(context, arc, SkinSelectors.NAV_BAR_ALINK_STYLE_CLASS);
writer.writeText(text, null);
writer.endElement("a");
}
// if we don't have any record then just render as <span> element
else if (records < 1)
{
writer.startElement("span", null);
renderStyleClass(context, arc, SkinSelectors.NAV_BAR_ILINK_STYLE_CLASS);
writer.writeText(text, null);
writer.endElement("span");
}
// For Non-JavaScript browsers, render a submit element
// (<input type = "submit"/> ). Encode the the name attribute with the
// parameter name and value thus it would enable the browsers to
// include the name of this element in its payLoad if it submits the
else
{
String nameAttri = XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.SOURCE_PARAM)
+ XhtmlUtils.getEncodedParameter(source)
+ XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.EVENT_PARAM)
+ XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.GOTO_EVENT)
+ XhtmlUtils.getEncodedParameter
(TrinidadRenderingConstants.VALUE_PARAM)
+ IntegerUtils.getString(value);
writer.startElement("input", null);
writer.writeAttribute("type", "submit", null);
writer.writeAttribute("name", nameAttri, null);
writer.writeAttribute("value", text, "text");
renderStyleClass(context, arc,
SkinSelectors.AF_COMMAND_BUTTON_STYLE_CLASS);
// This style makes a button to appear as a link
writer.writeAttribute("style",
"border:none;background:inherit;text-decoration:underline;",null);
writer.endElement("input");
}
}
/**
*/
protected Icon getIcon(
RenderingContext arc,
boolean isNext,
boolean isEnabled
)
{
// get the image location
String iconName;
if (isNext)
{
if (isEnabled)
{
iconName = SkinSelectors.AF_SELECT_RANGE_CHOICE_BAR_NEXT_ICON_NAME;
}
else
{
iconName = SkinSelectors.AF_SELECT_RANGE_CHOICE_BAR_NEXT_DISABLED_ICON_NAME;
}
}
else
{
if (isEnabled)
{
iconName = SkinSelectors.AF_SELECT_RANGE_CHOICE_BAR_PREV_ICON_NAME;
}
else
{
iconName = SkinSelectors.AF_SELECT_RANGE_CHOICE_BAR_PREV_DISABLED_ICON_NAME;
}
}
return arc.getIcon(iconName);
}
protected String getIconTitleKey(
boolean isNext,
boolean isEnabled
)
{
if (isNext)
{
return (isEnabled) ? _NEXT_DESC_KEY : _DISABLED_NEXT_DESC_KEY;
}
else
{
return (isEnabled) ? _PREVIOUS_DESC_KEY : _DISABLED_PREVIOUS_DESC_KEY;
}
}
/**
* @todo GENERIC FIX: need to use renderURIAttribute() in Icon
* code to output the Icon URL. But that'll break a zillion
* renderkit tests.
*/
private void _renderArrow(
FacesContext context,
RenderingContext arc,
Icon icon,
boolean isNext,
String onclick) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
if (onclick != null)
{
writer.startElement("a", null);
writer.writeURIAttribute("href", "#", null);
writer.writeAttribute("onclick", onclick, null);
}
boolean isEnabled = (onclick != null);
String titleKey = getIconTitleKey(isNext, isEnabled);
String title = arc.getTranslatedString(titleKey);
OutputUtils.renderIcon(context, arc, icon, title, null);
if (onclick != null)
writer.endElement("a");
}
/**
* Gets the string to use for next/previous links
* in a table navigation bar.
*/
protected String getBlockString(
RenderingContext arc,
boolean isNext,
int numRecords
)
{
// check to make sure that we have some records in this direction:
if (numRecords > 0)
{
String pattern = (isNext)
? arc.getTranslatedString("af_selectRangeChoiceBar.NEXT")
: arc.getTranslatedString("af_selectRangeChoiceBar.PREVIOUS");
String value = IntegerUtils.getString(numRecords);
return XhtmlUtils.getFormattedString(pattern, new String[]{value});
}
else
{
// since we don't have any records, we are going to display some
// disabled text. see bug 1740486.
String text = (isNext)
? arc.getTranslatedString("af_selectRangeChoiceBar.DISABLED_NEXT")
: arc.getTranslatedString("af_selectRangeChoiceBar.DISABLED_PREVIOUS");
return text;
}
}
//
/**
* get the string for the current range
* @todo We probably shouldn't use the same substitution string
* when we know the max and when we don't. We should have two:
* {0}-{1} of {2}
* {0}-{1}
* (and not bother with the "of" substitution)
*/
@SuppressWarnings("unchecked")
private String _getRangeString(
FacesContext context,
RenderingContext arc,
UIComponent component,
long start,
int visibleItemCount,
long total,
UIComponent rangeLabel
)
{
// how many records do we really see now?
long currVisible = (total == TrinidadRenderingConstants.MAX_VALUE_UNKNOWN)
? visibleItemCount
: total - start + 1;
if (currVisible > visibleItemCount)
currVisible = visibleItemCount;
// getItemLabel from selectItem in the facet if it exists.
if ((rangeLabel != null) &&
((rangeLabel instanceof UISelectItem) ||
(rangeLabel instanceof UIXSelectItem)))
{
Range range = new Range();
// getting data is zero-indexed, whereas start is 1-indexed.
setRowIndex(component, (int)start-1);
Object startRow = getRowData(component);
range.setStart(startRow);
// get end row. If the end row doesn't exist, find the
// last row that does exist. The row indices are zero-indexed.
int endIndex = (int)(start + currVisible - 2);
endIndex = _setToExistingEndRow(component, (int)start -1, endIndex);
setRowIndex(component, endIndex);
// ok, we are sure we have an existing end row set, so set the end
// parameter on the range object.
range.setEnd(getRowData(component));
Object old = null;
String var = getVar(getFacesBean(component));
if (var != null)
{
Map<String, Object> requestMap =
context.getExternalContext().getRequestMap();
old = requestMap.put(var, range);
}
String label = (rangeLabel instanceof UISelectItem)
? ((UISelectItem) rangeLabel).getItemLabel()
: toString(((UIXSelectItem) rangeLabel).getAttributes().get("label"));
if (var != null)
{
Map<String, Object> requestMap =
context.getExternalContext().getRequestMap();
if (old == null)
requestMap.remove(var);
else
requestMap.put(var, old);
}
return label;
}
else
{
// formatter for generating the page string
String startParam = IntegerUtils.getString(start);
int endIndex = (int)(start + currVisible - 2);
endIndex = _setToExistingEndRow(component, (int)start -1, endIndex);
String endParam = IntegerUtils.getString(endIndex+1);
String pattern = null;
String[] parameters = null;
if ((total == TrinidadRenderingConstants.MAX_VALUE_UNKNOWN))
{
pattern = arc.getTranslatedString(_MULTI_RANGE_NO_TOTAL_FORMAT_STRING);
parameters = new String[]
{
startParam,
endParam
};
}
else
{
pattern = arc.getTranslatedString(_MULTI_RANGE_TOTAL_FORMAT_STRING);
parameters = new String[]
{
startParam,
endParam,
IntegerUtils.getString(total)
};
}
return XhtmlUtils.getFormattedString(pattern, parameters);
}
}
/**
* Find the highest end row in the range from startRowIndex to endRowIndex
* inclusive that exists, and make that row current
* (by calling selectRange.setRowIndex).
* @param startRowIndex. the start index for the first row in this range.
* @param endRowIndex the initial end row. that is, the row index for the
* last row in this range.
* @return the index of the highest end row that exists.
*/
private int _setToExistingEndRow(
UIComponent component,
int startRowIndex,
int endRowIndex )
{
boolean rowAvailable = isRowAvailable(component, endRowIndex);
// make sure that the end row exists. If it doesn't, then loop
// back from the end until we see that it does exist.
while (!rowAvailable && endRowIndex >= startRowIndex)
{
endRowIndex--;
rowAvailable = isRowAvailable(component, endRowIndex);
}
return endRowIndex;
}
// don't render as a table in certain locations like a page button bar
static boolean __renderAsTable(
UIComponent component
)
{
UIComponent parent = XhtmlUtils.getStructuralParent(component);
if ((parent instanceof UIXPanel) &&
("org.apache.myfaces.trinidad.ButtonBar".equals(parent.getRendererType()) ||
"org.apache.myfaces.trinidad.rich.ButtonBar".equals(parent.getRendererType())))
{
return false;
}
return true;
}
/**
* Writes the separator between two elements
*/
protected void renderItemSpacer(
FacesContext context,
RenderingContext arc) throws IOException
{
if (isPDA(arc))
{
context.getResponseWriter().writeText(XhtmlConstants.NBSP_STRING, null);
}
else
{
renderSpacer(context, arc, "5", "1");
}
}
/**
* Writes the separator between two elements
*/
private void _renderSpacerCell(
FacesContext context,
RenderingContext arc
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("td", null);
renderItemSpacer(context, arc);
writer.endElement("td");
}
/**
* render the "td".
*/
private void _renderStartTableCell(
ResponseWriter writer,
String id,
boolean alreadyRenderedId) throws IOException
{
writer.startElement("td", null);
if (!alreadyRenderedId)
{
writer.writeAttribute("id", id, null);
}
}
private String _getIDForFocus(
RenderingContext arc,
String baseId
)
{
// The navBar needs its initial focus to be on the Next button,
// according to the BLAF. Render a special id on the Next button
// if this navBar is to have the initial focus. (unless it needs
// initial focus, the Next button does not have an id on it)
// We get body's initialFocus attribute off of the rendering context.
// If this is equal to the navBar's id, then we make up a new id
// for the Next button.
Object initialFocusID =
arc.getProperties().get(TrinidadRenderingConstants.INITIAL_FOCUS_CONTEXT_PROPERTY);
String id = null;
if ((initialFocusID != null) && initialFocusID.equals(baseId))
{
// make up an id to use for the initial focus.
String focus = "-focus";
StringBuilder buffer = new StringBuilder(baseId.length()+
focus.length());
buffer.append(baseId);
buffer.append(focus);
id = buffer.toString();
// set the new id on the rendering context so that the body
// renderer can write it out to a script variable.
// A side-effect is that the initialFocusID in subsequent calls will
// never equal the navBar's id.
arc.getProperties().put(TrinidadRenderingConstants.INITIAL_FOCUS_CONTEXT_PROPERTY, id);
}
return id;
}
//
// Private variables
//
private PropertyKey _rowsKey;
private PropertyKey _firstKey;
private PropertyKey _showAllKey;
private PropertyKey _immediateKey;
private PropertyKey _varKey;
// resource keys
static private final String _PREVIOUS_DESC_KEY =
"af_selectRangeChoiceBar.PREVIOUS_TIP";
static private final String _NEXT_DESC_KEY =
"af_selectRangeChoiceBar.NEXT_TIP";
static private final String _DISABLED_PREVIOUS_DESC_KEY =
"af_selectRangeChoiceBar.PREV_DISABLED_TIP";
static private final String _DISABLED_NEXT_DESC_KEY =
"af_selectRangeChoiceBar.NEXT_DISABLED_TIP";
static private final String _CHOICE_TIP_KEY =
"af_selectRangeChoiceBar.CHOICE_TIP";
static private final String _MULTI_RANGE_NO_TOTAL_FORMAT_STRING =
"af_selectRangeChoiceBar.CHOICE_FORMAT_NO_TOTAL";
static private final String _MULTI_RANGE_TOTAL_FORMAT_STRING =
"af_selectRangeChoiceBar.CHOICE_FORMAT_TOTAL";
static private final String _PREVIOUS_TEXT_KEY =
"af_selectRangeChoiceBar.PREVIOUS_OPTION";
static private final String _MORE_TEXT_KEY =
"af_selectRangeChoiceBar.MORE_OPTION";
static private final String _SHOW_ALL_KEY =
"af_selectRangeChoiceBar.SHOW_ALL";
/**
* @todo This should be pulled from a skin property
*/
static private final long _MAX_VISIBLE_OPTIONS = 30L;
// on focus handler for the form case. We save off the old value so that
// we can restore it if the validation chokes
private static final String _CHOICE_FORM_ON_FOCUS =
"this._lastValue = this.selectedIndex";
static private final String _CHOICE_ID_SUFFIX = "c";
static private final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(SelectRangeChoiceBarRenderer.class);
}