Advanced Datepicker helper for CakePHP

This is a really old post. I’ve wrote a new one which you can find it here


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:

var oldLink = null;
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/:

<?php
/**
* 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->setEntity($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->setEntity($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:

<html>
  <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:

<?php
class TestController extends AppController {
    var $helpers = array(..., 'DatePicker');
    ....
}

6. Use the Helper in the view files with:
for Popup

    <?php echo $datePicker->picker('datefield');?>

or Flat

    <?php echo $datePicker->flat('datefield');?>

*** 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!

66 thoughts on “Advanced Datepicker helper for CakePHP

  1. jarrett

    jarrett :
    For anyone who needs to get the date and time.
    Add the following to changes to date_picker.php
    [helper]
    line 13
    var $format = ‘%Y-%m-%d %H:%M’;
    line 31
    function picker($fieldName, $options = array(), $showsTime = null) {
    line 38
    $options[‘after’] = $this->Html->link($this->Html->image(’{Your/Image/Location}’), ‘#’, array(’onClick’=>”return showCalendar(’”.$htmlAttributes[‘id’].”‘, ‘”.$this->format.”‘, ‘”.$showsTime.”‘); return false;”), null, false);
    [/helper]
    Then in your view that needs time as well
    [view]
    echo $datePicker->picker(’start_date’, array(’format’=>’%Y-%m-%d %H:%M’), ‘yes’);
    [/view]

  2. Anand

    Hi, I found the helper very useful.Thanks for such a nice bunch of code.In my project I need to restrict user to choose date between a “higher date” and a “lower” date.How can I achieve that using this helper

  3. Chris

    Thanks for the article. I seem to be having a similar problem to someone else however I am using cakephp 1.2 (not 1.1). Comes up with error “Warning (512): Method DatePickerHelper::setFormTag does not exist [CORE/cake/libs/view/helper.php, line 148]”

  4. nore

    I’m getting a JS error about _dynarch_popupCalendar, so loading the calendar is failing.
    What is it anyway? and what can I do?

  5. Yves Jaques

    This helper still works great even with CakePHP 1.3. Just need to pay attention to two very important things: You MUST use JSCalendar version1 NOT 2. Otherwise you will just get a lot of errors. Secondly, if using CakePHP1.3 you must disable escaping of the image in the date_picker.php helper by changing this line: $options[‘after’] = $this->Html->link($this->Html->image(‘../js/jscalendar/cal.gif’), ‘#’, array(‘escape’ => false,’onClick’=>”return showCalendar(‘”.$htmlAttributes[‘id’].”‘, ‘”.$this->format.”‘); return false;”), null, false);

  6. Lee sea wuyhs

    I adding : Advanced Datepicker helper for CakePHP. . Error:
    [php]
    Debugger::handleError() – CORE\cake\libs\debugger.php, line 306
    Helper::call__() – CORE\cake\libs\view\helper.php, line 154
    Overloadable::__call() – CORE\cake\libs\overloadable_php5.php, line 50
    DatePickerHelper::setFormTag() – [internal], line ??
    DatePickerHelper::picker() – APP\views\helpers\date_picker.php, line 51
    include – APP\views\blogs\add_posts.ctp, line 211
    View::_render() – CORE\cake\libs\view\view.php, line 723
    DebugView::_render() – APP\plugins\debug_kit\views\debug.php, line 43
    View::render() – CORE\cake\libs\view\view.php, line 419
    DebugView::render() – APP\plugins\debug_kit\views\debug.php, line 85
    Controller::render() – CORE\cake\libs\controller\controller.php, line 913
    Dispatcher::_invoke() – CORE\cake\dispatcher.php, line 207
    Dispatcher::dispatch() – CORE\cake\dispatcher.php, line 171
    [main] – APP\webroot\index.php, line 83
    [/php]

    ———-
    Can you send to me demo : Advanced Datepicker helper for CakePHP.

    -Email: leeseawuyhs@yahoo.com
    Thanks.

  7. jobish

    If u need time
    pls replace

    $options[‘after’] = $this->Html->image(‘calendar.png’, array(‘id’ => $fieldName.’-trigger’)).’ Calendar.setup({trigger: “‘.$fieldName.’-trigger”, inputField: “‘.$htmlAttributes[‘id’].'”, dateFormat: “‘. $this->format.'”, showTime: 12 }); ‘;

  8. newjob

    I can’t save db with this format ( %y-%m-%d %H:%M) into mysql. pls tell me! Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *