org.primefaces.extensions.component.masterdetail.MasterDetailRenderer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of primefaces-extensions Show documentation
Show all versions of primefaces-extensions Show documentation
PrimeFaces Extensions components and utilities for PrimeFaces.
/*
* Copyright (c) 2011-2023 PrimeFaces Extensions
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.primefaces.extensions.component.masterdetail;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.render.Renderer;
import org.primefaces.component.breadcrumb.BreadCrumb;
import org.primefaces.extensions.util.Attrs;
import org.primefaces.extensions.util.ExtLangUtils;
import org.primefaces.model.menu.DefaultMenuItem;
import org.primefaces.model.menu.MenuElement;
import org.primefaces.model.menu.MenuItem;
import org.primefaces.renderkit.CoreRenderer;
import org.primefaces.util.ComponentUtils;
import org.primefaces.util.FastStringWriter;
import org.primefaces.util.LangUtils;
/**
* Renderer for the {@link MasterDetail} component.
*
* @author Oleg Varaksin / last modified by $Author$
* @version $Revision$
* @since 0.2
*/
public class MasterDetailRenderer extends CoreRenderer {
private static final String FACET_HEADER = "header";
private static final String FACET_FOOTER = "footer";
private static final String FACET_LABEL = Attrs.LABEL;
@Override
public void encodeEnd(final FacesContext fc, final UIComponent component) throws IOException {
final MasterDetail masterDetail = (MasterDetail) component;
final MasterDetailLevel mdl;
if (masterDetail.isSelectDetailRequest(fc)) {
// component has been navigated via SelectDetailLevel
final MasterDetailLevel mdlToProcess = masterDetail.getDetailLevelToProcess(fc);
if (fc.isValidationFailed()) {
mdl = mdlToProcess;
}
else {
mdl = getDetailLevelToEncode(fc, masterDetail, mdlToProcess, masterDetail.getDetailLevelToGo(fc));
// reset last saved validation state and stored values of editable components
final MasterDetailLevelVisitCallback visitCallback = new MasterDetailLevelVisitCallback();
mdlToProcess.visitTree(VisitContext.createVisitContext(fc), visitCallback);
final String preserveInputs = masterDetail.getPreserveInputs(fc);
final String resetInputs = masterDetail.getResetInputs(fc);
final String[] piIds = preserveInputs != null ? preserveInputs.split("[\\s,]+") : null;
final String[] riIds = resetInputs != null ? resetInputs.split("[\\s,]+") : null;
final boolean preserveAll = ExtLangUtils.contains(piIds, "@all");
final boolean resetAll = ExtLangUtils.contains(riIds, "@all");
final List editableValueHolders = visitCallback.getEditableValueHolders();
for (final EditableValueHolder editableValueHolder : editableValueHolders) {
final String clientId = ((UIComponent) editableValueHolder).getClientId(fc);
if (resetAll || ExtLangUtils.contains(riIds, clientId)) {
editableValueHolder.resetValue();
}
else if (preserveAll || ExtLangUtils.contains(piIds, clientId)) {
editableValueHolder.setValue(getConvertedSubmittedValue(fc, editableValueHolder));
}
else {
// default behavior
editableValueHolder.resetValue();
}
}
}
masterDetail.updateModel(fc, mdl);
}
else {
// component has been navigated from the outside, e.g. GET request or POST update from another component
mdl = masterDetail.getDetailLevelByLevel(masterDetail.getLevel());
}
if (mdl == null) {
throw new FacesException(
"MasterDetailLevel [Level=" + masterDetail.getLevel() +
"] must be nested inside a MasterDetail component!");
}
// render MasterDetailLevel
encodeMarkup(fc, masterDetail, mdl);
// reset calculated values
masterDetail.resetCalculatedValues();
}
protected MasterDetailLevel getDetailLevelToEncode(final FacesContext fc, final MasterDetail masterDetail,
final MasterDetailLevel mdlToProcess,
final MasterDetailLevel mdlToGo) {
if (masterDetail.getSelectLevelListener() != null) {
final SelectLevelEvent selectLevelEvent = new SelectLevelEvent(masterDetail, mdlToProcess.getLevel(),
mdlToGo.getLevel());
final int levelToEncode = (Integer) masterDetail.getSelectLevelListener()
.invoke(fc.getELContext(), new Object[] {selectLevelEvent});
if (levelToEncode != mdlToGo.getLevel()) {
// new MasterDetailLevel to go
return masterDetail.getDetailLevelByLevel(levelToEncode);
}
}
return mdlToGo;
}
protected void encodeMarkup(final FacesContext fc, final MasterDetail masterDetail, final MasterDetailLevel mdl)
throws IOException {
if (mdl == null) {
throw new FacesException("MasterDetailLevel must be nested inside a MasterDetail component!");
}
final ResponseWriter writer = fc.getResponseWriter();
final String clientId = masterDetail.getClientId(fc);
final String styleClass = masterDetail.getStyleClass() == null ? "pe-master-detail" : "pe-master-detail " + masterDetail.getStyleClass();
writer.startElement("div", masterDetail);
writer.writeAttribute("id", clientId, "id");
writer.writeAttribute(Attrs.CLASS, styleClass, "styleClass");
if (masterDetail.getStyle() != null) {
writer.writeAttribute(Attrs.STYLE, masterDetail.getStyle(), Attrs.STYLE);
}
if (masterDetail.isShowBreadcrumb()) {
if (masterDetail.isBreadcrumbAboveHeader()) {
// render breadcrumb and then header
renderBreadcrumb(fc, masterDetail, mdl);
encodeFacet(fc, masterDetail, FACET_HEADER);
}
else {
// render header and then breadcrumb
encodeFacet(fc, masterDetail, FACET_HEADER);
renderBreadcrumb(fc, masterDetail, mdl);
}
}
else {
// render header without breadcrumb
encodeFacet(fc, masterDetail, FACET_HEADER);
}
// render container for MasterDetailLevel
writer.startElement("div", null);
writer.writeAttribute("id", clientId + "_detaillevel", "id");
writer.writeAttribute(Attrs.CLASS, "pe-master-detail-level", null);
// try to get context value if contextVar exists
Object contextValue = null;
final String contextVar = mdl.getContextVar();
if (LangUtils.isNotBlank(contextVar)) {
contextValue = masterDetail.getContextValueFromFlow(fc, mdl, true);
}
if (contextValue != null) {
final Map requestMap = fc.getExternalContext().getRequestMap();
requestMap.put(contextVar, contextValue);
}
// render MasterDetailLevel
mdl.encodeAll(fc);
if (contextValue != null) {
fc.getExternalContext().getRequestMap().remove(contextVar);
}
writer.endElement("div");
// render footer
encodeFacet(fc, masterDetail, FACET_FOOTER);
writer.endElement("div");
}
protected void renderBreadcrumb(final FacesContext fc, final MasterDetail masterDetail, final MasterDetailLevel mdl)
throws IOException {
// get breadcrumb and its current model
final BreadCrumb breadcrumb = masterDetail.getBreadcrumb();
// update breadcrumb items
updateBreadcrumb(fc, breadcrumb, masterDetail, mdl);
if (!masterDetail.isShowBreadcrumbFirstLevel()) {
final int levelToRender = mdl.getLevel();
if (levelToRender == 1) {
breadcrumb.setStyleClass("ui-helper-hidden");
}
else {
breadcrumb.setStyleClass("");
}
}
// render breadcrumb
breadcrumb.encodeAll(fc);
}
protected void encodeFacet(final FacesContext fc, final UIComponent component, final String name)
throws IOException {
final UIComponent facet = component.getFacet(name);
if (ComponentUtils.shouldRenderFacet(facet)) {
facet.encodeAll(fc);
}
}
protected void updateBreadcrumb(final FacesContext fc, final BreadCrumb breadcrumb, final MasterDetail masterDetail,
final MasterDetailLevel mdlToRender) throws IOException {
boolean lastMdlFound = false;
final int levelToRender = mdlToRender.getLevel();
final boolean isShowAllBreadcrumbItems = masterDetail.isShowAllBreadcrumbItems();
for (final UIComponent child : masterDetail.getChildren()) {
if (child instanceof MasterDetailLevel) {
final MasterDetailLevel mdl = (MasterDetailLevel) child;
final DefaultMenuItem menuItem = getMenuItemByLevel(breadcrumb, masterDetail, mdl);
if (menuItem == null) {
// note: don't throw exception because menuItem can be null when MasterDetail is within DataTable
return;
}
if (!child.isRendered()) {
menuItem.setRendered(false);
if (!lastMdlFound) {
lastMdlFound = mdl.getLevel() == mdlToRender.getLevel();
}
continue;
}
if (lastMdlFound && !isShowAllBreadcrumbItems) {
menuItem.setRendered(false);
}
else {
menuItem.setRendered(true);
final Object contextValue = masterDetail.getContextValueFromFlow(fc, mdl,
mdl.getLevel() == mdlToRender.getLevel());
final String contextVar = mdl.getContextVar();
final boolean putContext = LangUtils.isNotBlank(contextVar) && contextValue != null;
if (putContext) {
final Map requestMap = fc.getExternalContext().getRequestMap();
requestMap.put(contextVar, contextValue);
}
final UIComponent facet = mdl.getFacet(FACET_LABEL);
if (ComponentUtils.shouldRenderFacet(facet)) {
// swap writers
final ResponseWriter writer = fc.getResponseWriter();
final FastStringWriter fsw = new FastStringWriter();
final ResponseWriter clonedWriter = writer.cloneWithWriter(fsw);
fc.setResponseWriter(clonedWriter);
// render facet's children
facet.encodeAll(fc);
// restore the original writer
fc.setResponseWriter(writer);
// set menuitem label from facet
menuItem.setValue(ExtLangUtils.unescapeXml(fsw.toString()));
}
else {
// set menuitem label from tag attribute
menuItem.setValue(mdl.getLevelLabel());
}
if (isShowAllBreadcrumbItems && lastMdlFound) {
menuItem.setDisabled(true);
}
else {
menuItem.setDisabled(mdl.isLevelDisabled());
}
if (putContext) {
fc.getExternalContext().getRequestMap().remove(contextVar);
}
if (!menuItem.isDisabled()) {
// set current level parameter
updateUIParameter(menuItem, masterDetail.getClientId(fc) + MasterDetail.CURRENT_LEVEL,
levelToRender);
}
}
if (!lastMdlFound) {
lastMdlFound = mdl.getLevel() == mdlToRender.getLevel();
}
}
}
}
protected DefaultMenuItem getMenuItemByLevel(final BreadCrumb breadcrumb, final MasterDetail masterDetail,
final MasterDetailLevel mdl) {
final String menuItemId = masterDetail.getId() + "_bcItem_" + mdl.getLevel();
for (final MenuElement child : breadcrumb.getModel().getElements()) {
if (menuItemId.equals(child.getId())) {
return (DefaultMenuItem) child;
}
}
return null;
}
protected void updateUIParameter(final MenuItem menuItem, final String name, final Object value) {
final Map> params = menuItem.getParams();
if (params == null) {
return;
}
for (final String key : params.keySet()) {
if (key.equals(name)) {
params.remove(key);
menuItem.setParam(name, value);
break;
}
}
}
@Override
public void encodeChildren(final FacesContext fc, final UIComponent component) {
// rendering happens on encodeEnd
}
@Override
public boolean getRendersChildren() {
return true;
}
public static Object getConvertedSubmittedValue(final FacesContext fc, final EditableValueHolder evh) {
final Object submittedValue = evh.getSubmittedValue();
if (submittedValue == null) {
return null;
}
try {
final UIComponent component = (UIComponent) evh;
final Renderer renderer = getRenderer(fc, component);
if (renderer != null) {
// convert submitted value by renderer
return renderer.getConvertedValue(fc, component, submittedValue);
}
else if (submittedValue instanceof String) {
// convert submitted value by registered (implicit or explicit)
// converter
final Converter converter = ComponentUtils.getConverter(fc, component);
if (converter != null) {
return converter.getAsObject(fc, component, (String) submittedValue);
}
}
}
catch (final Exception e) {
// a conversion error occurred
return null;
}
return submittedValue;
}
public static Renderer getRenderer(final FacesContext fc, final UIComponent component) {
final String rendererType = component.getRendererType();
if (rendererType != null) {
return fc.getRenderKit().getRenderer(component.getFamily(), rendererType);
}
return null;
}
}