Using the Unobtrusive Date Picker Widget in CakePHP
First things first
- Download and extract the Unobtrusive Date-Picker Widget here: http://www.frequency-decoder.com/2006/10/02/unobtrusive-date-picker-widgit-update
- Copy the 'css/datepicker.css' file to 'app/webroot/css'
- Copy the 'js/datepicker.js' file to 'app/webroot/js'
- Copy the contents of the media folder to 'app/webroot/img/datepicker'
- Open the datepicker.css file in your 'app/webroot/css' folder. Using the Find/Replace functionality of your editor replace all occurences of '../media' with '../img/datepicker'
Edit the default view
Note: If you do not have a default.thtml file then you will need to create one, for simplicity you can just copy Cake's default one from 'cake/libs/view/templates/layouts' to 'app/views/layouts' and edit the latter.
Put the following before the closing head tag
Download code
<?php echo $html->css('datepicker')."\n"; ?>
<?php echo $javascript->link('datepicker.js')."\n"; ?>
Note: This may give you an error when you try to load your page, that is because you are trying to use the javascript helper but you have not defined it in your controller. Since it will be used site-wide the easiest thing is to define it in 'app_controller.php'.
Create 'app/app_controller.php' and enter the following:
Download code
<?php
class AppController extends Controller
{
var $helpers = array('Javascript');
}
?>
Create afterFind and beforeSave methods
So in order to separate the date from the time when using the dateTime format in MySQL you could use a custom query string, but this was not recommended to me so instead I decided to run an after find to create a pseudo-field.
You will need to recombine the fields before it is written back to the database otherwise the information will not be properly saved, this is where the beforeSave method comes into play.
Edit your model and enter the following methods:
Download code
<?php
/*
* The validation below is optional, just to give you an idea
* of how to validate these fields.
*/
var $validate = array(
'dateOnly' => '/[0-9]{2}[\-\/\.][0-9]{2}[\-\/\.][0-9]{2,4}$/i',
'headline' => VALID_NOT_EMPTY,
'detail' => VALID_NOT_EMPTY
);
// Extra form validation, since VALID_NOT_EMPTY does not work on
// these fields.
function validates() {
$event = $this->data['Event'];
if(empty($event['date_hour']) || empty($event['date_meridian']) || empty($event['date_meridian']))
$this->invalidate('date');
$errors = $this->invalidFields();
return count($errors) == 0;
}
/*
* The validation above is optional, just to give you an idea
* of how to validate these fields.
*/
function afterFind($results) {
// Create a dateOnly pseudofield using date field.
foreach ($results as $key => $val) {
if (isset($val['Event']['date']))
$results[$key]['Event']['dateOnly'] = date('m-d-Y',strtotime($val['Event']['date']));
}
return $results;
}
function beforeSave()
{
// Convert 12 hour to 24 hour
if($this->data['Event']['date_meridian'] == 'pm')
$hour = $this->data['Event']['date_hour'] + 12;
else
$hour = $this->data['Event']['date_hour'];
// Get month day and year from date string
$timestamp = strtotime(str_replace('-','/',$this->data['Event']['dateOnly']));
$month = date('m',$timestamp);
$day = date('d',$timestamp);
$year = date('Y',$timestamp);
$this->data['Event']['date'] = date('Y-m-d H:i:s', mktime(
$hour,
$this->data['Event']['date_min'],
null,
$month,
$day,
$year));
return true;
}
?>
Note: Be sure to substitue ['Event'] with your App name and ['date'] with the field in the database that contains the dateTime value.
Edit your view
You will need to modify you view to display the new format, below is what I used. Feel free to make any necessary changes for your application.
Example view:
Download code
<table cellpadding="0" cellspacing="0" class="view">
<tr>
<td><span class="title"><?php echo $form->labelTag('Event/dateOnly', 'Date');?></span></td>
<td>
<?php echo $html->input('Event/dateOnly', array('size' => '15', 'class' => 'w8em format-m-d-y divider-dash highlight-days-12 no-transparency'));?>
</td>
<td><?php echo $html->tagErrorMsg('Event/dateOnly', 'Please select the Date.');?></td>
</tr>
<tr>
<td><span class="title"><?php echo $form->labelTag('Event/date', 'Time');?></span></td>
<td>
<?php echo $html->hourOptionTag('Event/date'); ?>
<?php echo $html->minuteOptionTag('Event/date'); ?>
<?php echo $html->meridianOptionTag('Event/date'); ?>
<?php echo $html->checkbox('Event/allday', null, array());?>
<?php echo $form->labelTag('Event/allday', 'Allday');?>
</td>
<td><?php echo $html->tagErrorMsg('Event/date', 'Please select the Time.');?></td>
</tr>
</table>
You should now have a fully working date picker. If you are having problems, please post your comments and I will try to help resolve them.
Screenshots:
http://www.nexgentec.com/bakery/date-picker.jpg http://www.nexgentec.com/bakery/date-picker2.jpg
Comments
Question
1 Need help with date picker view...
If you could look at the code and make a suggestion or anyone for that matter I would really appreciate the help.
P.S.
If you want to email me that would work too, valendesigns@gmail.com
Comment
2 All working now
So if you're using cake 1.2 then things have changed and so when you make a form the regular way of $form->create() then the right way to do the view parts would be:
=$form->input('Event/dateOnly', array('size' => '15', 'class' => 'w8em format-m-d-y divider-dash highlight-days-12 no-transparency'));?>
=$form->hour(); ?> =$form->minute(); ?> =$form->meridian(); ?>
and in the model class you need to change the [date_hour] [date_min] and [date_meridian] to [hour] [min] and [meridian] and things should work properly now. Hope this helps anyone who couldn't get this to work.
Question
3 did you ever try
like the paginator/sorting-table:
http://www.frequency-decoder.com/demo/table-sort-revisited/paginate/
i don't know how easy this one is to get along with cakePHP
Comment
4 This does not appear to work for 1.2
First, I had to add some code to handle the case that the date is null in the afterFind method of the model.
Then I'm completely clueless as to how the date_hour and date_meridian that are referenced in the validates method are created. Either they're missing, or I'm missing somthing else.
So by including the code:
<?=$form->input('dateOnly', array('size' => '15', 'class' => 'w8em format-m-d-y divider-dash highlight-days-12 no-transparency'));?>
I get HTML like:
<div class="input text">
<label for="DateOnly">Date Only</label>
<input name="data[Job][dateOnly]" type="text" size="15" class="w8em format-m-d-y divider-dash highlight-days-12 no-transparency" value="11-24-2008" id="JobDateOnly" />
</div>
Which I'm guessing means the class is getting added to the wrong HTML tag.
Any suggestions?
Comment
5 OK found my problem
Thank goodness for Firebug - I was able to browse the DOM and see the Cake error there. Since it couldn't find the JS file, it was replacing the JS with HTML that was intended to show me the error.
Unfortunately, the browser just swallows that error, since it is embedded between script tags.
Comment
6 Excellent documentation
it really helped me a lot!!!!thanx Marcel Manning (mmanning) for this code and documentation.
Comment
7 RE: the other great stuff on that side?
It looks nice, but I would not recommend using it with CakePHP, since you can easily have the same functionality with AJAX.
The author states: "the script was developed for “worst case scenarios” and if at all possible, you should use an Ajax or simple server-side pagination solution instead." which I agree with.
Comment
8 RE: Excellent documentation
Thank You Tahmeed Alam, seems as though this is still the only Date Picker article in the Bakery.
Comment
9 Date fields the Cake 1.2 way!
I wasn't keen on having to add the afterFind/beforeSave methods to all my models that used this so did some digging and I have it working a bit more Cake-like .. although I'm sure it could be better.
Drop the following into your views and one date picker will update separate day-month-year select lists.
echo $form->day('dateField', date('d'), array('id' => 'FieldId-dd'), false)." - ";
echo $form->month('dateField', date('m'), array('id' => 'FieldId-mm'), false)." - ";
echo $form->year('dateField', '1980', date('Y'), null, array('id' => 'FieldId', 'class' => 'w8em split-date divider-dash highlight-days-12 no-transparency'), false);
Substitute 'dateField' for your date field and 'FieldId' for your field's id (tends to follow ModelFieldName format) and hey presto!
No need to do any model trickery as the date fields are now formatted as Cake likes them.
This works by specifying the 'split-date' class which breaks the date across 3 text fields or select lists and the widget does the hard work for you as long as you only add the class information to the year field and give your fields the correct ids:
Unfortunately you can't use Cake's automagic $form->input as it doesn't allow you to specify 3 different ids (it takes the fields id and adds Day, Month, Year) and adds the class declaration to each part of the date (resulting in 3 date pickers).
This results in a bit more code in the view, especially when you have to add in labels and divs etc. but I can handle that in favour of not having to add the extra methods into the model to create a false date field to work on.
Hope this proves useful,
Comment
10 Using additional date pickers
Yours seems to be the easier choice rather than adding the afterFind/beforeSave methods in 1.2, I tried it and it worked. Just to add additional info to those who will be trying this with more than one date picker.
The solution is easy this field (FieldId) will give one date picker:
* day: FieldId-dd
* month: FieldId-mm
* year: FieldId (no additional)
to add additional date picker just append 0-9 etc. to the FieldId:
* day: FieldId0-dd
* month: FieldId0-mm
* year: FieldId0 (no additional)
will give you additional instance of the date picker.
Question
11 Adding time and future years?
This works great. Appreciate the guidance.
I was wondering if you could provide an example of add/incorporating the time selection for your Cake-like date picker and allowing for years in the future?
Thanks for any help and your time!
Comment
12 Jumped the gun...
echo $form->hour('dateField', true, date('H') ) . " : ";
echo $form->minute('dateField', date('i') );
Thanks for humoring me!
Comment
13 Blank email notifications
Glad you found a solution :)
I would have replied, but the article comment email notifications from the site are showing blank and I did not know which article someone had commented on?!?
Comment
14 Error
uncaught exception: Index or size is negative or greater than the allowed amount (NS_ERROR_DOM_INDEX_SIZE_ERR)
[Break on this error] var datePickerController=(function dateP...;script.setAttribute("charset","utf-8");
Any idea?
Login To Submit A Comment