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  package org.apache.myfaces.custom.calendar;
20  
21  import java.io.IOException;
22  import java.text.DateFormat;
23  import java.text.DateFormatSymbols;
24  import java.text.ParseException;
25  import java.text.SimpleDateFormat;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  
32  import javax.faces.application.Application;
33  import javax.faces.application.FacesMessage;
34  import javax.faces.application.Resource;
35  import javax.faces.component.EditableValueHolder;
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIInput;
38  import javax.faces.component.UIParameter;
39  import javax.faces.component.behavior.ClientBehavior;
40  import javax.faces.component.html.HtmlCommandLink;
41  import javax.faces.component.html.HtmlOutputText;
42  import javax.faces.context.FacesContext;
43  import javax.faces.context.ResponseWriter;
44  import javax.faces.convert.Converter;
45  import javax.faces.convert.ConverterException;
46  import javax.faces.convert.DateTimeConverter;
47  import javax.faces.event.ComponentSystemEvent;
48  import javax.faces.event.ComponentSystemEventListener;
49  import javax.faces.event.ListenerFor;
50  
51  import org.apache.commons.lang.StringEscapeUtils;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  import org.apache.myfaces.component.LibraryLocationAware;
55  import org.apache.myfaces.component.UserRoleUtils;
56  import org.apache.myfaces.custom.inputTextHelp.HtmlInputTextHelp;
57  import org.apache.myfaces.dateformat.SimpleDateFormatter;
58  import org.apache.myfaces.renderkit.html.util.AddResource;
59  import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
60  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
61  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
62  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
63  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRenderer;
64  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
65  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.JavascriptUtils;
66  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.ResourceUtils;
67  import org.apache.myfaces.shared_tomahawk.util.MessageUtils;
68  import org.apache.myfaces.tomahawk.application.PreRenderViewAddResourceEvent;
69  import org.apache.myfaces.tomahawk.util.Constants;
70  import org.apache.myfaces.tomahawk.util.TomahawkResourceUtils;
71  
72  /**
73   * Render a "calendar" which the user can use to choose a specific day.
74   * <p>
75   * This renderer behaves quite differently for "inline" and "popup" calendars.
76   * <p>
77   * When inline, this component automatically creates child components (text and
78   * link components) to represent all the dates, scrollers, etc. within itself and
79   * renders those children. Clicking on any link in the component causes the
80   * surrounding form to immediately submit.
81   * <p>
82   * When popup, this component just renders an empty span with the component id,
83   * and a dozen or so lines of javascript which create an instance of a
84   * "tomahawk calendar object", set its properties from the properties on the
85   * associated HtmlInputCalendar component, and then invoke it. That javascript
86   * object then dynamically builds DOM objects and attaches them to the empty span.
87   * This component also renders a button or image that toggles the visibility of
88   * that empty span, thereby making the popup appear and disappear.
89   * <p>
90   * Note that the two ways of generating the calendar use totally different code;
91   * one implementation is here and the other is in a javascript resource file. For
92   * obvious reasons the appearance of the two calendars should be similar, so if a
93   * feature is added in one place it is recommended that the other be updated also. 
94   * <p>
95   * The behaviour of both of the Calendar objects varies depending upon the 
96   * "current locale". This is derived from getViewRoot().getLocale(), which is
97   * normally set according to the browser preferences; users whose browsers
98   * have "english" as the preferred language will get the "en" locale, while
99   * users with "deutsch" as the preferred language will get the "de" locale.
100  * One specific example is the "first day of week" (ie the day displayed at
101  * the left of each week row); this is "sunday" for the english locale, and
102  * "monday" for the german locale. There is currently no way for the
103  * calendar component to be configured to force a specific firstDayOfWeek
104  * to be used for all users.
105  * <p>
106  * 
107  * @JSFRenderer
108  *   renderKitId = "HTML_BASIC" 
109  *   family = "javax.faces.Input"
110  *   type = "org.apache.myfaces.Calendar"
111  * 
112  * @author Martin Marinschek (latest modification by $Author: lu4242 $)
113  * @version $Revision: 784635 $ $Date: 2009-06-14 18:49:00 -0500 (dom, 14 jun 2009) $
114  */
115 @ListenerFor(systemEventClass=PreRenderViewAddResourceEvent.class)
116 public class HtmlCalendarRenderer
117         extends HtmlRenderer implements ComponentSystemEventListener
118 {
119     private final Log log = LogFactory.getLog(HtmlCalendarRenderer.class);
120 
121     private static final String JAVASCRIPT_ENCODED = "org.apache.myfaces.calendar.JAVASCRIPT_ENCODED";
122     private static final String JAVASCRIPT_ENCODED_JSF2 = "org.apache.myfaces.calendar.JAVASCRIPT_ENCODED_JSF2";
123 
124     // TODO: move this to HtmlRendererUtils in shared
125     private static final String RESOURCE_NONE = "none";
126 
127     public void processEvent(ComponentSystemEvent event)
128     {
129         HtmlInputCalendar inputCalendar = (HtmlInputCalendar) event.getComponent();
130         
131         if (inputCalendar.isRenderAsPopup() && inputCalendar.isAddResources())
132         {
133             FacesContext facesContext = FacesContext.getCurrentInstance();
134             if (!facesContext.getAttributes().containsKey(JAVASCRIPT_ENCODED_JSF2))
135             {
136                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.inputTextHelp", "inputTextHelp.js");
137             }
138             addScriptAndCSSResourcesWithJSF2ResourceAPI(facesContext, inputCalendar);
139         }
140     }
141 
142     public void encodeEnd(FacesContext facesContext, UIComponent component)
143             throws IOException
144     {
145         RendererUtils.checkParamValidity(facesContext, component, HtmlInputCalendar.class);
146 
147         HtmlInputCalendar inputCalendar = (HtmlInputCalendar) component;
148 
149         Locale currentLocale = facesContext.getViewRoot().getLocale();
150         Map<String, List<ClientBehavior>> behaviors = inputCalendar.getClientBehaviors();
151         if (!behaviors.isEmpty())
152         {
153             ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, facesContext.getResponseWriter());
154         }
155         
156         log.debug("current locale:" + currentLocale.toString());
157 
158         String textValue;
159         
160         Converter converter = inputCalendar.getConverter();        
161         Object submittedValue = inputCalendar.getSubmittedValue();        
162         
163         Date value;
164 
165         if (submittedValue != null)
166         {
167             //Don't need to convert anything, the textValue is the same as the submittedValue
168             textValue = (String) submittedValue;
169             
170             if(textValue ==null || textValue.trim().length()==0 || textValue.equals(getHelperString(inputCalendar)))
171             {
172                 value = null;
173             }
174             else
175             {
176                 try
177                 {
178                     String formatStr = CalendarDateTimeConverter.createJSPopupFormat(facesContext, inputCalendar.getPopupDateFormat());
179                     Calendar timeKeeper = Calendar.getInstance(currentLocale);
180                     int firstDayOfWeek = timeKeeper.getFirstDayOfWeek() - 1;
181                     org.apache.myfaces.dateformat.DateFormatSymbols symbols = new org.apache.myfaces.dateformat.DateFormatSymbols(currentLocale);
182     
183                     SimpleDateFormatter dateFormat = new SimpleDateFormatter(formatStr, symbols, firstDayOfWeek);
184                     value = dateFormat.parse(textValue);
185                 }
186                 catch (IllegalArgumentException illegalArgumentException)
187                 {
188                     value = null;
189                 }
190             }
191         }
192         else
193         {
194             if (converter == null)
195             {
196                 CalendarDateTimeConverter defaultConverter = new CalendarDateTimeConverter();
197                 
198                 value = (Date) getDateBusinessConverter(inputCalendar).getDateValue(facesContext, component, inputCalendar.getValue());
199 
200                 textValue = defaultConverter.getAsString(facesContext, inputCalendar, value);
201             }
202             else
203             {
204                 Object componentValue = null;
205                 boolean usedComponentValue = false;
206                 //Use converter to retrieve the value.
207                 if(converter instanceof DateConverter)
208                 {
209                     value = ((DateConverter) converter).getAsDate(facesContext, inputCalendar);
210                 }
211                 else
212                 {
213                     componentValue = inputCalendar.getValue();
214                     if (componentValue instanceof Date)
215                     {
216                         value = (Date) componentValue;
217                     }
218                     else
219                     {
220                         usedComponentValue = true;
221                         value = null;
222                     }
223                 }
224                 textValue = converter.getAsString(facesContext, inputCalendar, usedComponentValue ? componentValue : value);
225             }
226         }
227         /*        
228         try
229         {
230             // value = RendererUtils.getDateValue(inputCalendar);
231 
232             Converter converter = getConverter(inputCalendar);
233             if (converter instanceof DateConverter)
234             {
235                 value = ((DateConverter) converter).getAsDate(facesContext, component);
236             }
237             else
238             {
239                 //value = RendererUtils.getDateValue(inputCalendar);
240                 Object objectValue = RendererUtils.getObjectValue(component);
241                 if (objectValue == null || objectValue instanceof Date)
242                 {
243                     value = (Date) objectValue;
244                 }
245                 else
246                 {
247                     //Use Converter.getAsString and convert to date using 
248                     String stringValue = converter.getAsString(facesContext, component, objectValue);
249 
250                     if(stringValue ==null || stringValue.trim().length()==0 ||stringValue.equals(getHelperString(inputCalendar)))
251                     {
252                         value = null;
253                     }
254                     else
255                     {
256                         String formatStr = CalendarDateTimeConverter.createJSPopupFormat(facesContext, inputCalendar.getPopupDateFormat());
257                         Calendar timeKeeper = Calendar.getInstance(currentLocale);
258                         int firstDayOfWeek = timeKeeper.getFirstDayOfWeek() - 1;
259                         org.apache.myfaces.dateformat.DateFormatSymbols symbols = new org.apache.myfaces.dateformat.DateFormatSymbols(currentLocale);
260     
261                         SimpleDateFormatter dateFormat = new SimpleDateFormatter(formatStr, symbols, firstDayOfWeek);
262                         value = dateFormat.parse(stringValue);
263                     }
264                 }
265             }
266         }
267         catch (IllegalArgumentException illegalArgumentException)
268         {
269             value = null;
270         }
271         */
272 
273         Calendar timeKeeper = Calendar.getInstance(currentLocale);
274         timeKeeper.setTime(value!=null?value:new Date());
275 
276         DateFormatSymbols symbols = new DateFormatSymbols(currentLocale);
277 
278         if(inputCalendar.isRenderAsPopup())
279         {
280             renderPopup(facesContext, inputCalendar, textValue, timeKeeper, symbols);
281         }
282         else
283         {
284             renderInline(facesContext, inputCalendar, timeKeeper, symbols);
285         }
286 
287         component.getChildren().removeAll(component.getChildren());
288     }
289 
290     private void renderPopup(
291             FacesContext facesContext, 
292             HtmlInputCalendar inputCalendar,
293             String value,
294             Calendar timeKeeper,
295             DateFormatSymbols symbols) throws IOException
296     {
297         if(inputCalendar.isAddResources())
298             addScriptAndCSSResources(facesContext, inputCalendar);
299 
300          // Check for an enclosed converter:
301          UIInput uiInput = (UIInput) inputCalendar;
302          Converter converter = uiInput.getConverter();
303          String dateFormat = null;
304          if (converter != null && converter instanceof DateTimeConverter) {
305              dateFormat = ((DateTimeConverter) converter).getPattern();
306          }
307          if (dateFormat == null) {
308              dateFormat = CalendarDateTimeConverter.createJSPopupFormat(facesContext,
309                                                                         inputCalendar.getPopupDateFormat());
310          }
311 
312         Application application = facesContext.getApplication();
313 
314         HtmlInputTextHelp inputText = getOrCreateInputTextChild(inputCalendar, application);
315 
316         RendererUtils.copyHtmlInputTextAttributes(inputCalendar, inputText);
317         
318         inputText.setId(inputCalendar.getId()+"_input");
319         
320         boolean forceId = RendererUtils.getBooleanValue(
321             JSFAttr.FORCE_ID_ATTR,
322             inputCalendar.getAttributes().get(JSFAttr.FORCE_ID_ATTR),
323             false);
324         if (forceId) {
325             inputText.getAttributes().put(JSFAttr.FORCE_ID_ATTR, Boolean.TRUE);
326         }
327 
328         inputText.setConverter(null); // value for this transient component will already be converted
329         inputText.setTransient(true);
330         inputText.setHelpText(inputCalendar.getHelpText());
331         inputText.setSelectText(true);
332 
333         inputText.setValue(value);
334         /*
335         if (value == null && inputCalendar.getSubmittedValue() != null)
336         {
337             inputText.setValue(inputCalendar.getSubmittedValue());
338         }
339         else
340         {
341             inputText.setValue(getConverter(inputCalendar).getAsString(
342                     facesContext,inputCalendar,value));
343         }*/
344         inputText.setDisabled(inputCalendar.isDisabled());
345         inputText.setReadonly(inputCalendar.isReadonly());
346         inputText.setEnabledOnUserRole(inputCalendar.getEnabledOnUserRole());
347         inputText.setVisibleOnUserRole(inputCalendar.getVisibleOnUserRole());
348 
349         //This is where two components with the same id are in the tree,
350         //so make sure that during the rendering the id is unique.
351 
352         //inputCalendar.setId(inputCalendar.getId()+"tempId");
353 
354         inputCalendar.getChildren().add(inputText);
355         
356         //Reset client id to ensure proper operation
357         inputText.setId(inputText.getId());
358         
359         inputText.setName(inputCalendar.getClientId(facesContext));
360         
361         inputText.setTargetClientId(inputCalendar.getClientId(facesContext));
362         
363         ResponseWriter writer = facesContext.getResponseWriter();
364         
365         writer.startElement(HTML.SPAN_ELEM, inputCalendar);
366         
367         writer.writeAttribute(HTML.ID_ATTR, inputCalendar.getClientId(facesContext), null);
368 
369         RendererUtils.renderChild(facesContext, inputText);
370 
371         // Remove inputText moved to the end of this method. 
372         // inputCalendar.getChildren().remove(inputText);
373         
374         //Set back the correct id to the input calendar
375         //inputCalendar.setId(inputText.getId());
376 
377         writer.startElement(HTML.SPAN_ELEM,inputCalendar);
378         writer.writeAttribute(HTML.ID_ATTR,inputCalendar.getClientId(facesContext)+"Span",
379                               JSFAttr.ID_ATTR);
380         writer.endElement(HTML.SPAN_ELEM);
381 
382         if (!isDisabled(facesContext, inputCalendar) && !inputCalendar.isReadonly())
383         {
384             writer.startElement(HTML.SCRIPT_ELEM, inputCalendar);
385             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR,HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT,null);
386 
387             String calendarVar = JavascriptUtils.getValidJavascriptName(
388                     inputCalendar.getClientId(facesContext)+"CalendarVar",false);
389 
390             writer.writeText(calendarVar+"=new org_apache_myfaces_PopupCalendar();\n",null);
391             writer.writeText(getLocalizedLanguageScript(facesContext,symbols,
392                                                         timeKeeper.getFirstDayOfWeek(),inputCalendar,calendarVar)+"\n",null);
393             // pass the selectMode attribute
394             StringBuffer script = new StringBuffer();
395             setStringVariable(script, calendarVar +".initData.selectMode",inputCalendar.getPopupSelectMode());
396             writer.writeText(script.toString(), null);
397 
398             writer.writeText(calendarVar+".init(document.getElementById('"+
399                              inputCalendar.getClientId(facesContext)+"Span"+"'));\n",null);
400             writer.endElement(HTML.SCRIPT_ELEM);
401             if(!inputCalendar.isDisplayValueOnly())
402             {
403                 getScriptBtn(writer, facesContext, inputCalendar,
404                                               dateFormat,inputCalendar.getPopupButtonString(), new FunctionCallProvider(){
405                     public String getFunctionCall(FacesContext facesContext, UIComponent uiComponent, String dateFormat)
406                     {
407                         String clientId = uiComponent.getClientId(facesContext);
408                         String inputClientId = null;
409                         if (Boolean.TRUE.equals(uiComponent.getAttributes().get(JSFAttr.FORCE_ID_ATTR)))
410                         {
411                             if (uiComponent.getChildCount() > 0)
412                             {
413                                 for (int i = 0, size = uiComponent.getChildCount(); i < size ; i++)
414                                 {
415                                     UIComponent child = uiComponent.getChildren().get(i);
416                                     if (child instanceof HtmlInputTextHelp && child.getId() != null && child.getId().endsWith("_input"))
417                                     {
418                                         inputClientId = child.getClientId();
419                                     }
420                                 }
421                             }
422                             if (inputClientId == null)
423                             {
424                                 String oldId = uiComponent.getId();
425                                 uiComponent.setId(uiComponent.getId()+"_input");
426                                 inputClientId = uiComponent.getClientId();
427                                 uiComponent.setId(oldId);
428                             }
429                         }
430                         else
431                         {
432                             inputClientId = clientId;
433                         }
434 
435                         String clientVar = JavascriptUtils.getValidJavascriptName(clientId+"CalendarVar",true);
436 
437                         return clientVar+"._popUpCalendar(this,document.getElementById('"+inputClientId+"'),'"+dateFormat+"')";
438                     }
439                 });
440             }
441         }
442 
443         writer.endElement(HTML.SPAN_ELEM);
444 
445         inputCalendar.getChildren().remove(inputText);
446     }
447 
448     private void renderInline(
449             FacesContext facesContext, 
450             HtmlInputCalendar inputCalendar,
451             Calendar timeKeeper,
452             DateFormatSymbols symbols) throws IOException
453     {
454         String[] weekdays = mapShortWeekdays(symbols);
455         String[] months = mapMonths(symbols);
456 
457         int lastDayInMonth = timeKeeper.getActualMaximum(Calendar.DAY_OF_MONTH);
458 
459         int currentDay = timeKeeper.get(Calendar.DAY_OF_MONTH);
460 
461         if (currentDay > lastDayInMonth)
462             currentDay = lastDayInMonth;
463 
464         timeKeeper.set(Calendar.DAY_OF_MONTH, 1);
465 
466         int weekDayOfFirstDayOfMonth = mapCalendarDayToCommonDay(timeKeeper.get(Calendar.DAY_OF_WEEK));
467 
468         int weekStartsAtDayIndex = mapCalendarDayToCommonDay(timeKeeper.getFirstDayOfWeek());
469 
470         ResponseWriter writer = facesContext.getResponseWriter();
471 
472         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
473         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
474 
475         writer.startElement(HTML.TABLE_ELEM, inputCalendar);
476         HtmlRendererUtils.renderHTMLAttributes(writer, inputCalendar, HTML.UNIVERSAL_ATTRIBUTES);
477 
478         Map<String, List<ClientBehavior>> behaviors = inputCalendar.getClientBehaviors();
479         
480         if (behaviors != null && !behaviors.isEmpty())
481         {
482             writer.writeAttribute(HTML.ID_ATTR, inputCalendar.getClientId(facesContext),null);
483             HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, inputCalendar, behaviors);
484             HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(facesContext, writer, inputCalendar, behaviors);
485         }
486         else
487         {
488             HtmlRendererUtils.writeIdIfNecessary(writer, inputCalendar, facesContext);
489             HtmlRendererUtils.renderHTMLAttributes(writer, inputCalendar, HTML.EVENT_HANDLER_ATTRIBUTES);
490             HtmlRendererUtils.renderHTMLAttributes(writer, inputCalendar, HTML.COMMON_FIELD_EVENT_ATTRIBUTES_WITHOUT_ONSELECT_AND_ONCHANGE);
491         }
492         writer.flush();
493 
494         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
495 
496         writer.startElement(HTML.TR_ELEM, inputCalendar);
497 
498         if(inputCalendar.getMonthYearRowClass() != null)
499             writer.writeAttribute(HTML.CLASS_ATTR, inputCalendar.getMonthYearRowClass(), null);
500 
501         writeMonthYearHeader(facesContext, writer, inputCalendar, timeKeeper,
502                              currentDay, weekdays, months);
503 
504         writer.endElement(HTML.TR_ELEM);
505 
506         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
507 
508         writer.startElement(HTML.TR_ELEM, inputCalendar);
509 
510         if(inputCalendar.getWeekRowClass() != null)
511             writer.writeAttribute(HTML.CLASS_ATTR, inputCalendar.getWeekRowClass(), null);
512 
513         writeWeekDayNameHeader(weekStartsAtDayIndex, weekdays,
514                                facesContext, writer, inputCalendar);
515 
516         writer.endElement(HTML.TR_ELEM);
517 
518         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
519 
520         writeDays(facesContext, writer, inputCalendar, timeKeeper,
521                   currentDay, weekStartsAtDayIndex, weekDayOfFirstDayOfMonth,
522                   lastDayInMonth, weekdays);
523 
524         writer.endElement(HTML.TABLE_ELEM);
525     }
526 
527     private HtmlInputTextHelp getOrCreateInputTextChild(HtmlInputCalendar inputCalendar, Application application)
528     {
529         HtmlInputTextHelp inputText = null;
530 
531         List li = inputCalendar.getChildren();
532 
533         for (int i = 0; i < li.size(); i++)
534         {
535             UIComponent uiComponent = (UIComponent) li.get(i);
536 
537             if(uiComponent instanceof HtmlInputTextHelp)
538             {
539                 inputText = (HtmlInputTextHelp) uiComponent;
540                 break;
541             }
542         }
543 
544         if(inputText == null)
545         {
546             inputText = (HtmlInputTextHelp) application.createComponent(HtmlInputTextHelp.COMPONENT_TYPE);
547             
548             //Copy all client behaviors 
549             for (Map.Entry<String,List<ClientBehavior>> entry : inputCalendar.getClientBehaviors().entrySet())
550             {
551                 List<ClientBehavior> list = entry.getValue();
552                 if (list != null && !list.isEmpty())
553                 {
554                     for (ClientBehavior cb : list)
555                     {
556                         inputText.addClientBehavior(entry.getKey(), cb);
557                     }
558                 }
559             }
560         }
561         return inputText;
562     }
563 
564     /**
565      * Used by the x:inputDate renderer : HTMLDateRenderer
566      */
567     static public void addScriptAndCSSResources(FacesContext facesContext, UIComponent component){
568         // Check to see if javascript has already been written (which could happen if more than one calendar
569         // on the same page). Note that this means that if two calendar controls in the same page have
570         // different styleLocation or scriptLocation settings then all but the first one get ignored.
571         // Having different settings for calendars on the same page would be unusual, so ignore this
572         // for now..
573         if (facesContext.getAttributes().containsKey(JAVASCRIPT_ENCODED) || 
574             facesContext.getAttributes().containsKey(JAVASCRIPT_ENCODED_JSF2))
575         {
576             return;
577         }
578 
579         AddResource addresource = AddResourceFactory.getInstance(facesContext);
580         // Add the javascript and CSS pages
581 
582         String styleLocation = HtmlRendererUtils.getStyleLocation(component);
583 
584         if(styleLocation==null)
585         {
586             /*
587             String styleLibrary = (String) component.getAttributes().get(LibraryLocationAware.STYLE_LIBRARY_ATTR);
588             if (styleLibrary == null)
589             {
590                 //addresource.addStyleSheet(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "WH/theme.css");
591                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, "oam.custom.calendar.WH", "theme.css");
592                 //addresource.addStyleSheet(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "DB/theme.css");
593                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, "oam.custom.calendar.DB", "theme.css");
594             }
595             else
596             {
597                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, styleLocation, "theme.css");
598             }*/
599         }
600         else if (!RESOURCE_NONE.equals(styleLocation))
601         {
602             addresource.addStyleSheet(facesContext, AddResource.HEADER_BEGIN, styleLocation+"/theme.css");
603         }
604         else
605         {
606             // output nothing; presumably the page directly references the necessary stylesheet
607         }
608 
609         String javascriptLocation = HtmlRendererUtils.getJavascriptLocation(component);
610 
611         if(javascriptLocation==null)
612         {
613             /*
614             String javascriptLibrary = (String) component.getAttributes().get(LibraryLocationAware.JAVASCRIPT_LIBRARY_ATTR);
615             if (javascriptLibrary == null)
616             {
617                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, PrototypeResourceLoader.class, "prototype.js");
618                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.prototype", "prototype.js");
619                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "date.js");
620                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.calendar", "date.js");
621                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "popcalendar.js");
622                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.calendar", "popcalendar.js");
623             }
624             else
625             {
626                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "prototype.js");
627                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "date.js");
628                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "popcalendar.js");
629 
630             }*/
631         }
632         else if (!RESOURCE_NONE.equals(javascriptLocation))
633         {
634             addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, javascriptLocation+ "/prototype.js");
635             addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, javascriptLocation+ "/date.js");
636             addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, javascriptLocation+ "/popcalendar.js");
637         }
638         else
639         {
640             // output nothing; presumably the page directly references the necessary javascript
641         }
642 
643         facesContext.getAttributes().put(JAVASCRIPT_ENCODED, Boolean.TRUE);
644     }
645     
646     static public void addScriptAndCSSResourcesWithJSF2ResourceAPI(FacesContext facesContext, UIComponent component){
647         // Check to see if javascript has already been written (which could happen if more than one calendar
648         // on the same page). Note that this means that if two calendar controls in the same page have
649         // different styleLocation or scriptLocation settings then all but the first one get ignored.
650         // Having different settings for calendars on the same page would be unusual, so ignore this
651         // for now..
652         if (facesContext.getAttributes().containsKey(JAVASCRIPT_ENCODED_JSF2))
653         {
654             return;
655         }
656 
657         // Add the javascript and CSS pages
658 
659         String styleLocation = HtmlRendererUtils.getStyleLocation(component);
660 
661         if(styleLocation==null)
662         {
663             String styleLibrary = (String) component.getAttributes().get(LibraryLocationAware.STYLE_LIBRARY_ATTR);
664             if (styleLibrary == null)
665             {
666                 //addresource.addStyleSheet(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "WH/theme.css");
667                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, "oam.custom.calendar.WH", "theme.css");
668                 //addresource.addStyleSheet(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "DB/theme.css");
669                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, "oam.custom.calendar.DB", "theme.css");
670             }
671             else
672             {
673                 TomahawkResourceUtils.addOutputStylesheetResource(facesContext, styleLocation, "theme.css");
674             }
675         }
676 
677         String javascriptLocation = HtmlRendererUtils.getJavascriptLocation(component);
678 
679         if(javascriptLocation==null)
680         {
681             String javascriptLibrary = (String) component.getAttributes().get(LibraryLocationAware.JAVASCRIPT_LIBRARY_ATTR);
682             if (javascriptLibrary == null)
683             {
684                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, PrototypeResourceLoader.class, "prototype.js");
685                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.prototype", "prototype.js");
686                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "date.js");
687                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.calendar", "date.js");
688                 //addresource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, HtmlCalendarRenderer.class, "popcalendar.js");
689                 TomahawkResourceUtils.addOutputScriptResource(facesContext, "oam.custom.calendar", "popcalendar.js");
690             }
691             else
692             {
693                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "prototype.js");
694                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "date.js");
695                 TomahawkResourceUtils.addOutputScriptResource(facesContext, javascriptLibrary, "popcalendar.js");
696 
697             }
698         }
699 
700         facesContext.getAttributes().put(JAVASCRIPT_ENCODED_JSF2, Boolean.TRUE);
701     }
702 
703     /**
704      * Creates and returns a String which contains the initialisation data for
705      * the popup calendar control as a sequence of javascript commands that
706      * assign values to properties of a javascript object whose name is in
707      * parameter popupCalendarVariable.
708      * <p>
709      * 
710      * @param firstDayOfWeek
711      *            is in java.util.Calendar form, ie Sun=1, Mon=2, Sat=7
712      */
713     public static String getLocalizedLanguageScript(FacesContext facesContext,
714             DateFormatSymbols symbols, int firstDayOfWeek, UIComponent uiComponent,
715             String popupCalendarVariable)
716     {
717 
718         // Convert day value to java.util.Date convention (Sun=0, Mon=1, Sat=6).
719         // This is the convention that javascript Date objects use.
720         int realFirstDayOfWeek = firstDayOfWeek - 1;
721 
722         String[] weekDays;
723 
724         if (realFirstDayOfWeek == 0)
725         {
726             // Sunday
727             weekDays = mapShortWeekdaysStartingWithSunday(symbols);
728         }
729         else if (realFirstDayOfWeek == 1)
730         {
731             // Monday
732             weekDays = mapShortWeekdays(symbols);
733         }
734         else if (realFirstDayOfWeek == 6)
735         {
736             // Saturday. Often used in Arabic countries
737             weekDays = mapShortWeekdaysStartingWithSaturday(symbols);
738         }
739         else
740         {
741             throw new IllegalStateException("Week may only start with saturday, sunday or monday.");
742         }
743 
744         StringBuffer script = new StringBuffer();
745         AddResource ar = AddResourceFactory.getInstance(facesContext);
746 
747         if (uiComponent instanceof HtmlInputCalendar)
748         {
749             HtmlInputCalendar calendar = (HtmlInputCalendar) uiComponent;
750             // Set the themePrefix variable
751             String popupTheme = calendar.getPopupTheme();
752             if (popupTheme == null)
753             {
754                 popupTheme = "DB";
755             }
756             setStringVariable(script, popupCalendarVariable + ".initData.themePrefix",
757                     "jscalendar-" + popupTheme);
758 
759             // specify the URL for the directory in which all the .gif images
760             // can be found
761             String imageLocation = HtmlRendererUtils.getImageLocation(uiComponent);
762             if (imageLocation == null)
763             {
764                 String imageLibrary = (String) uiComponent.getAttributes().get(LibraryLocationAware.IMAGE_LIBRARY_ATTR);
765                 if (imageLibrary == null)
766                 {
767                     //String uri = ar.getResourceUri(facesContext, HtmlCalendarRenderer.class, popupTheme
768                     //        + "/");
769                     //setStringVariable(script, popupCalendarVariable + ".initData.imgDir",
770                     //        JavascriptUtils.encodeString(uri));
771                     Resource resource = facesContext.getApplication().getResourceHandler().createResource(
772                             ";j", "oam.custom.calendar"+((popupTheme!=null&&popupTheme.length()>0)?"."+popupTheme:""));
773                     String path = resource.getRequestPath();
774                     int index = path.indexOf("/;j");
775                     String prefix = path.substring(0, index+1); 
776                     String suffix = path.substring(index+3);
777                     setStringVariable(script, popupCalendarVariable + ".initData.imgDir",prefix);
778                     setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix",suffix);
779                 }
780                 else
781                 {
782                     Resource resource = facesContext.getApplication().getResourceHandler().createResource(
783                             ";j", imageLibrary);
784                     String path = resource.getRequestPath();
785                     int index = path.indexOf("/;j");
786                     String prefix = path.substring(0, index+1); 
787                     String suffix = path.substring(index+3);
788                     setStringVariable(script, popupCalendarVariable + ".initData.imgDir",prefix);
789                     setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix",suffix);                    
790                 }
791             }
792             else
793             {
794                 setStringVariable(script, popupCalendarVariable + ".initData.imgDir",
795                         (JavascriptUtils.encodeString(AddResourceFactory.getInstance(facesContext)
796                                 .getResourceUri(facesContext, imageLocation + "/"))));
797                 setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix","");
798             }
799         }
800         else
801         {
802             String imageLocation = HtmlRendererUtils.getImageLocation(uiComponent);
803             if (imageLocation == null)
804             {
805                 String imageLibrary = (String) uiComponent.getAttributes().get(LibraryLocationAware.IMAGE_LIBRARY_ATTR);
806                 if (imageLibrary == null)
807                 {
808                     //String uri = ar.getResourceUri(facesContext, HtmlCalendarRenderer.class, "images/");
809                     //setStringVariable(script, popupCalendarVariable + ".initData.imgDir",
810                     ///        JavascriptUtils.encodeString(uri));
811                     //setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix","");
812                     Resource resource = facesContext.getApplication().getResourceHandler().createResource(
813                             ";j", "oam.custom.calendar.images");
814                     String path = resource.getRequestPath();
815                     int index = path.indexOf("/;j");
816                     String prefix = path.substring(0, index+1); 
817                     String suffix = path.substring(index+3);
818                     setStringVariable(script, popupCalendarVariable + ".initData.imgDir",prefix);
819                     setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix",suffix);
820                 }
821                 else
822                 {
823                     Resource resource = facesContext.getApplication().getResourceHandler().createResource(
824                             ";j", imageLibrary);
825                     String path = resource.getRequestPath();
826                     int index = path.indexOf("/;j");
827                     String prefix = path.substring(0, index+1); 
828                     String suffix = path.substring(index+3);
829                     setStringVariable(script, popupCalendarVariable + ".initData.imgDir",prefix);
830                     setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix",suffix);
831                 }
832             }
833             else
834             {
835                 setStringVariable(script, popupCalendarVariable + ".initData.imgDir",
836                         (JavascriptUtils.encodeString(AddResourceFactory.getInstance(facesContext)
837                                 .getResourceUri(facesContext, imageLocation + "/"))));
838                 setStringVariable(script, popupCalendarVariable + ".initData.imgDirSuffix","");
839             }
840         }
841         defineStringArray(script, popupCalendarVariable + ".initData.monthName", mapMonths(symbols));
842         defineStringArray(script, popupCalendarVariable + ".initData.dayName", weekDays);
843         setIntegerVariable(script, popupCalendarVariable + ".initData.startAt", realFirstDayOfWeek);
844 
845         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.weekdays",
846                 mapWeekdaysStartingWithSunday(symbols));
847         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.shortWeekdays",
848                 mapShortWeekdaysStartingWithSunday(symbols));
849         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.shortMonths",
850                 mapShortMonths(symbols));
851         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.months",
852                 mapMonths(symbols));
853         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.eras", symbols
854                 .getEras());
855         defineStringArray(script, popupCalendarVariable + ".dateFormatSymbols.ampms", symbols
856                 .getAmPmStrings());
857 
858         if (uiComponent instanceof HtmlInputCalendar)
859         {
860 
861             HtmlInputCalendar inputCalendar = (HtmlInputCalendar) uiComponent;
862 
863             if (inputCalendar.getPopupGotoString() != null)
864                 setStringVariable(script, popupCalendarVariable + ".initData.gotoString",
865                         inputCalendar.getPopupGotoString());
866             if (inputCalendar.getPopupTodayString() != null)
867                 setStringVariable(script, popupCalendarVariable + ".initData.todayString",
868                         inputCalendar.getPopupTodayString());
869             if (inputCalendar.getPopupTodayDateFormat() != null)
870                 setStringVariable(script, popupCalendarVariable + ".initData.todayDateFormat",
871                         inputCalendar.getPopupTodayDateFormat());
872             else if (inputCalendar.getPopupDateFormat() != null)
873                 setStringVariable(script, popupCalendarVariable + ".initData.todayDateFormat",
874                         inputCalendar.getPopupDateFormat());
875             if (inputCalendar.getPopupWeekString() != null)
876                 setStringVariable(script, popupCalendarVariable + ".initData.weekString",
877                         inputCalendar.getPopupWeekString());
878             if (inputCalendar.getPopupScrollLeftMessage() != null)
879                 setStringVariable(script, popupCalendarVariable + ".initData.scrollLeftMessage",
880                         inputCalendar.getPopupScrollLeftMessage());
881             if (inputCalendar.getPopupScrollRightMessage() != null)
882                 setStringVariable(script, popupCalendarVariable + ".initData.scrollRightMessage",
883                         inputCalendar.getPopupScrollRightMessage());
884             if (inputCalendar.getPopupSelectMonthMessage() != null)
885                 setStringVariable(script, popupCalendarVariable + ".initData.selectMonthMessage",
886                         inputCalendar.getPopupSelectMonthMessage());
887             if (inputCalendar.getPopupSelectYearMessage() != null)
888                 setStringVariable(script, popupCalendarVariable + ".initData.selectYearMessage",
889                         inputCalendar.getPopupSelectYearMessage());
890             if (inputCalendar.getPopupSelectDateMessage() != null)
891                 setStringVariable(script, popupCalendarVariable + ".initData.selectDateMessage",
892                         inputCalendar.getPopupSelectDateMessage());
893             setBooleanVariable(script, popupCalendarVariable + ".initData.popupLeft", inputCalendar
894                     .isPopupLeft());
895 
896         }
897 
898         return script.toString();
899     }
900 
901     private static void setBooleanVariable(StringBuffer script, String name, boolean value)
902     {
903         script.append(name);
904         script.append(" = ");
905         script.append(value);
906         script.append(";\n");
907     }
908 
909     private static void setIntegerVariable(StringBuffer script, String name, int value)
910     {
911         script.append(name);
912         script.append(" = ");
913         script.append(value);
914         script.append(";\n");
915     }
916 
917     private static void setStringVariable(StringBuffer script, String name, String value)
918     {
919         script.append(name);
920         script.append(" = \"");
921         script.append(StringEscapeUtils.escapeJavaScript(value));
922         script.append("\";\n");
923     }
924 
925     private static void defineStringArray(StringBuffer script, String arrayName, String[] array)
926     {
927         script.append(arrayName);
928         script.append(" = new Array(");
929 
930         for(int i=0;i<array.length;i++)
931         {
932             if(i!=0)
933                 script.append(",");
934 
935             script.append("\"");
936             script.append(StringEscapeUtils.escapeJavaScript(array[i]));
937             script.append("\"");
938         }
939 
940         script.append(");\n");
941     }
942 
943     public static void getScriptBtn(ResponseWriter writer, FacesContext facesContext, UIComponent uiComponent,
944                                       String dateFormat, String popupButtonString, FunctionCallProvider prov)
945         throws IOException
946     {
947         boolean renderButtonAsImage = false;
948         String popupButtonStyle = null;
949         String popupButtonStyleClass = null;
950 
951         if(uiComponent instanceof HtmlInputCalendar)
952         {
953             HtmlInputCalendar calendar = (HtmlInputCalendar)uiComponent;
954             renderButtonAsImage = calendar.isRenderPopupButtonAsImage();
955             popupButtonStyle = calendar.getPopupButtonStyle();
956             popupButtonStyleClass = calendar.getPopupButtonStyleClass();
957         }
958 
959         if (!renderButtonAsImage) {
960             // render the button
961             writer.startElement(HTML.INPUT_ELEM, uiComponent);
962             writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_BUTTON, null);
963 
964             writer.writeAttribute(HTML.ONCLICK_ATTR,
965                                   prov.getFunctionCall(facesContext,uiComponent,dateFormat),
966                                   null);
967 
968             if(popupButtonString==null)
969                 popupButtonString="...";
970             writer.writeAttribute(HTML.VALUE_ATTR, StringEscapeUtils.escapeJavaScript(popupButtonString), null);
971 
972             if(popupButtonStyle != null)
973             {
974                 writer.writeAttribute(HTML.STYLE_ATTR, popupButtonStyle, null);
975             }
976 
977             if(popupButtonStyleClass != null)
978             {
979                 writer.writeAttribute(HTML.CLASS_ATTR, popupButtonStyleClass, null);
980             }
981             
982             writer.endElement(HTML.INPUT_ELEM);
983         } else {
984             // render the image
985             writer.startElement(HTML.IMG_ELEM, uiComponent);
986             AddResource addResource = AddResourceFactory.getInstance(facesContext);
987 
988             String imgUrl = (String) uiComponent.getAttributes().get("popupButtonImageUrl");
989 
990             if(imgUrl!=null)
991             {
992                 writer.writeAttribute(HTML.SRC_ATTR, addResource.getResourceUri(facesContext, imgUrl), null);
993             }
994             else
995             {
996                 //writer.writeAttribute(HTML.SRC_ATTR, addResource.getResourceUri(facesContext, HtmlCalendarRenderer.class, "images/calendar.gif"), null);
997                 Resource res = facesContext.getApplication().getResourceHandler().createResource("calendar.gif", "oam.custom.calendar.images");
998                 writer.writeAttribute(HTML.SRC_ATTR, res.getRequestPath(), null);
999             }
1000 
1001             if(popupButtonStyle != null)
1002             {
1003                 writer.writeAttribute(HTML.STYLE_ATTR, popupButtonStyle, null);
1004             }
1005             else
1006             {
1007                 writer.writeAttribute(HTML.STYLE_ATTR, "vertical-align:bottom;", null);
1008             }
1009 
1010             if(popupButtonStyleClass != null)
1011             {
1012                 writer.writeAttribute(HTML.CLASS_ATTR, popupButtonStyleClass, null);
1013             }
1014 
1015             writer.writeAttribute(HTML.ONCLICK_ATTR, prov.getFunctionCall(facesContext, uiComponent, dateFormat),
1016                                   null);
1017 
1018             writer.endElement(HTML.IMG_ELEM);
1019         }
1020     }
1021 
1022 
1023     private void writeMonthYearHeader(FacesContext facesContext, ResponseWriter writer, UIInput inputComponent, Calendar timeKeeper,
1024                                       int currentDay, String[] weekdays,
1025                                       String[] months)
1026             throws IOException
1027     {
1028         Calendar cal = shiftMonth(facesContext, timeKeeper, currentDay, -1);
1029 
1030         writeCell(facesContext, writer, inputComponent, "<", cal.getTime(), null);
1031 
1032         writer.startElement(HTML.TD_ELEM, inputComponent);
1033         writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(weekdays.length - 2), null);
1034         writer.writeText(months[timeKeeper.get(Calendar.MONTH)] + " " + timeKeeper.get(Calendar.YEAR), null);
1035         writer.endElement(HTML.TD_ELEM);
1036 
1037         cal = shiftMonth(facesContext, timeKeeper, currentDay, 1);
1038 
1039         writeCell(facesContext, writer, inputComponent, ">", cal.getTime(), null);
1040     }
1041 
1042     private Calendar shiftMonth(FacesContext facesContext,
1043                                 Calendar timeKeeper, int currentDay, int shift)
1044     {
1045         Calendar cal = copyCalendar(facesContext, timeKeeper);
1046 
1047         cal.set(Calendar.DAY_OF_MONTH, 1);
1048         cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) + shift);
1049 
1050         if(currentDay > cal.getActualMaximum(Calendar.DAY_OF_MONTH))
1051             currentDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
1052 
1053         cal.set(Calendar.DAY_OF_MONTH, currentDay);
1054         return cal;
1055     }
1056 
1057     private Calendar copyCalendar(FacesContext facesContext, Calendar timeKeeper)
1058     {
1059         Calendar cal = Calendar.getInstance(facesContext.getViewRoot().getLocale());
1060         cal.setTime(timeKeeper.getTime());
1061         return cal;
1062     }
1063 
1064     private void writeWeekDayNameHeader(int weekStartsAtDayIndex, String[] weekdays, FacesContext facesContext, ResponseWriter writer, UIInput inputComponent)
1065             throws IOException
1066     {
1067         for (int i = weekStartsAtDayIndex; i < weekdays.length; i++)
1068             writeCell(facesContext,
1069                       writer, inputComponent, weekdays[i], null, null);
1070 
1071         for (int i = 0; i < weekStartsAtDayIndex; i++)
1072             writeCell(facesContext, writer,
1073                       inputComponent, weekdays[i], null, null);
1074     }
1075 
1076     private void writeDays(FacesContext facesContext, ResponseWriter writer,
1077                            HtmlInputCalendar inputComponent, Calendar timeKeeper, int currentDay, int weekStartsAtDayIndex,
1078                            int weekDayOfFirstDayOfMonth, int lastDayInMonth, String[] weekdays)
1079             throws IOException
1080     {
1081         Calendar cal;
1082 
1083         int space = (weekStartsAtDayIndex < weekDayOfFirstDayOfMonth) ? (weekDayOfFirstDayOfMonth - weekStartsAtDayIndex)
1084                     : (weekdays.length - weekStartsAtDayIndex + weekDayOfFirstDayOfMonth);
1085 
1086         if (space == weekdays.length)
1087             space = 0;
1088 
1089         int columnIndexCounter = 0;
1090 
1091         for (int i = 0; i < space; i++)
1092         {
1093             if (columnIndexCounter == 0)
1094             {
1095                 writer.startElement(HTML.TR_ELEM, inputComponent);
1096             }
1097 
1098             writeCell(facesContext, writer, inputComponent, "",
1099                       null, inputComponent.getDayCellClass());
1100             columnIndexCounter++;
1101         }
1102 
1103         for (int i = 0; i < lastDayInMonth; i++)
1104         {
1105             if (columnIndexCounter == 0)
1106             {
1107                 writer.startElement(HTML.TR_ELEM, inputComponent);
1108             }
1109 
1110             cal = copyCalendar(facesContext, timeKeeper);
1111             cal.set(Calendar.DAY_OF_MONTH, i + 1);
1112 
1113             String cellStyle = inputComponent.getDayCellClass();
1114 
1115             if((currentDay - 1) == i)
1116                 cellStyle = inputComponent.getCurrentDayCellClass();
1117 
1118             writeCell(facesContext, writer,
1119                       inputComponent, String.valueOf(i + 1), cal.getTime(),
1120                       cellStyle);
1121 
1122             columnIndexCounter++;
1123 
1124             if (columnIndexCounter == weekdays.length)
1125             {
1126                 writer.endElement(HTML.TR_ELEM);
1127                 HtmlRendererUtils.writePrettyLineSeparator(facesContext);
1128                 columnIndexCounter = 0;
1129             }
1130         }
1131 
1132         if (columnIndexCounter != 0)
1133         {
1134             for (int i = columnIndexCounter; i < weekdays.length; i++)
1135             {
1136                 writeCell(facesContext, writer,
1137                           inputComponent, "", null, inputComponent.getDayCellClass());
1138             }
1139 
1140             writer.endElement(HTML.TR_ELEM);
1141             HtmlRendererUtils.writePrettyLineSeparator(facesContext);
1142         }
1143     }
1144 
1145     /**
1146      * Generate components and output for a single "day" cell within the calendar display.
1147      */
1148     private void writeCell(FacesContext facesContext,
1149                            ResponseWriter writer, UIInput component, String content,
1150                            Date valueForLink, String styleClass)
1151             throws IOException
1152     {
1153         writer.startElement(HTML.TD_ELEM, component);
1154 
1155         if (styleClass != null)
1156             writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
1157 
1158         if (valueForLink == null)
1159             writer.writeText(content, JSFAttr.VALUE_ATTR);
1160         else
1161         {
1162             writeLink(content, component, facesContext, valueForLink);
1163         }
1164 
1165         writer.endElement(HTML.TD_ELEM);
1166     }
1167 
1168     /**
1169      * Create child components to represent a link to a specific date value, and render them.
1170      * <p>
1171      * For a disabled calendar, this just creates a Text component, attaches it as a child
1172      * of the calendar and renders it. The value of the component is the string returned by
1173      * valueForLink.getTime().
1174      * <p>
1175      * For a non-disabled calendar, create an HtmlCommandLink child that wraps the text
1176      * returned by valueForLink.getTime(), and add it to the component.
1177      */
1178     private void writeLink(String content,
1179                            UIInput component,
1180                            FacesContext facesContext,
1181                            Date valueForLink)
1182             throws IOException
1183     {
1184         Converter converter = getConverter(component);
1185         Application application = facesContext.getApplication();
1186 
1187         HtmlOutputText text
1188                 = (HtmlOutputText)application.createComponent(HtmlOutputText.COMPONENT_TYPE);
1189         text.setValue(content);
1190         text.setId(component.getId() + "_" + valueForLink.getTime() + "_text");
1191         text.setTransient(true);
1192 
1193         HtmlInputCalendar calendar = (HtmlInputCalendar)component;
1194         if (isDisabled(facesContext, component) || calendar.isReadonly())
1195         {
1196             component.getChildren().add(text);
1197 
1198             RendererUtils.renderChild(facesContext, text);
1199             return;
1200         }
1201 
1202         HtmlCommandLink link
1203                 = (HtmlCommandLink)application.createComponent(HtmlCommandLink.COMPONENT_TYPE);
1204         link.setId(component.getId() + "_" + valueForLink.getTime() + "_link");
1205         link.setTransient(true);
1206         link.setImmediate(component.isImmediate());
1207 
1208         UIParameter parameter
1209                 = (UIParameter)application.createComponent(UIParameter.COMPONENT_TYPE);
1210         parameter.setId(component.getId() + "_" + valueForLink.getTime() + "_param");
1211         parameter.setTransient(true);
1212         parameter.setName(component.getClientId(facesContext));
1213         parameter.setValue(converter.getAsString(facesContext, component, valueForLink));
1214 
1215         RendererUtils.addOrReplaceChild(component,link);
1216         link.getChildren().add(parameter);
1217         link.getChildren().add(text);
1218 
1219         RendererUtils.renderChild(facesContext, link);
1220     }
1221 
1222     private Converter getConverter(UIInput component)
1223     {
1224         Converter converter = component.getConverter();
1225 
1226         if (converter == null)
1227         {
1228             converter = new CalendarDateTimeConverter();
1229         }
1230         return converter;
1231     }
1232     
1233     private DateBusinessConverter getDateBusinessConverter(AbstractHtmlInputCalendar component)
1234     {
1235         DateBusinessConverter dateBusinessConverter = component.getDateBusinessConverter(); 
1236         if (dateBusinessConverter == null)
1237         {
1238             dateBusinessConverter = new DefaultDateBusinessConverter();
1239         }
1240         return dateBusinessConverter;
1241     }
1242 
1243     private int mapCalendarDayToCommonDay(int day)
1244     {
1245         switch (day)
1246         {
1247             case Calendar.TUESDAY:
1248                 return 1;
1249             case Calendar.WEDNESDAY:
1250                 return 2;
1251             case Calendar.THURSDAY:
1252                 return 3;
1253             case Calendar.FRIDAY:
1254                 return 4;
1255             case Calendar.SATURDAY:
1256                 return 5;
1257             case Calendar.SUNDAY:
1258                 return 6;
1259             default:
1260                 return 0;
1261         }
1262     }
1263 
1264     private static String[] mapShortWeekdays(DateFormatSymbols symbols)
1265     {
1266         String[] weekdays = new String[7];
1267 
1268         String[] localeWeekdays = symbols.getShortWeekdays();
1269 
1270         weekdays[0] = localeWeekdays[Calendar.MONDAY];
1271         weekdays[1] = localeWeekdays[Calendar.TUESDAY];
1272         weekdays[2] = localeWeekdays[Calendar.WEDNESDAY];
1273         weekdays[3] = localeWeekdays[Calendar.THURSDAY];
1274         weekdays[4] = localeWeekdays[Calendar.FRIDAY];
1275         weekdays[5] = localeWeekdays[Calendar.SATURDAY];
1276         weekdays[6] = localeWeekdays[Calendar.SUNDAY];
1277 
1278         return weekdays;
1279     }
1280 
1281     private static String[] mapShortWeekdaysStartingWithSunday(DateFormatSymbols symbols)
1282     {
1283         String[] weekdays = new String[7];
1284 
1285         String[] localeWeekdays = symbols.getShortWeekdays();
1286 
1287         weekdays[0] = localeWeekdays[Calendar.SUNDAY];
1288         weekdays[1] = localeWeekdays[Calendar.MONDAY];
1289         weekdays[2] = localeWeekdays[Calendar.TUESDAY];
1290         weekdays[3] = localeWeekdays[Calendar.WEDNESDAY];
1291         weekdays[4] = localeWeekdays[Calendar.THURSDAY];
1292         weekdays[5] = localeWeekdays[Calendar.FRIDAY];
1293         weekdays[6] = localeWeekdays[Calendar.SATURDAY];
1294 
1295         return weekdays;
1296     }
1297 
1298     
1299     private static String[] mapShortWeekdaysStartingWithSaturday(DateFormatSymbols symbols) 
1300     {
1301         String[] weekdays = new String[7];
1302 
1303         String[] localeWeekdays = symbols.getShortWeekdays();
1304 
1305         weekdays[0] = localeWeekdays[Calendar.SATURDAY];
1306         weekdays[1] = localeWeekdays[Calendar.SUNDAY];
1307         weekdays[2] = localeWeekdays[Calendar.MONDAY];
1308         weekdays[3] = localeWeekdays[Calendar.TUESDAY];
1309         weekdays[4] = localeWeekdays[Calendar.WEDNESDAY];
1310         weekdays[5] = localeWeekdays[Calendar.THURSDAY];
1311         weekdays[6] = localeWeekdays[Calendar.FRIDAY];
1312 
1313         return weekdays;
1314     }    
1315     
1316     private static String[] mapWeekdaysStartingWithSunday(DateFormatSymbols symbols)
1317     {
1318         String[] weekdays = new String[7];
1319 
1320         String[] localeWeekdays = symbols.getWeekdays();
1321 
1322         weekdays[0] = localeWeekdays[Calendar.SUNDAY];
1323         weekdays[1] = localeWeekdays[Calendar.MONDAY];
1324         weekdays[2] = localeWeekdays[Calendar.TUESDAY];
1325         weekdays[3] = localeWeekdays[Calendar.WEDNESDAY];
1326         weekdays[4] = localeWeekdays[Calendar.THURSDAY];
1327         weekdays[5] = localeWeekdays[Calendar.FRIDAY];
1328         weekdays[6] = localeWeekdays[Calendar.SATURDAY];
1329 
1330         return weekdays;
1331     }
1332 
1333     public static String[] mapMonths(DateFormatSymbols symbols)
1334     {
1335         String[] months = new String[12];
1336 
1337         String[] localeMonths = symbols.getMonths();
1338 
1339         months[0] = localeMonths[Calendar.JANUARY];
1340         months[1] = localeMonths[Calendar.FEBRUARY];
1341         months[2] = localeMonths[Calendar.MARCH];
1342         months[3] = localeMonths[Calendar.APRIL];
1343         months[4] = localeMonths[Calendar.MAY];
1344         months[5] = localeMonths[Calendar.JUNE];
1345         months[6] = localeMonths[Calendar.JULY];
1346         months[7] = localeMonths[Calendar.AUGUST];
1347         months[8] = localeMonths[Calendar.SEPTEMBER];
1348         months[9] = localeMonths[Calendar.OCTOBER];
1349         months[10] = localeMonths[Calendar.NOVEMBER];
1350         months[11] = localeMonths[Calendar.DECEMBER];
1351 
1352         return months;
1353     }
1354 
1355     public static String[] mapShortMonths(DateFormatSymbols symbols)
1356     {
1357         String[] months = new String[12];
1358 
1359         String[] localeMonths = symbols.getShortMonths();
1360 
1361         months[0] = localeMonths[Calendar.JANUARY];
1362         months[1] = localeMonths[Calendar.FEBRUARY];
1363         months[2] = localeMonths[Calendar.MARCH];
1364         months[3] = localeMonths[Calendar.APRIL];
1365         months[4] = localeMonths[Calendar.MAY];
1366         months[5] = localeMonths[Calendar.JUNE];
1367         months[6] = localeMonths[Calendar.JULY];
1368         months[7] = localeMonths[Calendar.AUGUST];
1369         months[8] = localeMonths[Calendar.SEPTEMBER];
1370         months[9] = localeMonths[Calendar.OCTOBER];
1371         months[10] = localeMonths[Calendar.NOVEMBER];
1372         months[11] = localeMonths[Calendar.DECEMBER];
1373 
1374         return months;
1375     }
1376 
1377 
1378     public void decode(FacesContext facesContext, UIComponent component)
1379     {
1380         if(HtmlRendererUtils.isDisabledOrReadOnly(component))
1381         {
1382             // nothing to do here
1383             return;
1384         }
1385 
1386         RendererUtils.checkParamValidity(facesContext, component, HtmlInputCalendar.class);
1387 
1388         //String helperString = getHelperString(component);
1389 
1390         if (!(component instanceof EditableValueHolder)) {
1391             throw new IllegalArgumentException("Component "
1392                                                + component.getClientId(facesContext)
1393                                                + " is not an EditableValueHolder");
1394         }
1395         Map paramMap = facesContext.getExternalContext()
1396                 .getRequestParameterMap();
1397         String clientId = component.getClientId(facesContext);
1398 
1399         if(paramMap.containsKey(clientId))
1400         {
1401             String value = (String) paramMap.get(clientId);
1402 
1403             //if(!value.equalsIgnoreCase(helperString))
1404             //{
1405                 ((EditableValueHolder) component).setSubmittedValue(value);
1406             //}
1407             //else
1408             //{
1409                 // The field was initially filled with the "helper string", and has
1410                 // not been altered by the user so treat this as if null had been
1411                 // passed by the user.
1412                 //
1413                 // TODO: does this mean the target date is set to todays date?
1414                 // And how does this affect the "required" property?
1415                 //((EditableValueHolder) component).setSubmittedValue("");
1416             //}
1417         }
1418         else
1419         {
1420             log.warn(HtmlRendererUtils.NON_SUBMITTED_VALUE_WARNING +
1421                 " Component : "+
1422                 RendererUtils.getPathToComponent(component));
1423         }
1424 
1425         HtmlRendererUtils.decodeClientBehaviors(facesContext, component);
1426     }
1427     
1428     protected static boolean isDisabled(FacesContext facesContext, UIComponent uiComponent)
1429     {
1430         if (!UserRoleUtils.isEnabledOnUserRole(uiComponent))
1431         {
1432             return true;
1433         }
1434         else
1435         {
1436             if (uiComponent instanceof HtmlInputCalendar)
1437             {
1438                 return ((HtmlInputCalendar)uiComponent).isDisabled();
1439             }
1440             else
1441             {
1442                 return org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils.getBooleanAttribute(uiComponent, HTML.DISABLED_ATTR, false);
1443             }
1444         }
1445     }
1446 
1447     public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue) throws ConverterException
1448     {
1449         RendererUtils.checkParamValidity(facesContext, uiComponent, HtmlInputCalendar.class);
1450 
1451         AbstractHtmlInputCalendar uiInput = (AbstractHtmlInputCalendar) uiComponent;
1452 
1453         Converter converter = uiInput.getConverter();
1454 
1455         if (submittedValue != null && !(submittedValue instanceof String))
1456         {
1457             throw new IllegalArgumentException("Submitted value of type String expected");
1458         }
1459         
1460         //Do not convert if submittedValue is helper string  
1461         if(submittedValue != null && submittedValue.equals(getHelperString(uiComponent)))
1462             return null;
1463         
1464         if(converter==null)
1465         {
1466             converter = new CalendarDateTimeConverter();
1467             
1468             Date date = (Date) converter.getAsObject(facesContext, uiComponent, (String) submittedValue);
1469             
1470             return getDateBusinessConverter(uiInput).getBusinessValue(facesContext, uiComponent, date);
1471         }
1472         else
1473         {
1474             return converter.getAsObject(facesContext, uiComponent, (String) submittedValue);
1475         }
1476     }
1477 
1478     public interface DateConverter extends Converter
1479     {
1480         public Date getAsDate(FacesContext facesContext, UIComponent uiComponent);
1481     }
1482 
1483     private static String getHelperString(UIComponent uiComponent)
1484     {
1485         return uiComponent instanceof HtmlInputCalendar?((HtmlInputCalendar) uiComponent).getHelpText():null;
1486     }
1487 
1488     public static class CalendarDateTimeConverter implements DateConverter
1489     {
1490         private static final String CONVERSION_MESSAGE_ID = "org.apache.myfaces.calendar.CONVERSION";
1491 
1492         public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String s)
1493         {
1494             if(s==null || s.trim().length()==0 || s.equals(getHelperString(uiComponent)))
1495                 return null;
1496 
1497             if(uiComponent instanceof HtmlInputCalendar && ((HtmlInputCalendar) uiComponent).isRenderAsPopup())
1498             {
1499                 HtmlInputCalendar calendar = (HtmlInputCalendar) uiComponent;
1500                 String popupDateFormat = calendar.getPopupDateFormat();
1501                 String formatStr = createJSPopupFormat(facesContext, popupDateFormat);
1502                 Locale locale = facesContext.getViewRoot().getLocale();
1503                 Calendar timeKeeper = Calendar.getInstance(locale);
1504                 int firstDayOfWeek = timeKeeper.getFirstDayOfWeek() - 1;
1505                 org.apache.myfaces.dateformat.DateFormatSymbols symbols = new org.apache.myfaces.dateformat.DateFormatSymbols(locale);
1506                 SimpleDateFormatter dateFormat = new SimpleDateFormatter(formatStr, symbols, firstDayOfWeek);
1507                 
1508                 Date date = dateFormat.parse(s); 
1509                 if (date != null) {
1510                     return date;
1511                 }
1512                 FacesMessage msg = MessageUtils.getMessage(Constants.TOMAHAWK_DEFAULT_BUNDLE,FacesMessage.SEVERITY_ERROR,CONVERSION_MESSAGE_ID,new Object[]{
1513                         uiComponent.getId(),s},facesContext);
1514                 throw new ConverterException(msg);
1515             }
1516             else
1517             {
1518                 DateFormat dateFormat = createStandardDateFormat(facesContext);
1519                 dateFormat.setLenient(false);
1520                 try
1521                 {
1522                     Date date = dateFormat.parse(s); 
1523                     return date;
1524                 }
1525                 catch (ParseException e)
1526                 {
1527                     FacesMessage msg = MessageUtils.getMessage(Constants.TOMAHAWK_DEFAULT_BUNDLE,FacesMessage.SEVERITY_ERROR,CONVERSION_MESSAGE_ID,new Object[]{
1528                             uiComponent.getId(),s},facesContext);
1529                     throw new ConverterException(msg,e);
1530                 }
1531             }
1532         }
1533 
1534         public Date getAsDate(FacesContext facesContext, UIComponent uiComponent)
1535         {
1536             return RendererUtils.getDateValue(uiComponent);
1537         }
1538 
1539         public static String createJSPopupFormat(FacesContext facesContext, String popupDateFormat)
1540         {
1541 
1542             if(popupDateFormat == null)
1543             {
1544                 SimpleDateFormat defaultDateFormat = createStandardDateFormat(facesContext);
1545                 popupDateFormat = defaultDateFormat.toPattern();
1546             }
1547 
1548             return popupDateFormat;
1549         }
1550 
1551         public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object o)
1552         {
1553             Date date = (Date) o;
1554 
1555             if(date==null)
1556                 return getHelperString(uiComponent);
1557 
1558             if(uiComponent instanceof HtmlInputCalendar && ((HtmlInputCalendar) uiComponent).isRenderAsPopup())
1559             {
1560                 HtmlInputCalendar calendar = (HtmlInputCalendar) uiComponent;
1561                 String popupDateFormat = calendar.getPopupDateFormat();
1562                 String formatStr = createJSPopupFormat(facesContext, popupDateFormat);
1563                 Locale locale = facesContext.getViewRoot().getLocale();
1564                 Calendar timeKeeper = Calendar.getInstance(locale);
1565                 int firstDayOfWeek = timeKeeper.getFirstDayOfWeek() - 1;
1566                 org.apache.myfaces.dateformat.DateFormatSymbols symbols = new org.apache.myfaces.dateformat.DateFormatSymbols(locale);
1567 
1568                 SimpleDateFormatter dateFormat = new SimpleDateFormatter(formatStr, symbols, firstDayOfWeek);
1569                 return dateFormat.format(date);
1570             }
1571             else
1572             {
1573                 DateFormat dateFormat = createStandardDateFormat(facesContext);
1574                 dateFormat.setLenient(false);
1575                 return dateFormat.format(date);
1576             }
1577         }
1578 
1579         private static SimpleDateFormat createStandardDateFormat(FacesContext facesContext)
1580         {
1581             DateFormat dateFormat;
1582             dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
1583                                                     facesContext.getViewRoot().getLocale());
1584 
1585             if(dateFormat instanceof SimpleDateFormat)
1586                 return (SimpleDateFormat) dateFormat;
1587             else
1588                 return new SimpleDateFormat("dd.MM.yyyy", facesContext.getViewRoot().getLocale());
1589         }
1590     }
1591 }