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

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

/*
 * Copyright 2011-2015 PrimeFaces Extensions
 *
 * Licensed 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.
 *
 * $Id$
 */

package org.primefaces.extensions.component.masterdetail;

import org.apache.commons.lang3.StringUtils;
import org.primefaces.component.breadcrumb.BreadCrumb;
import org.primefaces.model.menu.DefaultMenuItem;
import org.primefaces.model.menu.MenuModel;
import org.primefaces.util.Constants;

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.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.PostRestoreStateEvent;
import java.util.Map;

/**
 * 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 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";

	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";

	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$
	 */
	protected enum PropertyKeys {

		level,
		contextValue,
		selectLevelListener,
		showBreadcrumb,
		showAllBreadcrumbItems,
		breadcrumbAboveHeader,
		style,
		styleClass;

		private String toString;

		PropertyKeys(String toString) {
			this.toString = toString;
		}

		PropertyKeys() {
		}

		@Override
		public String toString() {
			return ((this.toString != null) ? this.toString : super.toString());
		}
	}

	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(int level) {
		getStateHelper().put(PropertyKeys.level, level);
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return detailLevelToProcess;
	}

	public MasterDetailLevel getDetailLevelToGo(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) {
			int selectedLevel = Integer.valueOf(strSelectedLevel);
			detailLevelToGo = getDetailLevelByLevel(selectedLevel);
			if (detailLevelToGo != null) {
				return detailLevelToGo;
			}

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

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

		detailLevelToGo = getDetailLevelByStep(step);

		return detailLevelToGo;
	}

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

		return null;
	}

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

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

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

	public void updateModel(FacesContext fc, MasterDetailLevel mdlToGo) {
		final int levelToGo = mdlToGo.getLevel();
		ValueExpression levelVE = this.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);
		MasterDetailLevel mdl = getDetailLevelToProcess(fc);

		// get resolved context value
		Object contextValue = null;
		@SuppressWarnings("unchecked")
		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);

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

	public Object getContextValueFromFlow(FacesContext fc, MasterDetailLevel mdl, boolean includeModel) {
		// try to get context value from internal storage
		Object contextValue = mdl.getAttributes().get(this.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 this.getContextValue();
		}

		return null;
	}

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

				break;
			}
		}

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

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

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

					// create menu item to detail level
					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.addElement(menuItem);
				}
			}
		}

		return breadCrumb;
	}

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

	private void initDataForLevels(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.");
		}

		int currentLevel = Integer.valueOf(strCurrentLevel);
		int count = 0;

		for (UIComponent child : getChildren()) {
			if (child instanceof MasterDetailLevel) {
				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(int step) {
		int levelPositionToGo = getLevelPositionToProcess() + step;
		if (levelPositionToGo < 1) {
			levelPositionToGo = 1;
		} else if (levelPositionToGo > getLevelCount()) {
			levelPositionToGo = getLevelCount();
		}

		int pos = 0;
		for (UIComponent child : getChildren()) {
			if (child instanceof MasterDetailLevel) {
				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;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy