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

org.primefaces.extensions.component.masterdetail.MasterDetail Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011-2024 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.util.Map;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.PostRestoreStateEvent;

import org.primefaces.component.breadcrumb.BreadCrumb;
import org.primefaces.model.menu.DefaultMenuItem;
import org.primefaces.model.menu.MenuModel;
import org.primefaces.util.Constants;
import org.primefaces.util.LangUtils;

/**
 * MasterDetail component.
 *
 * @author Oleg Varaksin / last modified by $Author$
 * @version $Revision$
 * @since 0.2
 */
@ResourceDependency(library = "primefaces-extensions", name = "primefaces-extensions.css")
public class MasterDetail extends UIComponentBase {

    public static final String CONTEXT_VALUE_VALUE_EXPRESSION = "mdContextValueVE";
    public static final String SELECTED_LEVEL_VALUE_EXPRESSION = "selectedLevelVE";
    public static final String SELECTED_STEP_VALUE_EXPRESSION = "selectedStepVE";
    public static final String PRESERVE_INPUTS_VALUE_EXPRESSION = "preserveInputsVE";
    public static final String RESET_INPUTS_VALUE_EXPRESSION = "resetInputsVE";
    public static final String CONTEXT_VALUES = "mdContextValues";
    public static final String SELECT_DETAIL_REQUEST = "_selectDetailRequest";
    public static final String CURRENT_LEVEL = "_currentLevel";
    public static final String SELECTED_LEVEL = "_selectedLevel";
    public static final String SELECTED_STEP = "_selectedStep";
    public static final String PRESERVE_INPUTS = "_preserveInputs";
    public static final String RESET_INPUTS = "_resetInputs";
    public static final String CURRENT_CONTEXT_VALUE = "_curContextValue";
    public static final String RESOLVED_CONTEXT_VALUE = "contextValue_";
    public static final String BREADCRUMB_ID_PREFIX = "_bc";

    public static final String COMPONENT_TYPE = "org.primefaces.extensions.component.MasterDetail";
    public static final String COMPONENT_FAMILY = "org.primefaces.extensions.component";
    private static final String DEFAULT_RENDERER = "org.primefaces.extensions.component.MasterDetailRenderer";

    private MasterDetailLevel detailLevelToProcess;
    private MasterDetailLevel detailLevelToGo;
    private int levelPositionToProcess = -1;
    private int levelCount = -1;

    /**
     * Properties that are tracked by state saving.
     *
     * @author Oleg Varaksin / last modified by $Author$
     * @version $Revision$
     */
    @SuppressWarnings("java:S115")
    protected enum PropertyKeys {
        // @formatter:off
        level,
        contextValue,
        selectLevelListener,
        showBreadcrumb,
        showAllBreadcrumbItems,
        showBreadcrumbFirstLevel,
        breadcrumbAboveHeader,
        style,
        styleClass
        // @formatter:on
    }

    public MasterDetail() {
        setRendererType(DEFAULT_RENDERER);
    }

    @Override
    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public int getLevel() {
        return (Integer) getStateHelper().eval(PropertyKeys.level, 1);
    }

    public void setLevel(final int level) {
        getStateHelper().put(PropertyKeys.level, level);
    }

    public Object getContextValue() {
        return getStateHelper().eval(PropertyKeys.contextValue, null);
    }

    public void setContextValue(final Object contextValue) {
        getStateHelper().put(PropertyKeys.contextValue, contextValue);
    }

    public MethodExpression getSelectLevelListener() {
        return (MethodExpression) getStateHelper().eval(PropertyKeys.selectLevelListener, null);
    }

    public void setSelectLevelListener(final MethodExpression selectLevelListener) {
        getStateHelper().put(PropertyKeys.selectLevelListener, selectLevelListener);
    }

    public boolean isShowBreadcrumb() {
        return (Boolean) getStateHelper().eval(PropertyKeys.showBreadcrumb, true);
    }

    public void setShowBreadcrumb(final boolean showBreadcrumb) {
        getStateHelper().put(PropertyKeys.showBreadcrumb, showBreadcrumb);
    }

    public boolean isShowAllBreadcrumbItems() {
        return (Boolean) getStateHelper().eval(PropertyKeys.showAllBreadcrumbItems, false);
    }

    public void setShowBreadcrumbFirstLevel(final boolean showBreadcrumbFirstLevel) {
        getStateHelper().put(PropertyKeys.showBreadcrumbFirstLevel, showBreadcrumbFirstLevel);
    }

    public boolean isShowBreadcrumbFirstLevel() {
        return (Boolean) getStateHelper().eval(PropertyKeys.showBreadcrumbFirstLevel, true);
    }

    public void setShowAllBreadcrumbItems(final boolean showAllBreadcrumbItems) {
        getStateHelper().put(PropertyKeys.showAllBreadcrumbItems, showAllBreadcrumbItems);
    }

    public boolean isBreadcrumbAboveHeader() {
        return (Boolean) getStateHelper().eval(PropertyKeys.breadcrumbAboveHeader, true);
    }

    public void setBreadcrumbAboveHeader(final boolean breadcrumbAboveHeader) {
        getStateHelper().put(PropertyKeys.breadcrumbAboveHeader, breadcrumbAboveHeader);
    }

    public String getStyle() {
        return (String) getStateHelper().eval(PropertyKeys.style, null);
    }

    public void setStyle(final String style) {
        getStateHelper().put(PropertyKeys.style, style);
    }

    public String getStyleClass() {
        return (String) getStateHelper().eval(PropertyKeys.styleClass, null);
    }

    public void setStyleClass(final String styleClass) {
        getStateHelper().put(PropertyKeys.styleClass, styleClass);
    }

    @Override
    public void processEvent(final ComponentSystemEvent event) {
        super.processEvent(event);

        final FacesContext fc = FacesContext.getCurrentInstance();
        if (!(event instanceof PostRestoreStateEvent) || !isSelectDetailRequest(fc)) {
            return;
        }

        final PartialViewContext pvc = fc.getPartialViewContext();
        if (pvc.getRenderIds().isEmpty()) {
            // update the MasterDetail component automatically
            pvc.getRenderIds().add(getClientId(fc));
        }

        final MasterDetailLevel mdl = getDetailLevelToProcess(fc);
        final Object contextValue = getContextValueFromFlow(fc, mdl, true);
        final String contextVar = mdl.getContextVar();
        if (LangUtils.isNotBlank(contextVar) && contextValue != null) {
            final Map requestMap = fc.getExternalContext().getRequestMap();
            requestMap.put(contextVar, contextValue);
        }
    }

    @Override
    public void processDecodes(final FacesContext fc) {
        if (!isSelectDetailRequest(fc)) {
            super.processDecodes(fc);
        }
        else {
            getDetailLevelToProcess(fc).processDecodes(fc);
        }
    }

    @Override
    public void processValidators(final FacesContext fc) {
        if (!isSelectDetailRequest(fc)) {
            super.processValidators(fc);
        }
        else {
            getDetailLevelToProcess(fc).processValidators(fc);
        }
    }

    @Override
    public void processUpdates(final FacesContext fc) {
        if (!isSelectDetailRequest(fc)) {
            super.processUpdates(fc);
        }
        else {
            getDetailLevelToProcess(fc).processUpdates(fc);
        }
    }

    public MasterDetailLevel getDetailLevelToProcess(final FacesContext fc) {
        if (detailLevelToProcess == null) {
            initDataForLevels(fc);
        }

        return detailLevelToProcess;
    }

    public MasterDetailLevel getDetailLevelToGo(final FacesContext fc) {
        if (detailLevelToGo != null) {
            return detailLevelToGo;
        }

        final String strSelectedLevel = fc.getExternalContext().getRequestParameterMap()
                    .get(getClientId(fc) + SELECTED_LEVEL);
        final String strSelectedStep = fc.getExternalContext().getRequestParameterMap()
                    .get(getClientId(fc) + SELECTED_STEP);

        // selected level != null
        if (strSelectedLevel != null) {
            final int selectedLevel = Integer.parseInt(strSelectedLevel);
            detailLevelToGo = getDetailLevelByLevel(selectedLevel);
            if (detailLevelToGo != null) {
                return detailLevelToGo;
            }

            throw new FacesException("MasterDetailLevel for selected level = " + selectedLevel + " not found.");
        }

        final int step;
        if (strSelectedStep != null) {
            // selected step != null
            step = Integer.parseInt(strSelectedStep);
        }
        else {
            // selected level and selected step are null ==> go to the next level
            step = 1;
        }

        detailLevelToGo = getDetailLevelByStep(step);

        return detailLevelToGo;
    }

    public MasterDetailLevel getDetailLevelByLevel(final int level) {
        for (final UIComponent child : getChildren()) {
            if (child instanceof MasterDetailLevel) {
                final MasterDetailLevel mdl = (MasterDetailLevel) child;
                if (mdl.getLevel() == level) {
                    return mdl;
                }
            }
        }

        return null;
    }

    public boolean isSelectDetailRequest(final FacesContext fc) {
        return fc.getPartialViewContext().isAjaxRequest()
                    && fc.getExternalContext().getRequestParameterMap()
                                .containsKey(getClientId(fc) + SELECT_DETAIL_REQUEST);
    }

    public String getPreserveInputs(final FacesContext fc) {
        return fc.getExternalContext().getRequestParameterMap().get(getClientId(fc) + PRESERVE_INPUTS);
    }

    public String getResetInputs(final FacesContext fc) {
        return fc.getExternalContext().getRequestParameterMap().get(getClientId(fc) + RESET_INPUTS);
    }

    public void updateModel(final FacesContext fc, final MasterDetailLevel mdlToGo) {
        final int levelToGo = mdlToGo.getLevel();
        final ValueExpression levelVE = getValueExpression(PropertyKeys.level.toString());
        if (levelVE != null) {
            // update "level"
            levelVE.setValue(fc.getELContext(), levelToGo);
            getStateHelper().remove(PropertyKeys.level);
        }

        // get component caused this ajax request
        final String source = fc.getExternalContext().getRequestParameterMap()
                    .get(Constants.RequestParams.PARTIAL_SOURCE_PARAM);
        final MasterDetailLevel mdl = getDetailLevelToProcess(fc);

        // get resolved context value
        Object contextValue = null;
        final Map contextValues = (Map) mdl.getAttributes().get(CONTEXT_VALUES);
        if (contextValues != null) {
            contextValue = contextValues.get(RESOLVED_CONTEXT_VALUE + source);
        }

        if (contextValue != null) {
            // update current context value for corresponding MasterDetailLevel
            mdlToGo.getAttributes().put(getClientId(fc) + CURRENT_CONTEXT_VALUE, contextValue);

            final ValueExpression contextValueVE = getValueExpression(PropertyKeys.contextValue.toString());
            if (contextValueVE != null) {
                // update "contextValue"
                contextValueVE.setValue(fc.getELContext(), contextValue);
                getStateHelper().remove(PropertyKeys.contextValue);
            }
        }
    }

    public Object getContextValueFromFlow(final FacesContext fc, final MasterDetailLevel mdl,
                final boolean includeModel) {
        // try to get context value from internal storage
        final Object contextValue = mdl.getAttributes().get(getClientId(fc) + MasterDetail.CURRENT_CONTEXT_VALUE);
        if (contextValue != null) {
            return contextValue;
        }

        // try to get context value from external storage (e.g. managed bean)
        if (includeModel) {
            return getContextValue();
        }

        return null;
    }

    public BreadCrumb getBreadcrumb() {
        BreadCrumb breadCrumb = null;
        for (final UIComponent child : getChildren()) {
            if (child instanceof BreadCrumb) {
                breadCrumb = (BreadCrumb) child;

                break;
            }
        }

        final MenuModel model = breadCrumb != null ? breadCrumb.getModel() : null;

        if (model != null && model.getElements().isEmpty()) {
            final String clientId = getClientId();
            final String menuItemIdPrefix = getId() + "_bcItem_";

            for (final UIComponent child : getChildren()) {
                if (child instanceof MasterDetailLevel) {
                    final int level = ((MasterDetailLevel) child).getLevel();

                    // create menu item to detail level
                    final DefaultMenuItem menuItem = new DefaultMenuItem();
                    menuItem.setId(menuItemIdPrefix + level);
                    menuItem.setAjax(true);
                    menuItem.setImmediate(true);
                    menuItem.setProcess("@none");
                    menuItem.setUpdate("@parent");

                    // add UIParameter
                    menuItem.setParam(clientId + MasterDetail.SELECT_DETAIL_REQUEST, true);
                    menuItem.setParam(clientId + MasterDetail.CURRENT_LEVEL, -1);
                    menuItem.setParam(clientId + MasterDetail.SELECTED_LEVEL, level);

                    model.getElements().add(menuItem);
                }
            }
        }

        return breadCrumb;
    }

    public void resetCalculatedValues() {
        detailLevelToProcess = null;
        detailLevelToGo = null;
        levelPositionToProcess = -1;
        levelCount = -1;
    }

    private void initDataForLevels(final FacesContext fc) {
        final String strCurrentLevel = fc.getExternalContext().getRequestParameterMap()
                    .get(getClientId(fc) + CURRENT_LEVEL);
        if (strCurrentLevel == null) {
            throw new FacesException("Current level is missing in request.");
        }

        final int currentLevel = Integer.parseInt(strCurrentLevel);
        int count = 0;

        for (final UIComponent child : getChildren()) {
            if (child instanceof MasterDetailLevel) {
                final MasterDetailLevel mdl = (MasterDetailLevel) child;
                count++;

                if (detailLevelToProcess == null && mdl.getLevel() == currentLevel) {
                    detailLevelToProcess = mdl;
                    levelPositionToProcess = count;
                }
            }
        }

        levelCount = count;

        if (detailLevelToProcess == null) {
            throw new FacesException("Current MasterDetailLevel to process not found.");
        }
    }

    private MasterDetailLevel getDetailLevelByStep(final int step) {
        int levelPositionToGo = getLevelPositionToProcess() + step;
        if (levelPositionToGo < 1) {
            levelPositionToGo = 1;
        }
        else if (levelPositionToGo > getLevelCount()) {
            levelPositionToGo = getLevelCount();
        }

        int pos = 0;
        for (final UIComponent child : getChildren()) {
            if (child instanceof MasterDetailLevel) {
                final MasterDetailLevel mdl = (MasterDetailLevel) child;
                pos++;

                if (pos == levelPositionToGo) {
                    return mdl;
                }
            }
        }

        // should not happen
        return null;
    }

    private int getLevelPositionToProcess() {
        if (levelPositionToProcess == -1) {
            initDataForLevels(FacesContext.getCurrentInstance());
        }

        return levelPositionToProcess;
    }

    private int getLevelCount() {
        if (levelCount == -1) {
            initDataForLevels(FacesContext.getCurrentInstance());
        }

        return levelCount;
    }

    @Override
    public Object saveState(final FacesContext context) {
        // reset component for MyFaces view pooling
        detailLevelToGo = null;
        detailLevelToProcess = null;
        levelCount = -1;
        levelPositionToProcess = -1;

        return super.saveState(context);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy