Advanced Datepicker helper for CakePHP
CakePHP, Development, Frameworks, Labs, PHP September 13th, 2007
I am using this part of code from long time ago, but it wasn’t in a separate helper. It was part from one not very good written Auth component/helper mash up which I already not using. I fact the default Date input of CakePHP Framework is ugly and not user friendly at all. The drop-downs are good because the format is clear and users cannot mess it, but if you thinking for the user convenience, then you should know that it’s 3 times easier just to type the date in a field or to pick it from a Calendar pop up than selecting from 3 drop-downs.
In that helper I am using JSCalendar. The benefits of it are:
- Flexible Date and Time formats
- Full Theme support
- Translated in many languages
- Fast and very easy to setup
For additional information: Full Demo and Calendar Documentation Pages.
What you should do in order to run it in you CakePHP project.
Update 04 Oct 2007 - From now on the Helper includes 2 options
- Regular Po-pup Calendar - simple text field with link which will pop-up the calendar helper
- Flat Calendar - It’s using hidden field to store date more convenient if you don’t want to allow your users to mess up with data input in that field
Update 11 Oct 2007 - The code is tested and working with CakePHP v1.2! Previous versions of the Framework throws an error.
1. Download a copy of the calendar and unzip it in your {cakeproject}/app/webroot/js directory. It’s possible to strip down the unnecessary files like examples, docs etc.
2. Create a file with name common.js with the following content:
function setActiveStyleSheet(link, title) {
var i, a, main;
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
a.disabled = true;
if(a.getAttribute("title") == title) a.disabled = false;
}
}
if (oldLink) oldLink.style.fontWeight = 'normal';
oldLink = link;
link.style.fontWeight = 'bold';
return false;
}
// This function gets called when the end-user clicks on some date.
function selected(cal, date) {
cal.sel.value = date; // just update the date in the input field.
if (cal.dateClicked && (cal.sel.id == "sel1" || cal.sel.id == "sel3"))
// if we add this call we close the calendar on single-click.
// just to exemplify both cases, we are using this only for the 1st
// and the 3rd field, while 2nd and 4th will still require double-click.
cal.callCloseHandler();
}
// And this gets called when the end-user clicks on the _selected_ date,
// or clicks on the "Close" button. It just hides the calendar without
// destroying it.
function closeHandler(cal) {
cal.hide(); // hide the calendar
// cal.destroy();
_dynarch_popupCalendar = null;
}
// This function shows the calendar under the element having the given id.
// It takes care of catching "mousedown" signals on document and hiding the
// calendar if the click was outside.
function showCalendar(id, format, showsTime, showsOtherMonths) {
var el = document.getElementById(id);
if (_dynarch_popupCalendar != null) {
// we already have some calendar created
_dynarch_popupCalendar.hide(); // so we hide it first.
} else {
// first-time call, create the calendar.
var cal = new Calendar(1, null, selected, closeHandler);
// uncomment the following line to hide the week numbers
// cal.weekNumbers = false;
if (typeof showsTime == "string") {
cal.showsTime = true;
cal.time24 = (showsTime == "24");
}
if (showsOtherMonths) {
cal.showsOtherMonths = true;
}
_dynarch_popupCalendar = cal; // remember it in the global var
cal.setRange(1900, 2070); // min/max year allowed.
cal.create();
}
_dynarch_popupCalendar.setDateFormat(format); // set the specified date format
_dynarch_popupCalendar.parseDate(el.value); // try to parse the text in field
_dynarch_popupCalendar.sel = el; // inform it what input field we use
// the reference element that we pass to showAtElement is the button that
// triggers the calendar. In this example we align the calendar bottom-right
// to the button.
_dynarch_popupCalendar.showAtElement(el.nextSibling, "Br"); // show the calendar
return false;
}
var MINUTE = 60 * 1000;
var HOUR = 60 * MINUTE;
var DAY = 24 * HOUR;
var WEEK = 7 * DAY;
// If this handler returns true then the "date" given as
// parameter will be disabled. In this example we enable
// only days within a range of 10 days from the current
// date.
// You can use the functions date.getFullYear() -- returns the year
// as 4 digit number, date.getMonth() -- returns the month as 0..11,
// and date.getDate() -- returns the date of the month as 1..31, to
// make heavy calculations here. However, beware that this function
// should be very fast, as it is called for each day in a month when
// the calendar is (re)constructed.
function isDisabled(date) {
var today = new Date();
return (Math.abs(date.getTime() - today.getTime()) / DAY) > 10;
}
function showFlatCalendar(id, holder, format, selectedHandler) {
var prnt = document.getElementById(holder);
var element = document.getElementById(id);
// construct a calendar giving only the "selected" handler.
var cal = new Calendar(0, null, selectedHandler);
// hide week numbers
cal.setDateFormat(format);
cal.weekNumbers = false;
cal.create(prnt);
cal.parseDate(element.value);
cal.show();
}
Put this file in {cakeproject}/app/webroot/js/. Basicaly this is a code snipet which I found in Calendar documentation.
3. Get this Helper and put it into date_picker.php file under your Helpers directory - {cakeproject}/app/view/helpers/:
/**
* Autocomplete Helper
*
* @author Nik Chankov
* @website http://nik.chankov.net
* @version 1.0.0
*/
uses('view/helpers/Form');
class DatePickerHelper extends FormHelper {
var $format = '%Y-%m-%d';
/**
*Setup the format if exist in Configure class
*/
function _setup(){
$format = Configure::read('DatePicker.format');
if($format != null){
$this->format = $format;
}
}
/**
* The Main Function - picker
*
* @param string $field Name of the database field. Possible usage with Model.
* @param array $options Optional Array. Options are the same as in the usual text input field.
*/
function picker($fieldName, $options = array()) {
$this->_setup();
$this->setFormTag($fieldName);
$htmlAttributes = $this->domId($options);
$divOptions['class'] = 'date';
$options['type'] = 'text';
$options['div']['class'] = 'date';
$options['after'] = $this->Html->link($this->Html->image('../js/jscalendar/img.gif'), '#', array('onClick'=>"return showCalendar('".$htmlAttributes['id']."', '".$this->format."'); return false;"), null, false);
$output = $this->input($fieldName, $options);
return $output;
}
function flat($fieldName, $options = array()){
$this->_setup();
$this->setFormTag($fieldName);
$htmlAttributes = $this->domId($options);
$divOptions['class'] = 'date';
$options['type'] = 'hidden';
$options['div']['class'] = 'date';
$hoder = '<div id="'.$htmlAttributes['id'].'_cal'.'"></div><script type="text/javascript">showFlatCalendar("'.$htmlAttributes['id'].'", "'.$htmlAttributes['id'].'_cal'.'", "'.$this->format.'", function(cal, date){document.getElementById(\''.$htmlAttributes['id'].''.'\').value = date});</script>';
$output = $this->input($fieldName, $options).$hoder;
return $output;
}
}
?>
In fact as you can see it’s usinf a textbox from form helper with added at the end calendar image for date picker.
4. Add the folowing code in your layout file between head tags:
<head>
...
<!-- Javascript includes-->
<?php echo $javascript->link('jscalendar/calendar.js'); ?>
<?php echo $javascript->link('jscalendar/lang/calendar-en.js'); ?>
<?php echo $javascript->link('common.js'); ?>
<!-- CSS Theme-->
<?php echo $html->css('../js/jscalendar/skins/aqua/theme');?>
...
</head>
....
</html>
or in every view which using that helper. Depends on your taste.
5. Add this helper in $helpers variable under the controller:
6. Use the Helper in the view files with:
for Popup
or Flat
*** Update *** Abdullah Zainul Abidin extended this helper so now it’s not required to touch layout by adding the scripts and css required. Now they are called automatically in the header. Check it out
Happy Baking!
Add to:


Hello,
There is a small bug in 3. wrt picker(): if there is no $options specified, domId(null) generates a string whereas an associative array is expected.
A quick fix: replace the default value
function picker($fieldName, $options = null) {
by
function picker($fieldName, $options = array()) {
Hi Pierre,
Thanks for your comment.
You are right! It’s good to stick to Cake’s Coding Style.
Changing immediately.
Hi!
I’m trying out the jscalendar with cakephp. Your manual is very good, and I have the datepicker up and running. But I would really like to have the calendar “flat” underneath text field.(instead of a popup). Any idea how to do this.
Thanks in advance!!
Carina
@Carina - Thanks for your comments!
About the Flat Calendar option - I just made some amendments which will allow you to make Flat Calendar field. The field which hold the data is hidden, so users wont mess it up. They can only pick from the Calendar and that’s it. Calendar feed the date value from this hidden field so every time it will be selected properly.
Hope that helps!
I did everything you said here, but it doesn`t work.
Fatal error: Call to undefined method DatePickerHelper::setFormTag() in cakePHP/app/views/helpers/date_picker.php on line 25
Thanks…
@DGC Thanks for visiting my page.
This code is tested and developed on CakePHP verison 1.2, I just check the 1.1 Api and I didnt see such function setFormTag(). Probably this is the problem.
I would like to hear if this is the case, and thanks, I will add a note in the article that this is applied for v.1.2
Regards
[...] Advanced Datepicker helper for CakePHP | Dev weblog A plug in date/time picker (tags: cakephp calendar) [...]
I just found this and it is great. Could do with writing up for the bakery.
How do I pass variables through to the calendar? For example:
showsTime = ‘true’
or any of the other variables as seen at:
http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3
Thanks silid,
I am considering to write an article there. BUT I wrote an article based on my Helper for Autocomplete, and so far it’s not publiched and it’s quite a while since then
I will try to publish it anyway.
About the parameters - you could modify the webroot/js/common.js mentioned in my article. There is the Calendar “setup”. As you can see there are some options already used there. Unfortunately, once they are set there will be available for all callendar instances. So far there is no solution to make this for each instance of the calendar.
Have a good day!
Hi, I’ve just modified date_picker.php to works with CakePHP version 1.1.x.
In case you are interested of:
function picker($fieldName, $options = array()) {
$this->Html->setFormTag($fieldName);
$htmlAttributes = array(’class’=>’date’,'type’=>’text’,’size’ => ‘10′, ‘id’=>(isset($options['id'])?$options['id']:$fieldName)); if(isset($options['value'])){ $htmlAttributes['value'] = $options['value'];
}
$output = $this->Html->input($fieldName, $htmlAttributes);
$output .= $this->Html->link($this->Html->image(’../js/jscalendar/img.gif’), ‘#’, array(’class’=>’calendar’, ‘onClick’=>”return showCalendar(’”.$htmlAttributes['id'].”‘, ‘”.((isset($options['format'])?$options['format']:$this->format)).”‘); return false;”), null, false);
return $output;
}
Thanks,
Alberto
Hi Nik,
I have a question, I probe the code on Firefox and works fine, but when it open the calendar on IE 7, appears in another position respect than the calendar icon, why?
Thanks!
Hello-
I am trying to locate the .js files: calendar-en.js and calendar.js.
Thank you.
@Alberto D’Este - Thanks, this code would help somebody.
@Sul - I am using such helper in a project and I checked it with IE as well and there is no problem like you mention. I think that this is a problem of CSS rather than Calendar problem. But this is just a guess.
@Dawn - Calendar JS is not my code
I wish to be but it is not. so you can download the calendar from it’s official web site http://www.dynarch.com/projects/calendar/
The download link is in the right column of the page. Hope that helps.
How do you change the default position of the calendar from below the field to the right of the field? I’ve tried to change it in common.js, from “Br” to “R”, which should align it to the right of the field according to the jscalendar reference manual, but nothing happens. Thanks for your taking the time to do this. It is a great help and works great otherwise!
@Joe - I am glad that helper helps somebody
About the positioning I made a test it’s working you should remember that there are 2 letters defining the position:
first one is the vertical positioning and the possible variants are: T=Top,C=Center,B=Bottom
The second one is the horizontal pozition and the possible variants are: L=Left,C=Center,R=Right
so if you want to align the calendar popup to center right you should replace BR with CR and this will do the job.
Hope this make sense!
Hi,
Thanks for the code.
When I use it from an Add View, the calendar displays as expected. However on an Edit View where a date value exists (and is displayed in the input box) The calendar displays without any of the graphics, just the numbers ‘floating’ transparently where the finished calender should be.
Any thoughts?
Hi Buzz, for me this sounds like missing CSS. Another reason is that other CSS overrule the calendar one.
Thanks! Works like a charm!
Thanks! Saved me a good bit of time on my current project.
Thank you. I find it very useful. I’ve modified the helper a bit so that you wouldn’t need to add the links in every view that uses this helper. You can check it out on my website. Thank you once again for the great work.
Hi Abdullah,
I am glad that you found this helpful.
Adding the lines for JavaScript includes doesn’t need to be included in every action which you had.
The idea behind is that you can add the JS code in your layout, so once you do this the only code which you need to add in your helpers is the $datePicker->picker() function.
I think your addition is good but only if you have one date field per screen, otherwise you will load the JavaScript as many times as you have Date Field, which should work, but is not the best practice
I’m sorry but I’m a bit confused now. When I look at the source code of the page rendered using my modified helper, the javascript include lines are rendered only once but way on top of the page even when I have two or three date fields which use the datepicker. In fact, so far on top of the page that it actually renders even before the doctype (Which is probably not good either). I’ll see what I can do to fix it. But when you said load the javascript many times you meant there will be many times the javascript include lines are rendered right? or does cakephp actually loads them many times on the server? Thanks for the heads up though.
Abdullah,
I didn’t saw that you using beforeRender(). I thought you just print the script part just right before printing the input tag.
It is my fault, I didn’t understood your idea.
About printing the code above the doctype - I also think it’s not the best way.
Anyway, important is that this helper do what it supposed to do
Hi Nik,
Just like to mention that I solved the output before docman problem. Now it’s output is in the header part proper. Just in case you’re interested you can check out the link. Thank you again for this great helper.
Really a brilliant piece of code integration, thanx alot..You made my lunch time longer….lol…:-D
one small Q, Im trying to add the date and time, i pass the helper the params $datePicker->picker(’Sitepage/publish_date’, array(’format’=>’%Y-%m-%d [%W] %H:%M’)…but it doesnt show the calendar with the time picker:(
Answer to my own Q, here we go for cakephp1.1:
function picker($fieldName, $options = null, $showsTime = null) {
$this->Html->setFormTag($fieldName);
$htmlAttributes = array(’class’=>’date’,'type’=>’text’,’size’ => ‘10′, ‘id’=>(isset($options['id'])?$options['id']:$fieldName));
if(isset($options['value'])){
$htmlAttributes['value'] = $options['value'];
}
$output = $this->Html->input($fieldName, $htmlAttributes);
//var_dump($options);
$output .= $this->Html->link($this->Html->image(’../js/jscalendar-1.0/img.gif’), ‘#’, array(’class’=>’calendar’, ‘onClick’=>”return showCalendar(’”.$htmlAttributes['id'].”‘, ‘”.((isset($options['format'])?$options['format']:$this->format)).”‘, ‘”.$showsTime.”‘); return false;”), null, false);
return $output;
}
Then U simple call it with:
echo $datePicker->picker(’Sitepage/publish_date’, array(’format’=>’%Y-%m-%d %H:%M’), ‘yes’);
Hi Nik,
Got you very nice code working in my CakePHP 1.1 test app, using jarret’s fix. The only problem I’m having is that the popup calendar is being drawn for the full width of the web page! Do you have any ideas what the problem might be?
Thanks very much,
Steve
Hi SteveH,
Just to clarify the issue with Alberto’s approach:
the “problem” is that he miss to add the function _setup(); which actually extract the format from the Configure class. I take a look and in CakePHP1.1.x.x there is Configure class and I think the following code should work as a dream:
$this->_setup();
$this->Html->setFormTag($fieldName);
$htmlAttributes = array(’class’=>’date’,'type’=>’text’,’size’ => ‘10?, ‘id’=>(isset($options[’id’])?$options[’id’]:$fieldName)); if(isset($options[’value’])){ $htmlAttributes[’value’] = $options[’value’];
}
$output = $this->Html->input($fieldName, $htmlAttributes);
$output .= $this->Html->link($this->Html->image(’../js/jscalendar/img.gif’), ‘#’, array(’class’=>’calendar’, ‘onClick’=>”return showCalendar(’”.$htmlAttributes[’id’].”‘, ‘”.((isset($options[’format’])?$options[’format’]:$this->format)).”‘); return false;”), null, false);
return $output;
}
About calendar being drawn full width of the page: it’s a css issue. CakePHP define table to be with width:100% which automatically make table of the popup 100%. The solution is to add following css in your css files:
this will fix the issue.
Nik,
Your suggestions worked a treat!
Blagodarya,
Steve
Thanks for contributing this code to the community Nik! I love calendar date pickers!
I’m a cake noobie and need some help. I used the code you provided for CakePHP1.1.x.x, but I get an error:
Notice: Undefined offset: 1 in /cake/libs/view/helpers/html.php on line 947
Aside from that error showing up, the calendar picker appears to be working. Should I still be using this code in my edit page:
picker(’datefield’);?>
Any ideas? Your help is greatly appreciated! Thanks!
Jen
@JenR - can you send me some code, because as far I can see the function which causing that Notice is setFormTag() and there only the model and field was set.
I think the problem comes, because you using field which is not exist in database.
Oh duh! I didn’t put my own model name and db field in, it works now.
picker(’Calendar/start_date’);?>
Thanks for pointing me in the right direction!!
im getting an error saying that calender is not defined in my error console.
after creating everything as described here, i get the input field, but at the end of it theres just a little blue square and when i click it, nothing happens.
hashem, I think you didn’t put the code for calendar js in the right place, or you didn’t get the origilan calendar script from http://www.dynarch.com/projects/calendar/. the js code shown here is just an extension how to connect field and js, but the core js is different. About blue square - probably the icon is missing in your wwwroot/js/jscalendar/ folder.
Hope this helps
well i did get my code from that site and i put it in the webroot/js folder as mentioned above, but it still didnt work.
and what do you mean by “probably the icon is missing in your wwwroot/js/jscalendar/ folder.”
just so you know, im just a co-op student working on the portal for the company that hired me, as part of my co-op, so im no programming genious or anything.
Hashem, if you be able to show me the site where you experienced this problem probably I could give you a hint. Other than that - I cannot help much.
well i would but i cant, sorry. thanks anyway, ill let you know if i can figure out something specific
Hi Nik !
It’s really a great work,
but I need your help
th epicker works great saving in my databese correctly, but cause a problem to the debugger so that it couldn’t do a redirect
it says:
Warning: implode() [function.implode]: Bad arguments. in /var/cake/libs/debugger.php on line 497
where can it be the problem?
Hi,
Great work ! Works fine under 1.2. A bit hard to adapt to 1.1 for a noobie like me.
Just a question : does somebody notice that the icon beside the date field is on a new line ? I looked at the html source page, and I think it must come from a CSS attribute. But, which one ? I use the cake.generic.css default file.
i want to thanks who posted the cakephp 1.1.x version, without i would spend a lot of time trying to change then 1.2 version. Thanks a lot to everybody and a special thanks to Nick for his tip