« Fractions for Servoy | Main | Data Sutra 4.0 Released »

January 03, 2013

Hacking Servoy's Calendar Picker

Servoy's web client calendar picker is an easy target to make fun of—the last time it looked even somewhat passable was probably 15 years ago. A picture is worth a 1,000 words:

Servoy_calendar_picker

Additionally, Servoy doesn't have any hooks or abstractions to modify the look and feel of this component. And all the markup and css is generated at runtime so not so easy as just going in and adjusting a bit of CSS.

The rest of this post explains how to arrive at this:

Calendar_picker_after

The good news is that Servoy does use a css file (and graphics) to style the calendar widget. The bad news is that since it is generated at runtime you need to do some URL rewriting to divert Servoy's path for the style sheet and graphics to a location of your own choosing.

We use UrlRewriteFilter for this job as it simplifies the task of creating rewrite rules so you don't need a PhD. Here's the two rules to point to our new style sheet and graphics directory:

<rule>
<name>Servoy calendar css</name>
<note>Better calendar picker graphics</note>
<from>^/servoy-webclient/resources/datepicker/dhtmlgoodies_calendar.css$</from>
<to last="true">/servoy-webclient/templates/datasutra/override/calendar/calendar.css</to>
</rule>

<rule>
<name>Servoy calendar graphics</name>
<note>Better calendar picker graphics</note>
<from>^/servoy-webclient/resources/datepicker/images/(.*)$</from>
<to last="true">/servoy-webclient/templates/datasutra/override/calendar/images/$1</to>
</rule>

File location

Note that you can place these files anywhere in Servoy's ROOT directory and adjust the rules accordingly.

Now all you need to do is fiddle with the css and replace out some images. By "fiddling" I mean it really helps to know some advanced css tricks. In the following css note how we're using gradient stops, positional selectors, and even inserting content:

#calendarDiv {
position: absolute;
width: 250px;
border: 1px solid gray;
padding: 10px;
font-family: arial,helvetica,clean,sans-serif;
font-size: 10px;
padding-bottom: 20px;
visibility: hidden;
background: #F2F2F2;

background: -moz-linear-gradient(top, #ffffff 0%, #f9f9f9 41%, #e8eaea 63%, #d8d8da 79%, #d8d8da 97%, #8c8c8c 100%) 0 0 no-repeat, #F2F2F2;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(41%,#f9f9f9), color-stop(63%,#e8eaea), color-stop(79%,#d8d8da), color-stop(97%,#d8d8da), color-stop(100%,#8c8c8c)) 0 0 no-repeat, #F2F2F2;
background: -webkit-linear-gradient(top, #ffffff 0%,#f9f9f9 41%,#e8eaea 63%,#d8d8da 79%,#d8d8da 97%,#8c8c8c 100%) 0 0 no-repeat, #F2F2F2;
background: -o-linear-gradient(top, #ffffff 0%,#f9f9f9 41%,#e8eaea 63%,#d8d8da 79%,#d8d8da 97%,#8c8c8c 100%) 0 0 no-repeat, #F2F2F2;
background: -ms-linear-gradient(top, #ffffff 0%,#f9f9f9 41%,#e8eaea 63%,#d8d8da 79%,#d8d8da 97%,#8c8c8c 100%) 0 0 no-repeat, #F2F2F2;
background: linear-gradient(to bottom, #ffffff 0%,#f9f9f9 41%,#e8eaea 63%,#d8d8da 79%,#d8d8da 97%,#8c8c8c 100%) 0 0 no-repeat, #F2F2F2;
filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#8c8c8c',GradientType=0 );

-moz-background-size: 100% 24px;
-webkit-background-size: 100% 24px;
-o-background-size: 100% 24px;
-ms-background-size: 100% 24px;
background-size: 100% 24px;
}

#calendarDiv span, #calendarDiv img {
float: left;
padding-top: 1px;
}

#calendarDiv .calendar_week_column {
display: none;
}

#calendarDiv .selectBox img,
#calendarDiv .selectBoxOver img {
display: none;
}

#calendarDiv .selectBox,
#calendarDiv .selectBoxOver {
padding: 1px;
cursor: pointer;
padding-left: 2px;
font-size: 14px;

font-weight: bold;
position: relative;
padding-left: 6px;
padding-right: 6px;
border-radius: 2px;
}

#calendarDiv .selectBox {
background-color: #FBFBFB;
border: 1px solid #CCC;
}

#calendarDiv .selectBoxOver {
background-color: #426FD9;
border: 1px solid #FFF;
color: #FFF;
}

#calendarDiv .selectBoxTime,
#calendarDiv .selectBoxTimeOver {
padding: 1px;
cursor: pointer;
padding-left: 2px;
position: relative;
border-radius: 2px;
height: 1.3em;
}

#calendarDiv .selectBoxTime {
background-color: #FBFBFB;
border: 1px solid #CCC;
color: #262626;
}

#calendarDiv .selectBoxTimeOver {
background-color: #426FD9;
border: 1px solid #FFF;
color: #FFF;
}

#calendarDiv .topBar {
height: 1.6em;
padding: 2px;
margin-top: 18px;
margin-bottom: 8px;
text-align: center;
}

#calendarDiv .topBar img {
cursor: pointer;
}

#calendarDiv .topBar div {
display: inline-block;
margin-right: 1px;
}

#calendarDiv .topBar div:first-child img {
position: absolute;
left: 10px;
top: 32px;
}

#calendarDiv .topBar div:nth-of-type(2) img {
position: absolute;
right: 10px;
top: 32px;
}

#calendarDiv .topBar img:nth-of-type(1) {
position: absolute;
right: 10px;
top: 3px;
}

#calendarDiv .activeDay { /* Active day in the calendar */
color: #FFFFFF;
background-color: #426FD9;
}

#calendarDiv .todaysDate {
text-align: left;
position: absolute;
bottom: 4px;
right: 10px;
left: 10px;
}

#calendarDiv .todaysDate div {
float: left;
}

#calendarDiv #todaysDateString:before{
content: "Today (";
}
#calendarDiv #todaysDateString:after{
content: ")";
}

#calendarDiv .timeBar {
position: absolute;
right: -4px;
bottom: -2px;
}

#calendarDiv .timeBar div {
float: left;
margin-left: 1px;
cursor: pointer;
}

#calendarDiv #monthSelect {
margin-right: 4px;
}

#calendar_hour_txt, #calendar_minute_txt {
padding-left: 3px;
}

#calendarDiv #yearDropDown {
margin-top: -2px;
margin-left: 35px;
}

#calendarDiv #monthDropDown {
margin-top: -2px;
margin-left: 45px;
}

#calendarDiv #hourDropDown,
#calendarDiv #minuteDropDown {
margin-top: 35px;
margin-left: 10px;
}

#calendarDiv .monthYearPicker {
background-color: #FBFBFB;
border: 1px solid #CCC;
border-radius: 6px;
font-size: 12px;
padding: 3px;
position: absolute;
left: 0px;
top: 15px;
z-index: 1000;
display: none;
}

#calendarDiv .monthYearPicker div {
float: none;
clear: both;
padding: 1px;
margin: 1px;
cursor: pointer;
}

#calendarDiv .monthYearActive {
background-color: #426FD9;
color: #FFFFFF;
}

#calendarDiv table {
table-layout: fixed;
border-collapse: collapse;
background-color: #CCC;
}

#calendarDiv td[colspan]{
background-color: #CCC;
cursor: default;
}

#calendarDiv td {
border: 1px solid #CCC;
text-align: center;
cursor: pointer;
padding: 1px;
height: 1.6em;
line-height: 1.6em;
white-space: nowrap;
margin: 0px;
font-size: 12px;
color: #06C;
background-color: white;
}

#calendarDiv tr.calendar_week_row {
padding: 0;
border: 0;
}

#calendarDiv tr.calendar_week_row td {
border: 0;
height: 2em;
color: black;
background-color: #F2F2F2;
cursor: default;
}

Here is the link to download a zip containing the css and images. Enjoy!

Download Calendar

| Posted by David Workman on January 3, 2013 at 03:17 PM in Tips | Permalink

Comments

The images and basic styling idea for this reskin came from the YUI calendar control.
http://developer.yahoo.com/yui/examples/calendar/quickstart.html

Posted by: Troy Elliott | Jan 3, 2013 5:41:29 PM

Very nice! Of course we always want more.

At the following Yahoo url they show a calendar combined with a html field. http://developer.yahoo.com/yui/examples/layout/calrte_layout_source.html

I think users would love the way the two interact. It would be really nice if this could be done within Servoy.

Dean

Posted by: Dean Westover | Jan 6, 2013 5:18:11 PM

Dean -

Basically you want the calendar as a component you can place anywhere and calls a Servoy method of your choosing. I assume in your example insert a date into the html editor.

This is very doable. We're slammed for a bit here but by the end of the month for sure if no one else gets to it first.

Posted by: David Workman | Jan 7, 2013 10:18:32 AM

Nice one!

Posted by: Scott | Feb 7, 2013 7:46:30 PM

The comments to this entry are closed.