001    package org.apache.myfaces.tobago.component;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_IMMEDIATE;
023    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
024    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
025    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTED_INDEX;
026    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SWITCH_TYPE;
027    import org.apache.myfaces.tobago.ajax.api.AjaxComponent;
028    import org.apache.myfaces.tobago.ajax.api.AjaxUtils;
029    import org.apache.myfaces.tobago.event.TabChangeListener;
030    import org.apache.myfaces.tobago.event.TabChangeSource;
031    import org.apache.myfaces.tobago.event.TabChangeEvent;
032    
033    import javax.faces.component.UIComponent;
034    import javax.faces.context.FacesContext;
035    import javax.faces.el.EvaluationException;
036    import javax.faces.el.MethodBinding;
037    import javax.faces.el.ValueBinding;
038    import javax.faces.event.AbortProcessingException;
039    import javax.faces.event.FacesEvent;
040    import javax.faces.event.PhaseId;
041    import java.io.IOException;
042    import java.util.ArrayList;
043    import java.util.List;
044    
045    public class UITabGroup extends UIPanelBase implements TabChangeSource, AjaxComponent {
046    
047      private static final Log LOG = LogFactory.getLog(UITabGroup.class);
048    
049      public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.TabGroup";
050    
051      private Integer selectedIndex;
052      private int renderedIndex;
053      private String switchType;
054      private Boolean immediate;
055      private MethodBinding tabChangeListener = null;
056    
057      public static final String SWITCH_TYPE_CLIENT = "client";
058      public static final String SWITCH_TYPE_RELOAD_PAGE = "reloadPage";
059      public static final String SWITCH_TYPE_RELOAD_TAB = "reloadTab";
060    
061      @Override
062      public boolean getRendersChildren() {
063        return true;
064      }
065    
066      @Override
067      public void encodeBegin(FacesContext facesContext) throws IOException {
068        super.encodeBegin(facesContext);
069      }
070    
071      public void setImmediate(boolean immediate) {
072        this.immediate = immediate;
073      }
074    
075      public boolean isImmediate() {
076        if (immediate != null) {
077          return immediate;
078        }
079        ValueBinding vb = getValueBinding(ATTR_IMMEDIATE);
080        if (vb != null) {
081          return (!Boolean.FALSE.equals(vb.getValue(getFacesContext())));
082        } else {
083          return false;
084        }
085      }
086    
087      public void queueEvent(FacesEvent event) {
088        if (this == event.getSource()) {
089          if (isImmediate()) {
090            event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
091          } else {
092            event.setPhaseId(PhaseId.INVOKE_APPLICATION);
093          }
094        }
095        super.queueEvent(event);
096      }
097    
098      @Override
099      public void encodeChildren(FacesContext context)
100          throws IOException {
101      }
102    
103      @Override
104      public void encodeEnd(FacesContext facesContext) throws IOException {
105        resetTabLayout();
106        super.encodeEnd(facesContext);
107        setRenderedIndex(getSelectedIndex());
108      }
109    
110      private void resetTabLayout() {
111        for (UIComponent component : (List<UIComponent>) getChildren()) {
112          component.getAttributes().remove(ATTR_LAYOUT_WIDTH);
113          component.getAttributes().remove(ATTR_LAYOUT_HEIGHT);
114        }
115      }
116    
117      public UIPanelBase[] getTabs() {
118        List<UIPanelBase> tabs = new ArrayList<UIPanelBase>();
119        for (Object o : getChildren()) {
120          UIComponent kid = (UIComponent) o;
121          if (kid instanceof UIPanelBase) {
122            //if (kid.isRendered()) {
123            tabs.add((UIPanelBase) kid);
124            //}
125          } else {
126            LOG.error("Invalid component in UITabGroup: " + kid);
127          }
128        }
129        return tabs.toArray(new UIPanelBase[tabs.size()]);
130      }
131    
132      public UIPanelBase getActiveTab() {
133        return getTab(getSelectedIndex());
134      }
135    
136    
137      @Override
138      public void processDecodes(FacesContext context) {
139        if (!isClientType()) {
140    
141          if (context == null) {
142            throw new NullPointerException("context");
143          }
144          if (!isRendered()) {
145            return;
146          }
147          UIPanelBase renderedTab = getRenderedTab();
148          renderedTab.processDecodes(context);
149          try {
150            decode(context);
151          } catch (RuntimeException e) {
152            context.renderResponse();
153            throw e;
154          }
155        } else {
156          super.processDecodes(context);
157        }
158      }
159    
160      @Override
161      public void processValidators(FacesContext context) {
162        if (!isClientType()) {
163          if (context == null) {
164            throw new NullPointerException("context");
165          }
166          if (!isRendered()) {
167            return;
168          }
169          UIPanelBase renderedTab = getRenderedTab();
170          renderedTab.processValidators(context);
171        } else {
172          super.processValidators(context);
173        }
174      }
175    
176      @Override
177      public void processUpdates(FacesContext context) {
178        if (!isClientType()) {
179          if (context == null) {
180            throw new NullPointerException("context");
181          }
182          if (!isRendered()) {
183            return;
184          }
185          UIPanelBase renderedTab = getRenderedTab();
186          renderedTab.processUpdates(context);
187    
188        } else {
189          super.processUpdates(context);
190        }
191      }
192    
193      public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
194        super.broadcast(facesEvent);
195        if (facesEvent instanceof TabChangeEvent && facesEvent.getComponent() == this) {
196          Integer index = ((TabChangeEvent) facesEvent).getNewTabIndex();
197          ValueBinding vb = getValueBinding(ATTR_SELECTED_INDEX);
198          if (vb != null) {
199            vb.setValue(getFacesContext(), index);
200          } else {
201            setSelectedIndex(index);
202          }
203          MethodBinding tabChangeListenerBinding = getTabChangeListener();
204          if (tabChangeListenerBinding != null) {
205            try {
206              tabChangeListenerBinding.invoke(getFacesContext(), new Object[]{facesEvent});
207            } catch (EvaluationException e) {
208              Throwable cause = e.getCause();
209              if (cause != null && cause instanceof AbortProcessingException) {
210                throw (AbortProcessingException) cause;
211              } else {
212                throw e;
213              }
214            }
215          }
216          getFacesContext().renderResponse();
217        }
218      }
219    
220      public void setTabChangeListener(MethodBinding tabStateChangeListener) {
221        this.tabChangeListener = tabStateChangeListener;
222      }
223    
224      public MethodBinding getTabChangeListener() {
225        return tabChangeListener;
226      }
227    
228    
229      public void addTabChangeListener(TabChangeListener listener) {
230        if (LOG.isWarnEnabled() && isClientType()) {
231          LOG.warn("Adding TabChangeListener to Client side Tabgroup!");
232        }
233        addFacesListener(listener);
234      }
235    
236      private boolean isClientType() {
237        return (switchType == null || switchType.equals(SWITCH_TYPE_CLIENT));
238      }
239    
240      public void removeTabChangeListener(TabChangeListener listener) {
241        removeFacesListener(listener);
242      }
243    
244      public TabChangeListener[] getTabChangeListeners() {
245        return (TabChangeListener[]) getFacesListeners(TabChangeListener.class);
246      }
247    
248      public Object saveState(FacesContext context) {
249        Object[] state = new Object[6];
250        state[0] = super.saveState(context);
251        state[1] = renderedIndex;
252        state[2] = selectedIndex;
253        state[3] = saveAttachedState(context, tabChangeListener);
254        state[4] = switchType;
255        state[5] = immediate;
256        return state;
257      }
258    
259      public void restoreState(FacesContext context, Object state) {
260        Object[] values = (Object[]) state;
261        super.restoreState(context, values[0]);
262        renderedIndex = (Integer) values[1];
263        selectedIndex = (Integer) values[2];
264        tabChangeListener = (MethodBinding) restoreAttachedState(context, values[3]);
265        switchType = (String) values[4];
266        immediate = (Boolean) values[5];
267      }
268    
269      public void encodeAjax(FacesContext facesContext) throws IOException {
270        setRenderedIndex(getSelectedIndex());
271        AjaxUtils.encodeAjaxComponent(facesContext, this);
272      }
273    
274      public int getSelectedIndex() {
275        if (selectedIndex != null) {
276          return selectedIndex;
277        }
278        ValueBinding vb = getValueBinding(ATTR_SELECTED_INDEX);
279        if (vb != null) {
280          Integer value = (Integer) vb.getValue(getFacesContext());
281          if (value != null) {
282            return value;
283          }
284        }
285        return 0;
286      }
287    
288      public void setSelectedIndex(int selectedIndex) {
289        this.selectedIndex = selectedIndex;
290      }
291    
292      private void setRenderedIndex(int index) {
293        renderedIndex = index;
294      }
295    
296      public int getRenderedIndex() {
297        return renderedIndex;
298      }
299    
300      public String getSwitchType() {
301        String value = null;
302        if (switchType != null) {
303          value = switchType;
304        } else {
305          ValueBinding vb = getValueBinding(ATTR_SWITCH_TYPE);
306          if (vb != null) {
307            value = (String) vb.getValue(FacesContext.getCurrentInstance());
308          }
309        }
310    
311        if (SWITCH_TYPE_CLIENT.equals(value)
312            || SWITCH_TYPE_RELOAD_PAGE.equals(value)
313            || SWITCH_TYPE_RELOAD_TAB.equals(value)) {
314          return value;
315        } else if (value == null) {
316          // return default
317          return SWITCH_TYPE_CLIENT;
318        } else {
319          LOG.warn("Illegal value for attribute switchtype : " + switchType
320              + " Using default value " + SWITCH_TYPE_CLIENT);
321          return SWITCH_TYPE_CLIENT;
322        }
323      }
324    
325      public void setSwitchType(String switchType) {
326        this.switchType = switchType;
327      }
328    
329      private UIPanelBase getTab(int index) {
330        int i = 0;
331        for (UIComponent component : (List<UIComponent>) getChildren()) {
332          if (component instanceof UIPanelBase) {
333            if (i == index) {
334              return (UIPanelBase) component;
335            }
336            i++;
337          } else {
338            LOG.error("Invalid component in UITabGroup: " + component);
339          }
340        }
341        LOG.error("Found no component with index: " + index + " childCount: " + getChildCount());
342        return null;
343      }
344    
345      private UIPanelBase getRenderedTab() {
346        return getTab(getRenderedIndex());
347      }
348    }