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