/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.extensions.yui.calendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.StringHeaderContributor;
import org.apache.wicket.extensions.yui.YuiLib;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.util.string.JavascriptUtils;
/**
* Abstract calendar component based on the YUI (Yahoo User Interface library) javascript widget.
* <p>
* Although this component by itself is fully functional, it doesn't do much other than just
* displaying the calendar. Hence, this class is abstract.
* </p>
* <p>
* An easy way to build upon this component is to override
* {@link #appendToInit(String, String, String, StringBuffer)} and add event handlers etc. in the
* YUI widget's initialization function.
* </p>
* See <a href="http://developer.yahoo.com/yui/calendar/">YUI's calendar documentation</a> for more
* info.
*
* @author eelcohillenius
*
* @see DatePicker
*/
// TODO provide localization strings (base them on the messages of
// JsDatePicker?)
public abstract class AbstractCalendar extends WebComponent
{
/**
* Format to be used when configuring YUI calendar. Can be used when using the
* "selected" property.
*/
public static final DateFormat FORMAT_DATE = new SimpleDateFormat("MM/dd/yyyy");
/**
* For specifying which page (month/year) to show in the calendar, use this format for the date.
* This is to be used together with the property "pagedate"
*/
public static final DateFormat FORMAT_PAGEDATE = new SimpleDateFormat("MM/yyyy");
private static final long serialVersionUID = 1L;
/**
* Construct. Contributes packaged dependencies.
*
* @param id
* The component id
*/
public AbstractCalendar(String id)
{
this(id, true);
}
/**
* Construct.
*
* @param id
* The component id
* @param contributeDependencies
* Whether to contribute the packaged dependencies. Pass false in case you want to
* include the dependencies manually in your own page, e.g. when you want to keep
* them in your web application dir. To contribute yourself (in case you want to pass
* false), your page header should look like:
*
* <pre>
* <script type="text/javascript" src="yahoo.js"></script>
* <script type="text/javascript" src="dom.js"></script>
* <script type="text/javascript" src="event.js"></script>
* <script type="text/javascript" src="calendar.js"></script>
* <link rel="stylesheet" type="text/css" href="calendar.css" />
* </pre>
*/
public AbstractCalendar(String id, boolean contributeDependencies)
{
super(id);
setOutputMarkupId(true);
if (contributeDependencies)
{
contributeDependencies();
}
add(new StringHeaderContributor(new LoadableDetachableModel()
{
private static final long serialVersionUID = 1L;
@Override
protected Object load()
{
// not pretty to look at, but cheaper than using a template
String markupId = AbstractCalendar.this.getMarkupId();
String javascriptId = getJavascriptId();
String javascriptWidgetId = getJavascriptWidgetId();
StringBuffer b = new StringBuffer();
b.append(JavascriptUtils.SCRIPT_OPEN_TAG);
// initialize wicket namespace and register the init function
// for the YUI widget
b.append("YAHOO.namespace(\"wicket\");\nfunction init");
b.append(javascriptId);
b.append("() {\n");
// instantiate the calendar object
b.append(" ");
b.append(javascriptWidgetId);
b.append(" = new YAHOO.widget.Calendar(\"");
b.append(javascriptId);
b.append("\",\"");
b.append(markupId);
Properties p = new Properties();
configureWidgetProperties(p);
b.append("\", { ");
for (Iterator i = p.entrySet().iterator(); i.hasNext();)
{
Entry entry = (Entry)i.next();
b.append(entry.getKey());
Object value = entry.getValue();
if (value instanceof CharSequence)
{
b.append(":\"");
b.append(value);
b.append("\"");
}
else if (value instanceof CharSequence[])
{
b.append(":[");
CharSequence[] valueArray = (CharSequence[])value;
for (int j = 0; j < valueArray.length; j++)
{
CharSequence tmpValue = valueArray[j];
b.append("\"");
b.append(tmpValue);
b.append("\"");
if (j < valueArray.length - 1)
{
b.append(",");
}
}
b.append("]");
}
else
{
b.append(":");
b.append(value);
}
// TODO handle arrays
if (i.hasNext())
{
b.append(",");
}
}
b.append(" });\n");
// append the javascript we want for our init function; call
// this in an overridable method so that clients can add their
// stuff without needing a big ass API
appendToInit(markupId, javascriptId, javascriptWidgetId, b);
// trigger rendering
b.append(" ");
b.append(javascriptWidgetId);
b.append(".render();\n");
b.append("}\n");
// register the function for execution when the page is loaded
b.append("YAHOO.util.Event.addListener(window, \"load\", init");
b.append(javascriptId);
b.append(");");
b.append(JavascriptUtils.SCRIPT_CLOSE_TAG);
return b;
}
}));
}
/**
* Gets the id of the javascript widget. Note that this is the non-namespaced id, so depending
* on what you want to do with it, you may need to prepend 'YAHOO.wicket.' to it. Or you can
* call {@link #getJavascriptWidgetId()}.
*
* @return The javascript id
* @see #getJavascriptWidgetId()
*/
public final String getJavascriptId()
{
return getMarkupId() + "Js";
}
/**
* The name spaced id of the widget.
*
* @return The widget id
* @see #getJavascriptId()
*/
public final String getJavascriptWidgetId()
{
return "YAHOO.wicket." + getJavascriptId();
}
/**
* add header contributions for packaged resources.
*/
private void contributeDependencies()
{
add(HeaderContributor.forJavaScript(YuiLib.class, "yahoo-dom-event/yahoo-dom-event.js"));
add(HeaderContributor.forJavaScript(AbstractCalendar.class, "calendar-min.js"));
add(HeaderContributor.forCss(AbstractCalendar.class, "assets/skins/sam/calendar.css"));
}
/**
* Append javascript to the initialization function for the YUI widget. Can be used by
* subclasses to conveniently extend configuration without having to write a separate
* contribution.
*
* @param markupId
* The markup id of the calendar component
* @param javascriptId
* the non-name spaced javascript id of the widget
* @param javascriptWidgetId
* the name space id of the widget
* @param b
* the buffer to append the script to
*/
protected void appendToInit(String markupId, String javascriptId, String javascriptWidgetId,
StringBuffer b)
{
}
/**
* Gives overriding classes the option of adding (or even changing/ removing) configuration
* properties for the javascript widget. See <a
* href="http://developer.yahoo.com/yui/calendar/">the widget's documentation</a> for the
* available options. If you want to override/ remove properties, you obviously should call
* <code>super.configureWidgetProperties(properties)</code>.
*
* @param widgetProperties
* the current widget properties
*/
protected void configureWidgetProperties(Map widgetProperties)
{
}
}