/**
* 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.radiobutton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.el.ValueExpression;
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.model.SelectItem;
import javax.faces.render.FacesRenderer;
import net.bootsfaces.beans.ELTools;
import net.bootsfaces.component.SelectItemAndComponent;
import net.bootsfaces.component.SelectItemUtils;
import net.bootsfaces.component.ajax.AJAXRenderer;
import net.bootsfaces.component.ajax.IAJAXComponent;
import net.bootsfaces.component.inputText.InputTextRenderer;
import net.bootsfaces.render.Responsive;
import net.bootsfaces.render.Tooltip;
/** This class generates the HTML code of <b:radiobutton />. */
@FacesRenderer(componentFamily = "net.bootsfaces.component", rendererType = "net.bootsfaces.component.radiobutton.Radiobutton")
public class RadiobuttonRenderer extends InputTextRenderer {
private UIComponent findComponentByName(UIComponent c, String name) {
Iterator<UIComponent> children = c.getFacetsAndChildren();
while(children.hasNext()) {
UIComponent comp = children.next();
if (comp instanceof Radiobutton) {
if (name.equals(((Radiobutton)comp).getName())) return comp;
}
UIComponent r = findComponentByName(comp, name);
if (r != null) return r;
}
return null;
}
private List<UIComponent> findComponentsByName(UIComponent c, String name) {
List<UIComponent> result = new ArrayList<UIComponent>();
Iterator<UIComponent> children = c.getFacetsAndChildren();
while(children.hasNext()) {
UIComponent comp = children.next();
if (comp instanceof Radiobutton) {
if (name.equals(((Radiobutton)comp).getName())) {
result.add(comp);
}
}
List<UIComponent> r = findComponentsByName(comp, name);
if (!r.isEmpty()) {
result.addAll(r);
}
}
return result;
}
@Override
public void decode(FacesContext context, UIComponent component) {
Radiobutton radioButton = (Radiobutton) component;
if (radioButton.isDisabled() || radioButton.isReadonly()) {
return;
}
decodeBehaviors(context, radioButton);
String clientId = radioButton.getClientId(context);
String name = radioButton.getName();
if (null == name) {
name = "input_" + clientId;
}
UIForm form = findSurroundingForm(component);
// AJAX fires decode to all radio buttons. Change value only for the first element
List<UIComponent> radioButtonGroup = findComponentsByName(form, radioButton.getName());
if (radioButtonGroup.get(0) == component) {
List<String> legalValues = collectLegalValues(context, radioButtonGroup);
super.decode(context, component, legalValues, "input_" + clientId);
}
}
private UIForm findSurroundingForm(UIComponent component) {
UIForm form = null;
UIComponent c = component;
while(c != null) {
if (c instanceof UIForm) {
form = (UIForm)c;
break;
}
c = c.getParent();
}
return form;
}
private List<String> collectLegalValues(FacesContext context, List<UIComponent> radioButtonGroup) {
List<String> legalValues = new ArrayList<String>();
for (UIComponent b: radioButtonGroup) {
Radiobutton r = (Radiobutton)b;
List<SelectItemAndComponent> options = SelectItemUtils.collectOptions(context, r);
if (options.size()>0) {
// traditional JSF approach using f:selectItem[s]
for (SelectItemAndComponent option:options) {
String o = null;
if (null != option.getSelectItem().getValue()) {
o = String.valueOf(option.getSelectItem().getValue());
}
legalValues.add(o);
}
} else {
// BootsFaces approach using b:radioButtons for each radiobutton item
legalValues.add(r.getItemValue());
}
}
return legalValues;
}
/**
* This methods generates the HTML code of the current b:radiobutton.
* <code>encodeBegin</code> generates the start of the component. After the, the JSF framework calls <code>encodeChildren()</code>
* to generate the HTML code between the beginning and the end of the component. For instance, in the case of a panel component
* the content of the panel is generated by <code>encodeChildren()</code>. After that, <code>encodeEnd()</code> is called
* to generate the rest of the HTML code.
* @param context the FacesContext.
* @param component the current b:radiobutton.
* @throws IOException thrown if something goes wrong when writing the HTML code.
*/
@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
if (!component.isRendered()) {
return;
}
Radiobutton radiobutton = (Radiobutton) component;
ResponseWriter rw = context.getResponseWriter();
String clientId = radiobutton.getClientId();
ValueExpression valueExpression = radiobutton.getValueExpression("value");
if (null == valueExpression) {
throw new FacesException("Radiobuttons always need a value. More precisely, the value attribute must be an EL expression pointing to an attribute of a JSF bean.");
}
String propertyName = valueExpression.getExpressionString();
Object beanValue = ELTools.evalAsObject(propertyName);
if (propertyName.startsWith("#{") && propertyName.endsWith("}")) {
propertyName=propertyName.substring(2, propertyName.length()-1).trim();
} else {
throw new FacesException("The value attribute of a radiobutton must be an EL expression.");
}
UIForm form = findSurroundingForm(component);
if (null == form) {
throw new FacesException("Radio buttons must be inside a form.");
}
String name = radiobutton.getName();
if (null == name) {
throw new FacesException("Please specify the 'name' attribute of the radio button.");
}
UIComponent radioButtonGroup = findComponentByName(form, name);
String radiobuttonGroupId = radioButtonGroup.getClientId(context);
RadioButtonInternalStateBean state = (RadioButtonInternalStateBean) ELTools.evalAsObject("#{radioButtonInternalStateBean}");
String key = "BF_generated_radiobuttonfield_"+radiobuttonGroupId;
if (!state.inputHasAlreadyBeenRendered(key)) {
rw.startElement("input", null);
rw.writeAttribute("id", "input_" + clientId, null);
rw.writeAttribute("name", name, null);
rw.writeAttribute("type", "hidden", null);
rw.writeAttribute("value", String.valueOf(beanValue), null);
AJAXRenderer.generateBootsFacesAJAXAndJavaScript(FacesContext.getCurrentInstance(), radiobutton, rw, false);
rw.endElement("input");
}
List<SelectItemAndComponent> options = SelectItemUtils.collectOptions(context, component);
if (options.size()>0) {
// traditional JSF approach using f:selectItem[s]
int counter=0;
for (SelectItemAndComponent option:options) {
generateASingleRadioButton(context, component, radiobutton, rw, propertyName, beanValue,
option.getSelectItem().getValue(),
option.getSelectItem().getLabel(), clientId+(counter++));
}
} else {
// BootsFaces approach using b:radioButtons for each radiobutton item
String itemValue = radiobutton.getItemValue();
String itemLabel = radiobutton.getItemLabel();
String itemId = clientId;
generateASingleRadioButton(context, component, radiobutton, rw, propertyName, beanValue, itemValue,
itemLabel, itemId);
}
}
private void generateASingleRadioButton(FacesContext context, UIComponent component, Radiobutton radiobutton,
ResponseWriter rw, String propertyName, Object beanValue, Object itemValue, String itemLabel, String itemId)
throws IOException {
rw.startElement("div", radiobutton);
rw.writeAttribute("id", itemId, null);
String styleClass=Responsive.getResponsiveStyleClass(radiobutton, false);
String styleClass2 = radiobutton.getStyleClass();
if (styleClass2!=null) {
styleClass = styleClass2 + styleClass;
}
styleClass=styleClass.trim() + " radio";
UIForm form = findSurroundingForm(component);
Radiobutton firstRadioButton = (Radiobutton)findComponentByName(form, radiobutton.getName());
String errorClass = getErrorAndRequiredClass(firstRadioButton, firstRadioButton.getClientId(context));
styleClass += " " + errorClass;
rw.writeAttribute("class", styleClass, null);
writeAttribute(rw, "style", radiobutton.getStyle());
Tooltip.generateTooltip(context, radiobutton, rw);
rw.startElement("label", component);
if (radiobutton.isDisabled()) {
rw.writeAttribute("class", "disabled", null);
}
rw.startElement("input", component);
if (!radiobutton.isDisabled()) {
boolean ajax = radiobutton.isAjax();
ajax |= null != ((IAJAXComponent) component).getUpdate();
String trigger = ajax ? ".trigger('click')" : "";
// Add onclick to input element to avoid event bubbling, if event is added on a label
rw.writeAttribute("onclick", "$('#input_" + firstRadioButton.getClientId(context).replace(":", "\\\\:") + "').val('" + itemValue + "')" + trigger, null);
}
rw.writeAttribute("type", "radio", null);
rw.writeAttribute("name", propertyName.replace('.','_'), null);
if (beanValue!=null) {
if (beanValue.toString().equals(itemValue)) {
rw.writeAttribute("checked", "checked", null);
}
} else if (itemValue==null){
rw.writeAttribute("checked", "checked", null);
}
if (radiobutton.isDisabled()) {
rw.writeAttribute("disabled", "true", null);
}
rw.endElement("input");
if (itemLabel!=null) {
rw.writeText(itemLabel, null);
}
encodeChildren(context, component);
rw.endElement("label");
rw.endElement("div");
Tooltip.activateTooltips(context, component, itemId);
}
/**
* This methods generates the HTML code of the current b:radiobutton.
* <code>encodeBegin</code> generates the start of the component. After the, the JSF framework calls <code>encodeChildren()</code>
* to generate the HTML code between the beginning and the end of the component. For instance, in the case of a panel component
* the content of the panel is generated by <code>encodeChildren()</code>. After that, <code>encodeEnd()</code> is called
* to generate the rest of the HTML code.
* @param context the FacesContext.
* @param component the current b:radiobutton.
* @throws IOException thrown if something goes wrong when writing the HTML code.
*/
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
// already rendered in encodeBegin
}
}