View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.renderkit.html.scarborough.standard.tag;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Facets;
24  import org.apache.myfaces.tobago.component.RendererTypes;
25  import org.apache.myfaces.tobago.component.UIBox;
26  import org.apache.myfaces.tobago.component.UIButton;
27  import org.apache.myfaces.tobago.component.UICalendar;
28  import org.apache.myfaces.tobago.component.UIDate;
29  import org.apache.myfaces.tobago.component.UIDatePicker;
30  import org.apache.myfaces.tobago.component.UIGridLayout;
31  import org.apache.myfaces.tobago.component.UIPanel;
32  import org.apache.myfaces.tobago.component.UIPopup;
33  import org.apache.myfaces.tobago.component.UITime;
34  import org.apache.myfaces.tobago.event.PopupActionListener;
35  import org.apache.myfaces.tobago.internal.util.DateFormatUtils;
36  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
37  import org.apache.myfaces.tobago.layout.Measure;
38  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
39  import org.apache.myfaces.tobago.util.ComponentUtils;
40  import org.apache.myfaces.tobago.util.CreateComponentUtils;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  import javax.el.ELContext;
45  import javax.el.ExpressionFactory;
46  import javax.el.ValueExpression;
47  import javax.faces.component.UIComponent;
48  import javax.faces.context.FacesContext;
49  import javax.faces.convert.Converter;
50  import javax.faces.convert.DateTimeConverter;
51  import java.io.IOException;
52  import java.util.TimeZone;
53  
54  public class DatePickerRenderer extends LinkRenderer {
55  
56    private static final Logger LOG = LoggerFactory.getLogger(DatePickerRenderer.class);
57  
58    @Override
59    public void onComponentCreated(FacesContext facesContext, UIComponent component, UIComponent parent) {
60      final UIDatePicker picker = (UIDatePicker) component;
61      if (picker.getFor() == null) {
62        picker.setFor("@auto");
63      }
64      picker.setImmediate(true);
65      final String linkId = picker.getId();
66      picker.setImage("image/date.gif");
67  
68      // create popup
69      final String popupId = linkId != null ? linkId + "popup" : facesContext.getViewRoot().createUniqueId();
70      final UIPopup popup = (UIPopup) CreateComponentUtils.createComponent(
71          facesContext, UIPopup.COMPONENT_TYPE, RendererTypes.POPUP, popupId);
72      final UIGridLayout layoutOfPopup = (UIGridLayout) CreateComponentUtils.createComponent(
73          facesContext, UIGridLayout.COMPONENT_TYPE, RendererTypes.GRID_LAYOUT, "layoutPopup");
74      layoutOfPopup.setColumns("auto");
75      layoutOfPopup.setRows("auto");
76      popup.getFacets().put(Facets.LAYOUT, layoutOfPopup);
77      popup.getAttributes().put(Attributes.Z_INDEX, 10);
78      picker.getFacets().put(Facets.POPUP, popup);
79      picker.onComponentPopulated(facesContext, parent);
80      popup.setRendered(false);
81      popup.onComponentPopulated(facesContext, picker);
82  
83      final ExpressionFactory expressionFactory = facesContext.getApplication().getExpressionFactory();
84      final ELContext elContext = facesContext.getELContext();
85      popup.setValueExpression(Attributes.LEFT, expressionFactory.createValueExpression(
86          elContext, "#{tobagoContext.actionPosition.right.pixel + 5}", Object.class));
87      popup.setValueExpression(Attributes.TOP, expressionFactory.createValueExpression(
88          elContext, "#{tobagoContext.actionPosition.top.pixel}", Object.class));
89  
90      final UIBox box = (UIBox) CreateComponentUtils.createComponent(
91          facesContext, UIBox.COMPONENT_TYPE, RendererTypes.BOX, "box");
92      popup.getChildren().add(box);
93      box.setValueExpression(Attributes.LABEL, expressionFactory.createValueExpression(
94          elContext, "#{tobagoContext.resourceBundle.datePickerTitle}", String.class));
95      final UIGridLayout layoutOfBox = (UIGridLayout) CreateComponentUtils.createComponent(
96          facesContext, UIGridLayout.COMPONENT_TYPE, RendererTypes.GRID_LAYOUT, "layout");
97      box.getFacets().put(Facets.LAYOUT, layoutOfBox);
98      layoutOfBox.setRows("*;auto;auto");
99  
100     final UICalendar calendar = (UICalendar) CreateComponentUtils.createComponent(
101         facesContext, UICalendar.COMPONENT_TYPE, RendererTypes.CALENDAR, "calendar");
102     box.getChildren().add(calendar);
103 
104      // fixme: should work automatically from the layout manager
105     final Measure width = getResourceManager().getThemeMeasure(facesContext, calendar, "minimumWidth");
106     layoutOfBox.setColumns(width.serialize());
107 
108     // add time input
109     final UIPanel timePanel = (UIPanel) CreateComponentUtils.createComponent(
110         facesContext, UIPanel.COMPONENT_TYPE, RendererTypes.PANEL, "timePanel");
111     box.getChildren().add(timePanel);
112     final UIGridLayout layoutOfTime = (UIGridLayout) CreateComponentUtils.createComponent(
113         facesContext, UIGridLayout.COMPONENT_TYPE, RendererTypes.GRID_LAYOUT, "timePanelLayout");
114     timePanel.getFacets().put(Facets.LAYOUT, layoutOfTime);
115     layoutOfTime.setColumns("1*;auto;1*");
116     final UIPanel cell1 = (UIPanel) CreateComponentUtils.createComponent(
117         facesContext, UIPanel.COMPONENT_TYPE, RendererTypes.PANEL, "cell1");
118     cell1.onComponentPopulated(facesContext, parent);
119     timePanel.getChildren().add(cell1);
120 
121     final UITime time = (UITime) CreateComponentUtils.createComponent(
122         facesContext, UITime.COMPONENT_TYPE, RendererTypes.TIME, "time");
123     timePanel.getChildren().add(time);
124 
125     final UIPanel cell2 = (UIPanel) CreateComponentUtils.createComponent(
126         facesContext, UIPanel.COMPONENT_TYPE, RendererTypes.PANEL, "cell2");
127     cell2.onComponentPopulated(facesContext, parent);
128     timePanel.getChildren().add(cell2);
129 
130     timePanel.onComponentPopulated(facesContext, parent);
131 
132 
133     final UIPanel buttonPanel = (UIPanel) CreateComponentUtils.createComponent(
134         facesContext, UIPanel.COMPONENT_TYPE, RendererTypes.PANEL, "buttonPanel");
135     final UIGridLayout layoutOfButtons = (UIGridLayout) CreateComponentUtils.createComponent(
136         facesContext, UIGridLayout.COMPONENT_TYPE, RendererTypes.GRID_LAYOUT, "buttonPanelLayout");
137     buttonPanel.setLayoutManager(layoutOfButtons);
138     layoutOfButtons.setColumns("*;*");
139     layoutOfButtons.setRows("auto");
140 
141     box.getChildren().add(buttonPanel);
142     box.onComponentPopulated(facesContext, parent);
143 
144     final UIButton okButton = (UIButton) CreateComponentUtils.createComponent(
145         facesContext, UIButton.COMPONENT_TYPE, RendererTypes.BUTTON, "ok");
146     buttonPanel.getChildren().add(okButton);
147     okButton.setValueExpression(Attributes.LABEL, expressionFactory.createValueExpression(
148         elContext, "#{tobagoContext.resourceBundle.datePickerOk}", String.class));
149     ComponentUtils.putDataAttributeWithPrefix(okButton, DataAttributes.DATE_PICKER_OK, true);
150     okButton.getAttributes().put(Attributes.POPUP_CLOSE, "afterSubmit");
151     okButton.setOmit(true);
152     final UIButton cancelButton = (UIButton) CreateComponentUtils.createComponent(
153         facesContext, UIButton.COMPONENT_TYPE, RendererTypes.BUTTON, "cancel");
154     buttonPanel.getChildren().add(cancelButton);
155     cancelButton.setValueExpression(Attributes.LABEL, expressionFactory.createValueExpression(
156         elContext, "#{tobagoContext.resourceBundle.datePickerCancel}", String.class));
157     cancelButton.getAttributes().put(Attributes.POPUP_CLOSE, "immediate");
158 
159     buttonPanel.onComponentPopulated(facesContext, parent);
160   }
161 
162   @Override
163   public void prepareRender(FacesContext facesContext, UIComponent component) throws IOException {
164     UIDatePicker picker = (UIDatePicker) component;
165     // todo: use Measure instead of int
166     // todo: call setWidth ???
167     picker.getAttributes().put(
168         Attributes.LAYOUT_WIDTH,
169         getResourceManager().getThemeMeasure(facesContext, picker, "pickerWidth").getPixel());
170 
171     FacesContextUtils.addPopup(facesContext, (UIPopup) picker.getFacets().get(Facets.POPUP));
172 
173     super.prepareRender(facesContext, picker);
174   }
175 
176   @Override
177   public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException {
178     UIDatePicker picker = (UIDatePicker) component;
179     UIDate dateInput = (UIDate) picker.getForComponent();
180     if (dateInput == null) {
181       LOG.error("The required UIDate component wasn't found for component id='" + component.getId());
182       return;
183     }
184     // this can't be done in "onComponentPopulated()" of the picker, it seems to be to early
185     final ValueExpression readonlyExpression = dateInput.getValueExpression(Attributes.READONLY);
186     if (readonlyExpression != null) {
187       picker.setValueExpression(Attributes.DISABLED, readonlyExpression);
188     } else {
189       final ValueExpression disabledExpression = dateInput.getValueExpression(Attributes.DISABLED);
190       if (disabledExpression != null) {
191         picker.setValueExpression(Attributes.DISABLED, disabledExpression);
192       } else {
193         picker.setDisabled(dateInput.isReadonly() || dateInput.isDisabled());
194       }
195     }
196     UIPopup popup = (UIPopup) picker.getFacets().get(Facets.POPUP);
197     picker.setRenderedPartially(new String[] {popup.getId()});
198     Converter converter = getConverter(facesContext, dateInput);
199     String converterPattern = "yyyy-MM-dd"; // from tobago-calendar.js  initCalendarParse
200     if (converter instanceof DateTimeConverter) {
201       converterPattern = DateFormatUtils.findPattern((DateTimeConverter) converter);
202     } else {
203       // LOG.warn("Converter for DateRenderer is not instance of DateTimeConverter. Using default Pattern "
204       //    + converterPattern);
205     }
206 
207     applyConverterPattern(facesContext, popup, converterPattern);
208 
209     if (!ComponentUtils.containsPopupActionListener(picker)) {
210       picker.addActionListener(new PopupActionListener(popup.getId()));
211     }
212     super.encodeBegin(facesContext, component);
213   }
214 
215   private void applyConverterPattern(FacesContext facesContext, UIPopup popup, String converterPattern) {
216     UIComponent box = popup.getChildren().get(0);
217     UIComponent timePanel = box.findComponent("timePanel");
218     if (converterPattern != null && (converterPattern.indexOf('h') > -1 || converterPattern.indexOf('H') > -1)) {
219       UITime time = (UITime) timePanel.findComponent("time");
220       DateTimeConverter dateTimeConverter
221           = (DateTimeConverter) facesContext.getApplication().createConverter(DateTimeConverter.CONVERTER_ID);
222       if (converterPattern.indexOf('s') > -1) {
223         dateTimeConverter.setPattern("HH:mm:ss");
224       } else {
225         dateTimeConverter.setPattern("HH:mm");
226       }
227       dateTimeConverter.setTimeZone(TimeZone.getDefault());
228       time.setConverter(dateTimeConverter);
229     } else {
230       timePanel.setRendered(false);
231     }
232   }
233 
234   @Override
235   public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException {
236     UIDatePicker link = (UIDatePicker) component;
237     UIDate dateInput = (UIDate) link.getForComponent();
238     if (dateInput != null) {
239       super.encodeEnd(facesContext, component);
240     } else {
241       LOG.error("The required UIDate component wasn't found for component id='" + component.getId());
242     }
243   }
244 }