/** * Copyright 2014-2017 Riccardo Massera (TheCoder4.Eu) and Stephan Rauh (http://www.beyondjava.net). * * This file is part of BootsFaces. * * 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. */ package net.bootsfaces.component.scrollSpy; import java.io.IOException; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.component.UIForm; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.FacesRenderer; import net.bootsfaces.component.scrollSpy.event.ScrollSpyEventListener; import net.bootsfaces.expressions.ExpressionResolver; import net.bootsfaces.render.CoreRenderer; import net.bootsfaces.utils.BsfUtils; /** This class generates the HTML code of <b:scrollSpy />. */ @FacesRenderer(componentFamily = "net.bootsfaces.component", rendererType = "net.bootsfaces.component.scrollSpy.ScrollSpy") public class ScrollSpyRenderer extends CoreRenderer { /** * Decode ajax behaviours specific to the components */ @Override public void decode(FacesContext context, UIComponent component) { ScrollSpy ss = (ScrollSpy) component; super.decode(context, ss); final ScrollSpyEventListener selectionListener = ss.getSelectionListener(); String params = context.getExternalContext().getRequestParameterMap().get("params"); if (params != null && selectionListener != null) { if (params != null) { params = params.replace("BsFEvent=", ""); String[] pair = params.split(":", 2); String key = pair[0]; String value = null; if (pair.length == 2) { value = pair[1]; } if (value != null) { if ("itemSelected".equals(key)) { selectionListener.itemSelected(value); } else { throw new FacesException("Unexpected event when trying to decode the scrollspy event: " + key); } } } } } /** * This methods generates the HTML code of the current b:scrollSpy. * @param context the FacesContext. * @param component the current b:scrollSpy. * @throws IOException thrown if something goes wrong when writing the HTML code. */ @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { // do nothing } @Override public void encodeEnd(FacesContext context, UIComponent component) throws IOException { if (!component.isRendered()) { return; } ScrollSpy scrollSpy = (ScrollSpy) component; ResponseWriter rw = context.getResponseWriter(); String clientId = scrollSpy.getClientId(); // Get attributes String container = scrollSpy.getContainer(); if(!BsfUtils.isStringValued(container)) { container = "body"; } else container = "#" + decodeAndEscapeSelectors(context, component, container); String target = scrollSpy.getTarget(); if(!BsfUtils.isStringValued(target)) { target = ".navbar"; } else target = "#" + decodeAndEscapeSelectors(context, component, target); int offset = scrollSpy.getOffset(); if(!BsfUtils.isStringValued(target)) { offset = 20; } boolean smooth = scrollSpy.isSmooth(); int smoothSpeed = scrollSpy.getSmoothSpeed(); boolean hasListeners = (scrollSpy.getSelectionListener() != null); // String updateItems = BsfUtils.GetOrDefault("'" + scrollSpy.getUpdate() + "'", "null"); String updateItems = scrollSpy.getUpdate(); if(updateItems != null) updateItems = ExpressionResolver.getComponentIDs(context, component, updateItems); if(hasListeners) { // check is inside form (MyFaces requires to be) final UIForm form = BsfUtils.getClosestForm(scrollSpy); if(form == null) { throw new FacesException("The scrollspy component must be inside a form if an actionlistener is defined", null); } } rw.startElement("script", component); rw.writeText("" + "$(document).ready(function() { " + " $('" + container + "').scrollspy({ target: '" + target + "', offset: " + offset + "});", null); if(smooth) { rw.writeText("" + "$('" + target + " a').on('click', function(event) { " + " console.log('click called'); " + " var hash = this.hash; " + " $('" + ("body".equals(container) ? "html, body": container) + "').animate({ " + " scrollTop: $(hash).parent().scrollTop() + $(hash).offset().top - $(hash).parent().offset().top " + " }, { " + " duration: " + smoothSpeed + ", " + " specialEasing: { " + " width: 'linear', " + " height: 'easeOutBounce' " + " } "+ " }); " + " event.preventDefault(); " + "}); ", null); } if(hasListeners) { rw.writeText("" + "$('" + target + "').on('activate.bs.scrollspy', function() { " + " var x = $('" + target + " li.active > a').text(); " + " BsF.ajax.callAjax(this, 'action', '" + updateItems + "', '" + clientId + "', null, null, null, 'itemSelected:' + x); " + "}); ", null); } rw.writeText("});", null); rw.endElement("script"); } /** * Decode and escape selectors if necessary * @param context * @param component * @param selector * @return */ private String decodeAndEscapeSelectors(FacesContext context, UIComponent component, String selector) { selector = ExpressionResolver.getComponentIDs(context, component, selector); selector = BsfUtils.escapeJQuerySpecialCharsInSelector(selector); return selector; } }