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