Revision history for JwCalendar
Revision [19177]
Last edited on 2008-01-28 00:14:30 by JavaWoman [Modified links pointing to docs server]No Differences
Additions:
=====Calendar action=====
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1&cid=0BEA6 Semantic (X)HTML Markup: Using Tables Appropriately]]
Looking at a common "calendar face" the candidates for header cells and caption are obvious: clearly the (abbreviated) day names are headers, and the name of the month at the top logically is a caption. That leaves the navigation, however, which I've moved to a separate section at the bottom. While I was at it, I took up [[DarTar]]'s suggestion for a link (back) to the current month since moving the navigation links to the bottom left a space in the middle.
The result looks like this (now that my code is active, this page can have multiple calendars - see another example below):
@@{{calendar}}implemented with **##""{{calendar}}""##**@@
==Making it accessible==
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a ##summary## attribute to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a ##title## attribute. Similarly, the data cell for "today" (if shown) gets a ##title## as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
More about accessible table markup:
~-[[http://www.w3.org/TR/WCAG10-HTML-TECHS/#data-tables 5.1 Tables of data]]
~-[[http://www.webaim.org/techniques/tables/2 Creating Accessible Tables, Part 2: Data Tables]]
===Extra functionality===
=="Dynamic" and "static" calendars==
My variant of the Calendar action does all that the original GmBowenCalendar does, and one thing extra:
~-without any parameters, it generates a calendar for the current month, with navigation links, leading to:
~-it picks up on URL parameters for month and/or year, and shows the calendar for the specified month, also with navigation links
~-however, if parameters are provided in the action code itself a static calendar is shown for the month specified (taking the default for a missing parameter from the URL parameter(s) or the current month)
The result is that you can show a dynamic calendar which "reacts" to URL parameters //in addition to// any number of static calendars for a specific month.
I'll include code for a static month below - if (as long as) my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}implemented with **##""{{calendar month="3" year="2006"}}""##**@@
===Different code===
==A pattern for an action==
The new code follows a specific "pattern" which I use for all my (new or modified) action code. In general it goes like this:
~1)**Constants section**: A section where all constants are defined; this includes constants used as defaults for possible optional acrion parameters as well as constants defining user-interface strings (so that whatever needs to be translated for i18n is grouped together), and any other constants needed. Also "alsmost" constants like lookup tables are defined here.
~1)**Parameters section**: A section where parameters are read, and validated, using whatever is defined as defaults for optional parameters (constants, or dynamic defaults such as "current month"). In this case, invalid or otherwise unusable parameters are silently ignored (using defaults instead); depending on the purpose of an action, an invalid parameter (or missing required parameter) may generate an error message instead.
~1)**Data preparation section**: A section where input is further processed into all variables needed to generate the output.
~1)**Output section**: At this point all data is prepared, so the final section does nothing but generate output: no new data is generated here, we only look up data prepared in the parameter or data preparation section (we can loop through an array though). (This is the same approach as with using a templating solution such as [[http://smarty.php.net/ Smarty]].)
This results in a clear and consistent logic of the code, and separation of process and data from presentation of the data.
==Dealing with multiple calendars==
The original code had a little function to derive the last day in a month; not only was this a somewhat inefficient, but having a function conflicted with including the code multiple times. The function has been replaced by (simpler) code in the data preparation section.
==Calendar generating logic==
Another difference is in the logic used for the start of the first week (when we may need to generate "blank" cells): this is taken out of the (original) loop, resulting in more logical and efficient code. The inverse of that is added at the end so that where necessary blank cells are added at the end of the table (having fewer cells in a row is not valid markup!).
==Internationalization==
Apart from two or three strings (defined in the constants section), the output produced by the action is completely internationalized: by using the appropriate functions the names for months and days (short and long) are already corresponding to the defined locale.
==Documentation==
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===The code===
==PHP code for the action==
Note that I've classified this as "version 0.8" since there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
%%(php)
<?php
/**
* Display a calendar face for a specified or the current month.
*
* Specifying a month and/or year in the action itself results in a "static" calendar face without
* navigation; conversely, providing no parameters in the action results in a calendar face with
* navigation links to previous, current and next month, with URL parameters determining which
* month is shown (with the current month as default).
*
* You can have one "dynamic" (navigable) calendar on a page (multiple ones would just be the same)
* and any number of "static" calendars.
*
* The current date (if visible) gets a special class to allow a different styling with CSS.
*
* Credit:
* This action was inspired mainly by the "Calendar Menu" code written by
* {@link http://www.blazonry.com/about.php Marcus Kazmierczak}
* (© 1998-2002 Astonish Inc.) which we traced back as being the ultimate origin of this code
* although our starting point was actually a (probably second-hand) variant found on the web which
* did not contain any attribution.
* However, not much of the original code is left in this version. Nevertheless, credit to
* Marcus Kazmierczak for the original that inspired this, however indirectly: Thanks!
*
* @package Actions
* @subpackage Date and Time
* @name Calendar
*
* @author {@link http://wikka.jsnx.com/GmBowen GmBowen} (first draft)
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (more modifications)
* @version 0.8
* @since Wikka 1.1.6.0
*
* @input integer $year optional: 4-digit year of the month to be displayed;
* default: current year
* the default can be overridden by providing a URL parameter 'year'
* @input integer $month optional: number of month (1 or 2 digits) to be displayed;
* default: current month
* the default can be overridden by providing a URL parameter 'month'
* @output data table for specified or current month
*
* @todo - take care we don't go over date limits for PHP with navigation links
* - configurable first day of week
*/
// ***** CONSTANTS section *****
define('MIN_DATETIME', strtotime('1970-01-01 00:00:00 GMT')); # earliest timestamp PHP can handle (Windows and some others - to be safe)
define('MAX_DATETIME', strtotime('2038-01-19 03:04:07 GMT')); # latest timestamp PHP can handle
define('MIN_YEAR', date('Y',MIN_DATETIME));
define('MAX_YEAR', date('Y',MAX_DATETIME)-1); # don't include partial January 2038
// not-quite-constants
$daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
define('CUR_YEAR', date('Y',mktime()));
define('CUR_MONTH', date('n',mktime()));
// format string for locale-specific month (%B) + 4-digit year (%Y) used for caption and title attributes
// NOTE: monthname is locale-specific but order of month and year may need to be switched: hence the double quotes!
define('LOC_MON_YEAR', "%B %Y"); # i18n
define('FMT_SUMMARY', "Calendar for %s"); # i18n
define('TODAY', "today"); # i18n
// ***** END CONSTANTS section *****
// ***** (ACTION) PARAMETERS Interface *****
// set parameter defaults: current year and month
$year = CUR_YEAR;
$month = CUR_MONTH;
// get and interpret parameters
// 1) overrride defaults with parameters provided in URL (accept only valid values)
if (isset($_GET['year']))
{
$uYear = (int)$_GET['year'];
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR) $year = $uYear;
}
if (isset($_GET['month']))
{
$uMonth = (int)$_GET['month'];
if ($uMonth >= 1 && $uMonth <= 12) $month = $uMonth;
}
// 2) override with parameters provided in action itself (accept only valid values)
$hasActionParams = FALSE;
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
switch ($param)
{
case 'year':
$uYear = (int)trim($value);
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR)
{
$year = $uYear;
$hasActionParams = TRUE;
}
break;
case 'month':
$uMonth = (int)trim($value);
if ($uMonth >= 1 && $uMonth <= 12)
{
$month = $uMonth;
$hasActionParams = TRUE;
}
break;
}
}
}
// ***** (ACTION) PARAMETERS Interface *****
// ***** DERIVED VARIABLES *****
// derive which weekday the first is on
$datemonthfirst = sprintf('%4d-%02d-%02d',$year,$month,1);
$firstwday = strftime('%w',strtotime($datemonthfirst)); # i18n
// derive (locale-specific) caption text
$monthYear = strftime(LOC_MON_YEAR,strtotime($datemonthfirst)); # i18n
$summary = sprintf(FMT_SUMMARY, $monthYear); # i18n
// derive last day of month
$lastmday = $daysInMonth[$month - 1];
if (2 == $month) # correct for leap year if necessary
{
if (1 == date('L',strtotime(sprintf('%4d-%02d-%02d',$year,1,1)))) $lastmday++;
}
// derive "today" to detect when to mark this up in the calendar face
$today = date("Y:m:d",mktime());
// build navigation variables - locale-specific (%B gets full month name)
// FIXME: @@@ take care we don't go over date limits for PHP
if (!$hasActionParams)
{
// previous month
$monthPrev = ($month-1 < 1) ? 12 : $month-1;
$yearPrev = ($month-1 < 1) ? $year-1 : $year;
$parPrev = "month=$monthPrev&year=$yearPrev";
$urlPrev = $this->Href('', '', $parPrev);
$titlePrev = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearPrev,$monthPrev,1)));# i18n
// current month
$parCur = 'month='.CUR_MONTH.'&year='.CUR_YEAR;
$urlCur = $this->Href('', '', $parCur);
$titleCur = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',CUR_YEAR,CUR_MONTH,1))); # i18n
// next month
$monthNext = ($month+1 > 12) ? 1 : $month+1;
$yearNext = ($month+1 > 12) ? $year+1 : $year;
$parNext = "month=$monthNext&year=$yearNext";
$urlNext = $this->Href('', '', $parNext);
$titleNext = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearNext,$monthNext,1)));# i18n
}
// build array with names of weekdays (locale-specific)
$tmpTime = strtotime("this Sunday"); # get a starting date that is a Sunday
$tmpDate = date('d',$tmpTime);
$tmpMonth = date('m',$tmpTime);
$tmpYear = date('Y',$tmpTime);
for ($i=0; $i<=6; $i++)
{
$aWeekdaysShort[$i] = strftime('%a',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
$aWeekdaysLong[$i] = strftime('%A',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
}
// ***** END DERIVED VARIABLES *****
// ***** OUTPUT SECTION *****
?>
<table cellpadding="2" cellspacing="1" class="calendar" summary="<?php echo $summary;?>">
<caption><?php echo $monthYear;?></caption>
<thead>
<tr>
<?php
for ($i=0; $i<=6; $i++)
{
?>
<th scope="col" width="26" abbr="<?php echo $aWeekdaysLong[$i];?>"><?php echo $aWeekdaysShort[$i];?></th>
<?php
}
?>
</tr>
</thead>
<tbody class="face">
<?php
// start row for first week (if it doesn't start on Sunday)
if ($firstwday > 0)
{
echo " <tr>\n";
}
// fill start of first week with blank cells before start of month
for ($i=1; $i<=$firstwday; $i++)
{
echo ' <td> </td>'."\n";
}
// loop through all the days of the month
$day = 1;
$wday = $firstwday;
while ($day <= $lastmday)
{
// start week row
if ($wday == 0)
{
echo " <tr>\n";
}
// handle markup for current day or any other day
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
if ($calday == $today)
{
echo ' <td title="'.TODAY.'" class="currentday">'.$day."</td>\n";
}
else
{
echo ' <td>'.$day."</td>\n";
}
// end week row
if ($wday == 6)
{
echo " </tr>\n";
}
// next day
$wday = ++$wday % 7;
$day++;
}
// fill week with blank cells after end of month
if ($wday > 0)
{
for ($i=$wday; $i<=6; $i++)
{
echo ' <td> </td>'."\n";
}
}
// end row for last week
if ($wday < 6)
{
echo " </tr>\n";
}
?>
</tbody>
<?php
// generate navigation only for calendar without (valid) action parameters!
// FIXME: @@@ take care we don't go over date limits for PHP
if ($hasActionParams === FALSE)
{
?>
<tbody class="calnav">
<tr>
<td colspan="3" align="left" class="prevmonth"><a href="<?php echo $urlPrev;?>" title="<?php echo $titlePrev;?>"><<</a></td>
<td align="center" class="curmonth"><a href="<?php echo $urlCur;?>" title="<?php echo $titleCur;?>">=</a></td>
<td colspan="3" align="right" class="nextmonth"><a href="<?php echo $urlNext;?>" title="<?php echo $titleNext;?>">>></a></td>
</tr>
</tbody>
<?php
}
?>
</table>
<?php
// ***** END OUTPUT SECTION *****
?>%%
==Corresponding CSS code==
The code provides a nice layout for the data table and has some comments with hints for variants. To be added at the end of ##wikka.css##.
%%(css)
/* Calendar styling - added 2004-11-30 - updated 2004-12-01 */
/* general styling */
table.calendar {
color: #000000;
background-color: #CCCCCC; /* comment out to have space between cells same color as page background */
/*border-collapse: collapse;*/ /* would make single-width borders, ignoring cell-spacing */
}
table.calendar caption {
background-color: #CCCCCC;
font-weight: bold;
line-height: 1.6em;
}
table.calendar thead {
background-color: #CCCCCC;
}
table.calendar tbody.face {
background-color: #CCCCCC;
}
table.calendar tbody.calnav {
background-color: #CCCCCC;
}
/* styling for some specific elements */
table.calendar thead th {
/*border: 1px solid #000000;*/ /* uncomment to have border around day name headers (will be page background if table background is undefined) */
padding: 1px;
text-align: center;
font-size: 85%;
width: 26px;
}
table.calendar tbody.face td {
border: 1px solid #000000;
text-align: right;
}
table.calendar td.currentday {
color: #993333;
background-color: #AAAAAA;
font-weight: bold;
}
/* styling of calendar navigation */
table.calendar tbody.calnav {
font-weight: bold;
}
table.calendar td.prevmonth {
text-align: left;
font-size: 85%;
}
table.calendar td.curmonth {
text-align: center;
}
table.calendar td.nextmonth {
text-align: right;
font-size: 85%;
}
table.calendar a:link {
color: #993333;
text-decoration: none;
}
table.calendar a:visited {
color: #993333;
text-decoration: none;
}
table.calendar a:hover {
color: #993333;
}
table.calendar a:active {
color: #993333;
text-decoration: none;
}
%%
===Comments?===
Comments and suggestion welcome. And please give it a good workout (using combinations of URL parameters and action parameters!) before including it in the upcoming release.
Finally: The ##@since## tag in the documentation block assumes this will be included in Wikka release 1.1.6.0 - adapt or remove as needed...
--JavaWoman
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1&cid=0BEA6 Semantic (X)HTML Markup: Using Tables Appropriately]]
Looking at a common "calendar face" the candidates for header cells and caption are obvious: clearly the (abbreviated) day names are headers, and the name of the month at the top logically is a caption. That leaves the navigation, however, which I've moved to a separate section at the bottom. While I was at it, I took up [[DarTar]]'s suggestion for a link (back) to the current month since moving the navigation links to the bottom left a space in the middle.
The result looks like this (now that my code is active, this page can have multiple calendars - see another example below):
@@{{calendar}}implemented with **##""{{calendar}}""##**@@
==Making it accessible==
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a ##summary## attribute to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a ##title## attribute. Similarly, the data cell for "today" (if shown) gets a ##title## as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
More about accessible table markup:
~-[[http://www.w3.org/TR/WCAG10-HTML-TECHS/#data-tables 5.1 Tables of data]]
~-[[http://www.webaim.org/techniques/tables/2 Creating Accessible Tables, Part 2: Data Tables]]
===Extra functionality===
=="Dynamic" and "static" calendars==
My variant of the Calendar action does all that the original GmBowenCalendar does, and one thing extra:
~-without any parameters, it generates a calendar for the current month, with navigation links, leading to:
~-it picks up on URL parameters for month and/or year, and shows the calendar for the specified month, also with navigation links
~-however, if parameters are provided in the action code itself a static calendar is shown for the month specified (taking the default for a missing parameter from the URL parameter(s) or the current month)
The result is that you can show a dynamic calendar which "reacts" to URL parameters //in addition to// any number of static calendars for a specific month.
I'll include code for a static month below - if (as long as) my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}implemented with **##""{{calendar month="3" year="2006"}}""##**@@
===Different code===
==A pattern for an action==
The new code follows a specific "pattern" which I use for all my (new or modified) action code. In general it goes like this:
~1)**Constants section**: A section where all constants are defined; this includes constants used as defaults for possible optional acrion parameters as well as constants defining user-interface strings (so that whatever needs to be translated for i18n is grouped together), and any other constants needed. Also "alsmost" constants like lookup tables are defined here.
~1)**Parameters section**: A section where parameters are read, and validated, using whatever is defined as defaults for optional parameters (constants, or dynamic defaults such as "current month"). In this case, invalid or otherwise unusable parameters are silently ignored (using defaults instead); depending on the purpose of an action, an invalid parameter (or missing required parameter) may generate an error message instead.
~1)**Data preparation section**: A section where input is further processed into all variables needed to generate the output.
~1)**Output section**: At this point all data is prepared, so the final section does nothing but generate output: no new data is generated here, we only look up data prepared in the parameter or data preparation section (we can loop through an array though). (This is the same approach as with using a templating solution such as [[http://smarty.php.net/ Smarty]].)
This results in a clear and consistent logic of the code, and separation of process and data from presentation of the data.
==Dealing with multiple calendars==
The original code had a little function to derive the last day in a month; not only was this a somewhat inefficient, but having a function conflicted with including the code multiple times. The function has been replaced by (simpler) code in the data preparation section.
==Calendar generating logic==
Another difference is in the logic used for the start of the first week (when we may need to generate "blank" cells): this is taken out of the (original) loop, resulting in more logical and efficient code. The inverse of that is added at the end so that where necessary blank cells are added at the end of the table (having fewer cells in a row is not valid markup!).
==Internationalization==
Apart from two or three strings (defined in the constants section), the output produced by the action is completely internationalized: by using the appropriate functions the names for months and days (short and long) are already corresponding to the defined locale.
==Documentation==
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===The code===
==PHP code for the action==
Note that I've classified this as "version 0.8" since there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
%%(php)
<?php
/**
* Display a calendar face for a specified or the current month.
*
* Specifying a month and/or year in the action itself results in a "static" calendar face without
* navigation; conversely, providing no parameters in the action results in a calendar face with
* navigation links to previous, current and next month, with URL parameters determining which
* month is shown (with the current month as default).
*
* You can have one "dynamic" (navigable) calendar on a page (multiple ones would just be the same)
* and any number of "static" calendars.
*
* The current date (if visible) gets a special class to allow a different styling with CSS.
*
* Credit:
* This action was inspired mainly by the "Calendar Menu" code written by
* {@link http://www.blazonry.com/about.php Marcus Kazmierczak}
* (© 1998-2002 Astonish Inc.) which we traced back as being the ultimate origin of this code
* although our starting point was actually a (probably second-hand) variant found on the web which
* did not contain any attribution.
* However, not much of the original code is left in this version. Nevertheless, credit to
* Marcus Kazmierczak for the original that inspired this, however indirectly: Thanks!
*
* @package Actions
* @subpackage Date and Time
* @name Calendar
*
* @author {@link http://wikka.jsnx.com/GmBowen GmBowen} (first draft)
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (more modifications)
* @version 0.8
* @since Wikka 1.1.6.0
*
* @input integer $year optional: 4-digit year of the month to be displayed;
* default: current year
* the default can be overridden by providing a URL parameter 'year'
* @input integer $month optional: number of month (1 or 2 digits) to be displayed;
* default: current month
* the default can be overridden by providing a URL parameter 'month'
* @output data table for specified or current month
*
* @todo - take care we don't go over date limits for PHP with navigation links
* - configurable first day of week
*/
// ***** CONSTANTS section *****
define('MIN_DATETIME', strtotime('1970-01-01 00:00:00 GMT')); # earliest timestamp PHP can handle (Windows and some others - to be safe)
define('MAX_DATETIME', strtotime('2038-01-19 03:04:07 GMT')); # latest timestamp PHP can handle
define('MIN_YEAR', date('Y',MIN_DATETIME));
define('MAX_YEAR', date('Y',MAX_DATETIME)-1); # don't include partial January 2038
// not-quite-constants
$daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
define('CUR_YEAR', date('Y',mktime()));
define('CUR_MONTH', date('n',mktime()));
// format string for locale-specific month (%B) + 4-digit year (%Y) used for caption and title attributes
// NOTE: monthname is locale-specific but order of month and year may need to be switched: hence the double quotes!
define('LOC_MON_YEAR', "%B %Y"); # i18n
define('FMT_SUMMARY', "Calendar for %s"); # i18n
define('TODAY', "today"); # i18n
// ***** END CONSTANTS section *****
// ***** (ACTION) PARAMETERS Interface *****
// set parameter defaults: current year and month
$year = CUR_YEAR;
$month = CUR_MONTH;
// get and interpret parameters
// 1) overrride defaults with parameters provided in URL (accept only valid values)
if (isset($_GET['year']))
{
$uYear = (int)$_GET['year'];
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR) $year = $uYear;
}
if (isset($_GET['month']))
{
$uMonth = (int)$_GET['month'];
if ($uMonth >= 1 && $uMonth <= 12) $month = $uMonth;
}
// 2) override with parameters provided in action itself (accept only valid values)
$hasActionParams = FALSE;
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
switch ($param)
{
case 'year':
$uYear = (int)trim($value);
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR)
{
$year = $uYear;
$hasActionParams = TRUE;
}
break;
case 'month':
$uMonth = (int)trim($value);
if ($uMonth >= 1 && $uMonth <= 12)
{
$month = $uMonth;
$hasActionParams = TRUE;
}
break;
}
}
}
// ***** (ACTION) PARAMETERS Interface *****
// ***** DERIVED VARIABLES *****
// derive which weekday the first is on
$datemonthfirst = sprintf('%4d-%02d-%02d',$year,$month,1);
$firstwday = strftime('%w',strtotime($datemonthfirst)); # i18n
// derive (locale-specific) caption text
$monthYear = strftime(LOC_MON_YEAR,strtotime($datemonthfirst)); # i18n
$summary = sprintf(FMT_SUMMARY, $monthYear); # i18n
// derive last day of month
$lastmday = $daysInMonth[$month - 1];
if (2 == $month) # correct for leap year if necessary
{
if (1 == date('L',strtotime(sprintf('%4d-%02d-%02d',$year,1,1)))) $lastmday++;
}
// derive "today" to detect when to mark this up in the calendar face
$today = date("Y:m:d",mktime());
// build navigation variables - locale-specific (%B gets full month name)
// FIXME: @@@ take care we don't go over date limits for PHP
if (!$hasActionParams)
{
// previous month
$monthPrev = ($month-1 < 1) ? 12 : $month-1;
$yearPrev = ($month-1 < 1) ? $year-1 : $year;
$parPrev = "month=$monthPrev&year=$yearPrev";
$urlPrev = $this->Href('', '', $parPrev);
$titlePrev = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearPrev,$monthPrev,1)));# i18n
// current month
$parCur = 'month='.CUR_MONTH.'&year='.CUR_YEAR;
$urlCur = $this->Href('', '', $parCur);
$titleCur = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',CUR_YEAR,CUR_MONTH,1))); # i18n
// next month
$monthNext = ($month+1 > 12) ? 1 : $month+1;
$yearNext = ($month+1 > 12) ? $year+1 : $year;
$parNext = "month=$monthNext&year=$yearNext";
$urlNext = $this->Href('', '', $parNext);
$titleNext = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearNext,$monthNext,1)));# i18n
}
// build array with names of weekdays (locale-specific)
$tmpTime = strtotime("this Sunday"); # get a starting date that is a Sunday
$tmpDate = date('d',$tmpTime);
$tmpMonth = date('m',$tmpTime);
$tmpYear = date('Y',$tmpTime);
for ($i=0; $i<=6; $i++)
{
$aWeekdaysShort[$i] = strftime('%a',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
$aWeekdaysLong[$i] = strftime('%A',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
}
// ***** END DERIVED VARIABLES *****
// ***** OUTPUT SECTION *****
?>
<table cellpadding="2" cellspacing="1" class="calendar" summary="<?php echo $summary;?>">
<caption><?php echo $monthYear;?></caption>
<thead>
<tr>
<?php
for ($i=0; $i<=6; $i++)
{
?>
<th scope="col" width="26" abbr="<?php echo $aWeekdaysLong[$i];?>"><?php echo $aWeekdaysShort[$i];?></th>
<?php
}
?>
</tr>
</thead>
<tbody class="face">
<?php
// start row for first week (if it doesn't start on Sunday)
if ($firstwday > 0)
{
echo " <tr>\n";
}
// fill start of first week with blank cells before start of month
for ($i=1; $i<=$firstwday; $i++)
{
echo ' <td> </td>'."\n";
}
// loop through all the days of the month
$day = 1;
$wday = $firstwday;
while ($day <= $lastmday)
{
// start week row
if ($wday == 0)
{
echo " <tr>\n";
}
// handle markup for current day or any other day
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
if ($calday == $today)
{
echo ' <td title="'.TODAY.'" class="currentday">'.$day."</td>\n";
}
else
{
echo ' <td>'.$day."</td>\n";
}
// end week row
if ($wday == 6)
{
echo " </tr>\n";
}
// next day
$wday = ++$wday % 7;
$day++;
}
// fill week with blank cells after end of month
if ($wday > 0)
{
for ($i=$wday; $i<=6; $i++)
{
echo ' <td> </td>'."\n";
}
}
// end row for last week
if ($wday < 6)
{
echo " </tr>\n";
}
?>
</tbody>
<?php
// generate navigation only for calendar without (valid) action parameters!
// FIXME: @@@ take care we don't go over date limits for PHP
if ($hasActionParams === FALSE)
{
?>
<tbody class="calnav">
<tr>
<td colspan="3" align="left" class="prevmonth"><a href="<?php echo $urlPrev;?>" title="<?php echo $titlePrev;?>"><<</a></td>
<td align="center" class="curmonth"><a href="<?php echo $urlCur;?>" title="<?php echo $titleCur;?>">=</a></td>
<td colspan="3" align="right" class="nextmonth"><a href="<?php echo $urlNext;?>" title="<?php echo $titleNext;?>">>></a></td>
</tr>
</tbody>
<?php
}
?>
</table>
<?php
// ***** END OUTPUT SECTION *****
?>%%
==Corresponding CSS code==
The code provides a nice layout for the data table and has some comments with hints for variants. To be added at the end of ##wikka.css##.
%%(css)
/* Calendar styling - added 2004-11-30 - updated 2004-12-01 */
/* general styling */
table.calendar {
color: #000000;
background-color: #CCCCCC; /* comment out to have space between cells same color as page background */
/*border-collapse: collapse;*/ /* would make single-width borders, ignoring cell-spacing */
}
table.calendar caption {
background-color: #CCCCCC;
font-weight: bold;
line-height: 1.6em;
}
table.calendar thead {
background-color: #CCCCCC;
}
table.calendar tbody.face {
background-color: #CCCCCC;
}
table.calendar tbody.calnav {
background-color: #CCCCCC;
}
/* styling for some specific elements */
table.calendar thead th {
/*border: 1px solid #000000;*/ /* uncomment to have border around day name headers (will be page background if table background is undefined) */
padding: 1px;
text-align: center;
font-size: 85%;
width: 26px;
}
table.calendar tbody.face td {
border: 1px solid #000000;
text-align: right;
}
table.calendar td.currentday {
color: #993333;
background-color: #AAAAAA;
font-weight: bold;
}
/* styling of calendar navigation */
table.calendar tbody.calnav {
font-weight: bold;
}
table.calendar td.prevmonth {
text-align: left;
font-size: 85%;
}
table.calendar td.curmonth {
text-align: center;
}
table.calendar td.nextmonth {
text-align: right;
font-size: 85%;
}
table.calendar a:link {
color: #993333;
text-decoration: none;
}
table.calendar a:visited {
color: #993333;
text-decoration: none;
}
table.calendar a:hover {
color: #993333;
}
table.calendar a:active {
color: #993333;
text-decoration: none;
}
%%
===Comments?===
Comments and suggestion welcome. And please give it a good workout (using combinations of URL parameters and action parameters!) before including it in the upcoming release.
Finally: The ##@since## tag in the documentation block assumes this will be included in Wikka release 1.1.6.0 - adapt or remove as needed...
--JavaWoman
Deletions:
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1
Additions:
=====Calendar action=====
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1
Deletions:
{{lastedit show="2"}}
==History==
GmBowen posted a nice little Calendar action on GmBowenCalendar which drew a lot of comments (and suggestions), and which JsnX proposed to include in the upcoming (1.1.6.0) version of Wikka. I commented that I'd like to see the code "cleaned up" before inclusion, and offered to do that. The result is here.
===Different output===
==Data table markup==
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
What's the difference? A layout table has nothing but a table tag (##table##), table rows (##tr##), and table data cells (##td##) within the rows. However, a **data table** is to present data in relation to each other; a calendar clearly is a data table, showing dates in a month with day names labelling groups of dates (and possibly more). In order to show relationships between data, a data table uses not only table **data** cells (##td##), but also **header** cells (##th##) to label the data, and preferably a **caption** (##caption##) that labels what the whole thing is about.
A good article about marking up data tables:
~-[[http://www.communitymx.com/content/article.cfm?page=1&cid=0BEA6 Semantic (X)HTML Markup: Using Tables Appropriately]]
Looking at a common "calendar face" the candidates for header cells and caption are obvious: clearly the (abbreviated) day names are headers, and the name of the month at the top logically is a caption. That leaves the navigation, however, which I've moved to a separate section at the bottom. While I was at it, I took up [[DarTar]]'s suggestion for a link (back) to the current month since moving the navigation links to the bottom left a space in the middle.
The result looks like this (now that my code is active, this page can have multiple calendars - see another example below):
@@{{calendar}}implemented with **##""{{calendar}}""##**@@
==Making it accessible==
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a ##summary## attribute to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a ##title## attribute. Similarly, the data cell for "today" (if shown) gets a ##title## as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
More about accessible table markup:
~-[[http://www.w3.org/TR/WCAG10-HTML-TECHS/#data-tables 5.1 Tables of data]]
~-[[http://www.webaim.org/techniques/tables/2 Creating Accessible Tables, Part 2: Data Tables]]
===Extra functionality===
=="Dynamic" and "static" calendars==
My variant of the Calendar action does all that the original GmBowenCalendar does, and one thing extra:
~-without any parameters, it generates a calendar for the current month, with navigation links, leading to:
~-it picks up on URL parameters for month and/or year, and shows the calendar for the specified month, also with navigation links
~-however, if parameters are provided in the action code itself a static calendar is shown for the month specified (taking the default for a missing parameter from the URL parameter(s) or the current month)
The result is that you can show a dynamic calendar which "reacts" to URL parameters //in addition to// any number of static calendars for a specific month.
I'll include code for a static month below - if (as long as) my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}implemented with **##""{{calendar month="3" year="2006"}}""##**@@
===Different code===
==A pattern for an action==
The new code follows a specific "pattern" which I use for all my (new or modified) action code. In general it goes like this:
~1)**Constants section**: A section where all constants are defined; this includes constants used as defaults for possible optional acrion parameters as well as constants defining user-interface strings (so that whatever needs to be translated for i18n is grouped together), and any other constants needed. Also "alsmost" constants like lookup tables are defined here.
~1)**Parameters section**: A section where parameters are read, and validated, using whatever is defined as defaults for optional parameters (constants, or dynamic defaults such as "current month"). In this case, invalid or otherwise unusable parameters are silently ignored (using defaults instead); depending on the purpose of an action, an invalid parameter (or missing required parameter) may generate an error message instead.
~1)**Data preparation section**: A section where input is further processed into all variables needed to generate the output.
~1)**Output section**: At this point all data is prepared, so the final section does nothing but generate output: no new data is generated here, we only look up data prepared in the parameter or data preparation section (we can loop through an array though). (This is the same approach as with using a templating solution such as [[http://smarty.php.net/ Smarty]].)
This results in a clear and consistent logic of the code, and separation of process and data from presentation of the data.
==Dealing with multiple calendars==
The original code had a little function to derive the last day in a month; not only was this a somewhat inefficient, but having a function conflicted with including the code multiple times. The function has been replaced by (simpler) code in the data preparation section.
==Calendar generating logic==
Another difference is in the logic used for the start of the first week (when we may need to generate "blank" cells): this is taken out of the (original) loop, resulting in more logical and efficient code. The inverse of that is added at the end so that where necessary blank cells are added at the end of the table (having fewer cells in a row is not valid markup!).
==Internationalization==
Apart from two or three strings (defined in the constants section), the output produced by the action is completely internationalized: by using the appropriate functions the names for months and days (short and long) are already corresponding to the defined locale.
==Documentation==
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===The code===
==PHP code for the action==
Note that I've classified this as "version 0.8" since there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
%%(php)
<?php
/**
* Display a calendar face for a specified or the current month.
*
* Specifying a month and/or year in the action itself results in a "static" calendar face without
* navigation; conversely, providing no parameters in the action results in a calendar face with
* navigation links to previous, current and next month, with URL parameters determining which
* month is shown (with the current month as default).
*
* You can have one "dynamic" (navigable) calendar on a page (multiple ones would just be the same)
* and any number of "static" calendars.
*
* The current date (if visible) gets a special class to allow a different styling with CSS.
*
* Credit:
* This action was inspired mainly by the "Calendar Menu" code written by
* {@link http://www.blazonry.com/about.php Marcus Kazmierczak}
* (© 1998-2002 Astonish Inc.) which we traced back as being the ultimate origin of this code
* although our starting point was actually a (probably second-hand) variant found on the web which
* did not contain any attribution.
* However, not much of the original code is left in this version. Nevertheless, credit to
* Marcus Kazmierczak for the original that inspired this, however indirectly: Thanks!
*
* @package Actions
* @subpackage Date and Time
* @name Calendar
*
* @author {@link http://wikka.jsnx.com/GmBowen GmBowen} (first draft)
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (more modifications)
* @version 0.8
* @since Wikka 1.1.6.0
*
* @input integer $year optional: 4-digit year of the month to be displayed;
* default: current year
* the default can be overridden by providing a URL parameter 'year'
* @input integer $month optional: number of month (1 or 2 digits) to be displayed;
* default: current month
* the default can be overridden by providing a URL parameter 'month'
* @output data table for specified or current month
*
* @todo - take care we don't go over date limits for PHP with navigation links
* - configurable first day of week
*/
// ***** CONSTANTS section *****
define('MIN_DATETIME', strtotime('1970-01-01 00:00:00 GMT')); # earliest timestamp PHP can handle (Windows and some others - to be safe)
define('MAX_DATETIME', strtotime('2038-01-19 03:04:07 GMT')); # latest timestamp PHP can handle
define('MIN_YEAR', date('Y',MIN_DATETIME));
define('MAX_YEAR', date('Y',MAX_DATETIME)-1); # don't include partial January 2038
// not-quite-constants
$daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
define('CUR_YEAR', date('Y',mktime()));
define('CUR_MONTH', date('n',mktime()));
// format string for locale-specific month (%B) + 4-digit year (%Y) used for caption and title attributes
// NOTE: monthname is locale-specific but order of month and year may need to be switched: hence the double quotes!
define('LOC_MON_YEAR', "%B %Y"); # i18n
define('FMT_SUMMARY', "Calendar for %s"); # i18n
define('TODAY', "today"); # i18n
// ***** END CONSTANTS section *****
// ***** (ACTION) PARAMETERS Interface *****
// set parameter defaults: current year and month
$year = CUR_YEAR;
$month = CUR_MONTH;
// get and interpret parameters
// 1) overrride defaults with parameters provided in URL (accept only valid values)
if (isset($_GET['year']))
{
$uYear = (int)$_GET['year'];
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR) $year = $uYear;
}
if (isset($_GET['month']))
{
$uMonth = (int)$_GET['month'];
if ($uMonth >= 1 && $uMonth <= 12) $month = $uMonth;
}
// 2) override with parameters provided in action itself (accept only valid values)
$hasActionParams = FALSE;
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
switch ($param)
{
case 'year':
$uYear = (int)trim($value);
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR)
{
$year = $uYear;
$hasActionParams = TRUE;
}
break;
case 'month':
$uMonth = (int)trim($value);
if ($uMonth >= 1 && $uMonth <= 12)
{
$month = $uMonth;
$hasActionParams = TRUE;
}
break;
}
}
}
// ***** (ACTION) PARAMETERS Interface *****
// ***** DERIVED VARIABLES *****
// derive which weekday the first is on
$datemonthfirst = sprintf('%4d-%02d-%02d',$year,$month,1);
$firstwday = strftime('%w',strtotime($datemonthfirst)); # i18n
// derive (locale-specific) caption text
$monthYear = strftime(LOC_MON_YEAR,strtotime($datemonthfirst)); # i18n
$summary = sprintf(FMT_SUMMARY, $monthYear); # i18n
// derive last day of month
$lastmday = $daysInMonth[$month - 1];
if (2 == $month) # correct for leap year if necessary
{
if (1 == date('L',strtotime(sprintf('%4d-%02d-%02d',$year,1,1)))) $lastmday++;
}
// derive "today" to detect when to mark this up in the calendar face
$today = date("Y:m:d",mktime());
// build navigation variables - locale-specific (%B gets full month name)
// FIXME: @@@ take care we don't go over date limits for PHP
if (!$hasActionParams)
{
// previous month
$monthPrev = ($month-1 < 1) ? 12 : $month-1;
$yearPrev = ($month-1 < 1) ? $year-1 : $year;
$parPrev = "month=$monthPrev&year=$yearPrev";
$urlPrev = $this->Href('', '', $parPrev);
$titlePrev = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearPrev,$monthPrev,1)));# i18n
// current month
$parCur = 'month='.CUR_MONTH.'&year='.CUR_YEAR;
$urlCur = $this->Href('', '', $parCur);
$titleCur = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',CUR_YEAR,CUR_MONTH,1))); # i18n
// next month
$monthNext = ($month+1 > 12) ? 1 : $month+1;
$yearNext = ($month+1 > 12) ? $year+1 : $year;
$parNext = "month=$monthNext&year=$yearNext";
$urlNext = $this->Href('', '', $parNext);
$titleNext = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearNext,$monthNext,1)));# i18n
}
// build array with names of weekdays (locale-specific)
$tmpTime = strtotime("this Sunday"); # get a starting date that is a Sunday
$tmpDate = date('d',$tmpTime);
$tmpMonth = date('m',$tmpTime);
$tmpYear = date('Y',$tmpTime);
for ($i=0; $i<=6; $i++)
{
$aWeekdaysShort[$i] = strftime('%a',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
$aWeekdaysLong[$i] = strftime('%A',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
}
// ***** END DERIVED VARIABLES *****
// ***** OUTPUT SECTION *****
?>
<table cellpadding="2" cellspacing="1" class="calendar" summary="<?php echo $summary;?>">
<caption><?php echo $monthYear;?></caption>
<thead>
<tr>
<?php
for ($i=0; $i<=6; $i++)
{
?>
<th scope="col" width="26" abbr="<?php echo $aWeekdaysLong[$i];?>"><?php echo $aWeekdaysShort[$i];?></th>
<?php
}
?>
</tr>
</thead>
<tbody class="face">
<?php
// start row for first week (if it doesn't start on Sunday)
if ($firstwday > 0)
{
echo " <tr>\n";
}
// fill start of first week with blank cells before start of month
for ($i=1; $i<=$firstwday; $i++)
{
echo ' <td> </td>'."\n";
}
// loop through all the days of the month
$day = 1;
$wday = $firstwday;
while ($day <= $lastmday)
{
// start week row
if ($wday == 0)
{
echo " <tr>\n";
}
// handle markup for current day or any other day
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
if ($calday == $today)
{
echo ' <td title="'.TODAY.'" class="currentday">'.$day."</td>\n";
}
else
{
echo ' <td>'.$day."</td>\n";
}
// end week row
if ($wday == 6)
{
echo " </tr>\n";
}
// next day
$wday = ++$wday % 7;
$day++;
}
// fill week with blank cells after end of month
if ($wday > 0)
{
for ($i=$wday; $i<=6; $i++)
{
echo ' <td> </td>'."\n";
}
}
// end row for last week
if ($wday < 6)
{
echo " </tr>\n";
}
?>
</tbody>
<?php
// generate navigation only for calendar without (valid) action parameters!
// FIXME: @@@ take care we don't go over date limits for PHP
if ($hasActionParams === FALSE)
{
?>
<tbody class="calnav">
<tr>
<td colspan="3" align="left" class="prevmonth"><a href="<?php echo $urlPrev;?>" title="<?php echo $titlePrev;?>"><<</a></td>
<td align="center" class="curmonth"><a href="<?php echo $urlCur;?>" title="<?php echo $titleCur;?>">=</a></td>
<td colspan="3" align="right" class="nextmonth"><a href="<?php echo $urlNext;?>" title="<?php echo $titleNext;?>">>></a></td>
</tr>
</tbody>
<?php
}
?>
</table>
<?php
// ***** END OUTPUT SECTION *****
?>%%
==Corresponding CSS code==
The code provides a nice layout for the data table and has some comments with hints for variants. To be added at the end of ##wikka.css##.
%%(css)
/* Calendar styling - added 2004-11-30 - updated 2004-12-01 */
/* general styling */
table.calendar {
color: #000000;
background-color: #CCCCCC; /* comment out to have space between cells same color as page background */
/*border-collapse: collapse;*/ /* would make single-width borders, ignoring cell-spacing */
}
table.calendar caption {
background-color: #CCCCCC;
font-weight: bold;
line-height: 1.6em;
}
table.calendar thead {
background-color: #CCCCCC;
}
table.calendar tbody.face {
background-color: #CCCCCC;
}
table.calendar tbody.calnav {
background-color: #CCCCCC;
}
/* styling for some specific elements */
table.calendar thead th {
/*border: 1px solid #000000;*/ /* uncomment to have border around day name headers (will be page background if table background is undefined) */
padding: 1px;
text-align: center;
font-size: 85%;
width: 26px;
}
table.calendar tbody.face td {
border: 1px solid #000000;
text-align: right;
}
table.calendar td.currentday {
color: #993333;
background-color: #AAAAAA;
font-weight: bold;
}
/* styling of calendar navigation */
table.calendar tbody.calnav {
font-weight: bold;
}
table.calendar td.prevmonth {
text-align: left;
font-size: 85%;
}
table.calendar td.curmonth {
text-align: center;
}
table.calendar td.nextmonth {
text-align: right;
font-size: 85%;
}
table.calendar a:link {
color: #993333;
text-decoration: none;
}
table.calendar a:visited {
color: #993333;
text-decoration: none;
}
table.calendar a:hover {
color: #993333;
}
table.calendar a:active {
color: #993333;
text-decoration: none;
}
%%
===Comments?===
Comments and suggestion welcome. And please give it a good workout (using combinations of URL parameters and action parameters!) before including it in the upcoming release.
Finally: The ##@since## tag in the documentation block assumes this will be included in Wikka release 1.1.6.0 - adapt or remove as needed...
--JavaWoman
Additions:
/* Calendar styling - added 2004-11-30 - updated 2004-12-01 */
Deletions:
Revision [2724]
Edited on 2004-12-01 17:31:04 by JavaWoman [Tweaking colors in CSS - see comments on the page]Additions:
color: #000000;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
color: #993333;
Deletions:
color: #DD0000;
color: #DD0000;
color: #DD0000;
color: #DD0000;
Revision [2712]
Edited on 2004-12-01 11:59:18 by JavaWoman [adding another sample and source code samples for both]Additions:
The result looks like this (now that my code is active, this page can have multiple calendars - see another example below):
@@{{calendar}}implemented with **##""{{calendar}}""##**@@
I'll include code for a static month below - if (as long as) my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}implemented with **##""{{calendar month="3" year="2006"}}""##**@@
@@{{calendar}}implemented with **##""{{calendar}}""##**@@
I'll include code for a static month below - if (as long as) my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}implemented with **##""{{calendar month="3" year="2006"}}""##**@@
Deletions:
@@{{calendar month="3" year="2006"}}@@
(Until then you'd just see a calendar for the current month.)
Revision [2706]
Edited on 2004-11-30 22:41:12 by JavaWoman [another link about accessible table markup]Additions:
~-[[http://www.webaim.org/techniques/tables/2 Creating Accessible Tables, Part 2: Data Tables]]
Additions:
Looking at a common "calendar face" the candidates for header cells and caption are obvious: clearly the (abbreviated) day names are headers, and the name of the month at the top logically is a caption. That leaves the navigation, however, which I've moved to a separate section at the bottom. While I was at it, I took up [[DarTar]]'s suggestion for a link (back) to the current month since moving the navigation links to the bottom left a space in the middle.
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a ##summary## attribute to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a ##title## attribute. Similarly, the data cell for "today" (if shown) gets a ##title## as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
Note that I've classified this as "version 0.8" since there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
* navigation links to previous, current and next month, with URL parameters determining which
* month is shown (with the current month as default).
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a ##summary## attribute to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a ##title## attribute. Similarly, the data cell for "today" (if shown) gets a ##title## as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
Note that I've classified this as "version 0.8" since there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
* navigation links to previous, current and next month, with URL parameters determining which
* month is shown (with the current month as default).
Deletions:
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a summary to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a title attribute. Similarly, the data cell for "today" (if shown) gets a title as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
Note that I've classified this as "version 0.8" sice there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
* navigation links to previous, current and next month, with URL URL parameters determining which
* month is shown (with teh current month as default).
Additions:
// derive "today" to detect when to mark this up in the calendar face
$today = date("Y:m:d",mktime());
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
if ($calday == $today)
$today = date("Y:m:d",mktime());
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
if ($calday == $today)
Deletions:
$today = date("Y:m:d",mktime());
if ($calday == $today)
Additions:
{{lastedit show="2"}}
%%
===Comments?===
Comments and suggestion welcome. And please give it a good workout (using combinations of URL parameters and action parameters!) before including it in the upcoming release.
Finally: The ##@since## tag in the documentation block assumes this will be included in Wikka release 1.1.6.0 - adapt or remove as needed...
--JavaWoman
%%
===Comments?===
Comments and suggestion welcome. And please give it a good workout (using combinations of URL parameters and action parameters!) before including it in the upcoming release.
Finally: The ##@since## tag in the documentation block assumes this will be included in Wikka release 1.1.6.0 - adapt or remove as needed...
--JavaWoman
Deletions:
Additions:
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===The code===
==PHP code for the action==
Note that I've classified this as "version 0.8" sice there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
%%(php)
<?php
/**
* Display a calendar face for a specified or the current month.
*
* Specifying a month and/or year in the action itself results in a "static" calendar face without
* navigation; conversely, providing no parameters in the action results in a calendar face with
* navigation links to previous, current and next month, with URL URL parameters determining which
* month is shown (with teh current month as default).
*
* You can have one "dynamic" (navigable) calendar on a page (multiple ones would just be the same)
* and any number of "static" calendars.
*
* The current date (if visible) gets a special class to allow a different styling with CSS.
*
* Credit:
* This action was inspired mainly by the "Calendar Menu" code written by
* {@link http://www.blazonry.com/about.php Marcus Kazmierczak}
* (© 1998-2002 Astonish Inc.) which we traced back as being the ultimate origin of this code
* although our starting point was actually a (probably second-hand) variant found on the web which
* did not contain any attribution.
* However, not much of the original code is left in this version. Nevertheless, credit to
* Marcus Kazmierczak for the original that inspired this, however indirectly: Thanks!
*
* @package Actions
* @subpackage Date and Time
* @name Calendar
*
* @author {@link http://wikka.jsnx.com/GmBowen GmBowen} (first draft)
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (more modifications)
* @version 0.8
* @since Wikka 1.1.6.0
*
* @input integer $year optional: 4-digit year of the month to be displayed;
* default: current year
* the default can be overridden by providing a URL parameter 'year'
* @input integer $month optional: number of month (1 or 2 digits) to be displayed;
* default: current month
* the default can be overridden by providing a URL parameter 'month'
* @output data table for specified or current month
*
* @todo - take care we don't go over date limits for PHP with navigation links
* - configurable first day of week
*/
// ***** CONSTANTS section *****
define('MIN_DATETIME', strtotime('1970-01-01 00:00:00 GMT')); # earliest timestamp PHP can handle (Windows and some others - to be safe)
define('MAX_DATETIME', strtotime('2038-01-19 03:04:07 GMT')); # latest timestamp PHP can handle
define('MIN_YEAR', date('Y',MIN_DATETIME));
define('MAX_YEAR', date('Y',MAX_DATETIME)-1); # don't include partial January 2038
// not-quite-constants
$daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
define('CUR_YEAR', date('Y',mktime()));
define('CUR_MONTH', date('n',mktime()));
// format string for locale-specific month (%B) + 4-digit year (%Y) used for caption and title attributes
// NOTE: monthname is locale-specific but order of month and year may need to be switched: hence the double quotes!
define('LOC_MON_YEAR', "%B %Y"); # i18n
define('FMT_SUMMARY', "Calendar for %s"); # i18n
define('TODAY', "today"); # i18n
// ***** END CONSTANTS section *****
// ***** (ACTION) PARAMETERS Interface *****
// set parameter defaults: current year and month
$year = CUR_YEAR;
$month = CUR_MONTH;
// get and interpret parameters
// 1) overrride defaults with parameters provided in URL (accept only valid values)
if (isset($_GET['year']))
{
$uYear = (int)$_GET['year'];
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR) $year = $uYear;
}
if (isset($_GET['month']))
{
$uMonth = (int)$_GET['month'];
if ($uMonth >= 1 && $uMonth <= 12) $month = $uMonth;
}
// 2) override with parameters provided in action itself (accept only valid values)
$hasActionParams = FALSE;
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
switch ($param)
{
case 'year':
$uYear = (int)trim($value);
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR)
{
$year = $uYear;
$hasActionParams = TRUE;
}
break;
case 'month':
$uMonth = (int)trim($value);
if ($uMonth >= 1 && $uMonth <= 12)
{
$month = $uMonth;
$hasActionParams = TRUE;
}
break;
}
}
}
// ***** (ACTION) PARAMETERS Interface *****
// ***** DERIVED VARIABLES *****
// derive which weekday the first is on
$datemonthfirst = sprintf('%4d-%02d-%02d',$year,$month,1);
$firstwday = strftime('%w',strtotime($datemonthfirst)); # i18n
// derive (locale-specific) caption text
$monthYear = strftime(LOC_MON_YEAR,strtotime($datemonthfirst)); # i18n
$summary = sprintf(FMT_SUMMARY, $monthYear); # i18n
// derive last day of month
$lastmday = $daysInMonth[$month - 1];
if (2 == $month) # correct for leap year if necessary
{
if (1 == date('L',strtotime(sprintf('%4d-%02d-%02d',$year,1,1)))) $lastmday++;
}
// build navigation variables - locale-specific (%B gets full month name)
// FIXME: @@@ take care we don't go over date limits for PHP
if (!$hasActionParams)
{
// previous month
$monthPrev = ($month-1 < 1) ? 12 : $month-1;
$yearPrev = ($month-1 < 1) ? $year-1 : $year;
$parPrev = "month=$monthPrev&year=$yearPrev";
$urlPrev = $this->Href('', '', $parPrev);
$titlePrev = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearPrev,$monthPrev,1)));# i18n
// current month
$parCur = 'month='.CUR_MONTH.'&year='.CUR_YEAR;
$urlCur = $this->Href('', '', $parCur);
$titleCur = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',CUR_YEAR,CUR_MONTH,1))); # i18n
// next month
$monthNext = ($month+1 > 12) ? 1 : $month+1;
$yearNext = ($month+1 > 12) ? $year+1 : $year;
$parNext = "month=$monthNext&year=$yearNext";
$urlNext = $this->Href('', '', $parNext);
$titleNext = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearNext,$monthNext,1)));# i18n
}
// build array with names of weekdays (locale-specific)
$tmpTime = strtotime("this Sunday"); # get a starting date that is a Sunday
$tmpDate = date('d',$tmpTime);
$tmpMonth = date('m',$tmpTime);
$tmpYear = date('Y',$tmpTime);
for ($i=0; $i<=6; $i++)
{
$aWeekdaysShort[$i] = strftime('%a',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
$aWeekdaysLong[$i] = strftime('%A',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
}
// ***** END DERIVED VARIABLES *****
// ***** OUTPUT SECTION *****
?>
<table cellpadding="2" cellspacing="1" class="calendar" summary="<?php echo $summary;?>">
<caption><?php echo $monthYear;?></caption>
<thead>
<tr>
<?php
for ($i=0; $i<=6; $i++)
{
?>
<th scope="col" width="26" abbr="<?php echo $aWeekdaysLong[$i];?>"><?php echo $aWeekdaysShort[$i];?></th>
<?php
}
?>
</tr>
</thead>
<tbody class="face">
<?php
// start row for first week (if it doesn't start on Sunday)
if ($firstwday > 0)
{
echo " <tr>\n";
}
// fill start of first week with blank cells before start of month
for ($i=1; $i<=$firstwday; $i++)
{
echo ' <td> </td>'."\n";
}
// loop through all the days of the month
$day = 1;
$wday = $firstwday;
while ($day <= $lastmday)
{
// start week row
if ($wday == 0)
{
echo " <tr>\n";
}
// handle markup for current day or any other day
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
$today = date("Y:m:d",mktime());
if ($calday == $today)
{
echo ' <td title="'.TODAY.'" class="currentday">'.$day."</td>\n";
}
else
{
echo ' <td>'.$day."</td>\n";
}
// end week row
if ($wday == 6)
{
echo " </tr>\n";
}
// next day
$wday = ++$wday % 7;
$day++;
}
// fill week with blank cells after end of month
if ($wday > 0)
{
for ($i=$wday; $i<=6; $i++)
{
echo ' <td> </td>'."\n";
}
}
// end row for last week
if ($wday < 6)
{
echo " </tr>\n";
}
?>
</tbody>
<?php
// generate navigation only for calendar without (valid) action parameters!
// FIXME: @@@ take care we don't go over date limits for PHP
if ($hasActionParams === FALSE)
{
?>
<tbody class="calnav">
<tr>
<td colspan="3" align="left" class="prevmonth"><a href="<?php echo $urlPrev;?>" title="<?php echo $titlePrev;?>"><<</a></td>
<td align="center" class="curmonth"><a href="<?php echo $urlCur;?>" title="<?php echo $titleCur;?>">=</a></td>
<td colspan="3" align="right" class="nextmonth"><a href="<?php echo $urlNext;?>" title="<?php echo $titleNext;?>">>></a></td>
</tr>
</tbody>
<?php
}
?>
</table>
<?php
// ***** END OUTPUT SECTION *****
?>%%
==Corresponding CSS code==
The code provides a nice layout for the data table and has some comments with hints for variants. To be added at the end of ##wikka.css##.
%%(css)
/* Calendar styling - added 2004-11-30 */
/* general styling */
table.calendar {
background-color: #CCCCCC; /* comment out to have space between cells same color as page background */
/*border-collapse: collapse;*/ /* would make single-width borders, ignoring cell-spacing */
}
table.calendar caption {
background-color: #CCCCCC;
font-weight: bold;
line-height: 1.6em;
}
table.calendar thead {
background-color: #CCCCCC;
}
table.calendar tbody.face {
background-color: #CCCCCC;
}
table.calendar tbody.calnav {
background-color: #CCCCCC;
}
/* styling for some specific elements */
table.calendar thead th {
/*border: 1px solid #000000;*/ /* uncomment to have border around day name headers (will be page background if table background is undefined) */
padding: 1px;
text-align: center;
font-size: 85%;
width: 26px;
}
table.calendar tbody.face td {
border: 1px solid #000000;
text-align: right;
}
table.calendar td.currentday {
color: #DD0000;
background-color: #AAAAAA;
font-weight: bold;
}
/* styling of calendar navigation */
table.calendar tbody.calnav {
font-weight: bold;
}
table.calendar td.prevmonth {
text-align: left;
font-size: 85%;
}
table.calendar td.curmonth {
text-align: center;
}
table.calendar td.nextmonth {
text-align: right;
font-size: 85%;
}
table.calendar a:link {
color: #DD0000;
text-decoration: none;
}
table.calendar a:visited {
color: #DD0000;
text-decoration: none;
}
table.calendar a:hover {
color: #DD0000;
}
table.calendar a:active {
color: #DD0000;
text-decoration: none;
}
%%
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===The code===
==PHP code for the action==
Note that I've classified this as "version 0.8" sice there are a few (non-essential) things left to do (see ##@todo## in the documentation block). Further explanations above...
%%(php)
<?php
/**
* Display a calendar face for a specified or the current month.
*
* Specifying a month and/or year in the action itself results in a "static" calendar face without
* navigation; conversely, providing no parameters in the action results in a calendar face with
* navigation links to previous, current and next month, with URL URL parameters determining which
* month is shown (with teh current month as default).
*
* You can have one "dynamic" (navigable) calendar on a page (multiple ones would just be the same)
* and any number of "static" calendars.
*
* The current date (if visible) gets a special class to allow a different styling with CSS.
*
* Credit:
* This action was inspired mainly by the "Calendar Menu" code written by
* {@link http://www.blazonry.com/about.php Marcus Kazmierczak}
* (© 1998-2002 Astonish Inc.) which we traced back as being the ultimate origin of this code
* although our starting point was actually a (probably second-hand) variant found on the web which
* did not contain any attribution.
* However, not much of the original code is left in this version. Nevertheless, credit to
* Marcus Kazmierczak for the original that inspired this, however indirectly: Thanks!
*
* @package Actions
* @subpackage Date and Time
* @name Calendar
*
* @author {@link http://wikka.jsnx.com/GmBowen GmBowen} (first draft)
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (more modifications)
* @version 0.8
* @since Wikka 1.1.6.0
*
* @input integer $year optional: 4-digit year of the month to be displayed;
* default: current year
* the default can be overridden by providing a URL parameter 'year'
* @input integer $month optional: number of month (1 or 2 digits) to be displayed;
* default: current month
* the default can be overridden by providing a URL parameter 'month'
* @output data table for specified or current month
*
* @todo - take care we don't go over date limits for PHP with navigation links
* - configurable first day of week
*/
// ***** CONSTANTS section *****
define('MIN_DATETIME', strtotime('1970-01-01 00:00:00 GMT')); # earliest timestamp PHP can handle (Windows and some others - to be safe)
define('MAX_DATETIME', strtotime('2038-01-19 03:04:07 GMT')); # latest timestamp PHP can handle
define('MIN_YEAR', date('Y',MIN_DATETIME));
define('MAX_YEAR', date('Y',MAX_DATETIME)-1); # don't include partial January 2038
// not-quite-constants
$daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
define('CUR_YEAR', date('Y',mktime()));
define('CUR_MONTH', date('n',mktime()));
// format string for locale-specific month (%B) + 4-digit year (%Y) used for caption and title attributes
// NOTE: monthname is locale-specific but order of month and year may need to be switched: hence the double quotes!
define('LOC_MON_YEAR', "%B %Y"); # i18n
define('FMT_SUMMARY', "Calendar for %s"); # i18n
define('TODAY', "today"); # i18n
// ***** END CONSTANTS section *****
// ***** (ACTION) PARAMETERS Interface *****
// set parameter defaults: current year and month
$year = CUR_YEAR;
$month = CUR_MONTH;
// get and interpret parameters
// 1) overrride defaults with parameters provided in URL (accept only valid values)
if (isset($_GET['year']))
{
$uYear = (int)$_GET['year'];
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR) $year = $uYear;
}
if (isset($_GET['month']))
{
$uMonth = (int)$_GET['month'];
if ($uMonth >= 1 && $uMonth <= 12) $month = $uMonth;
}
// 2) override with parameters provided in action itself (accept only valid values)
$hasActionParams = FALSE;
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
switch ($param)
{
case 'year':
$uYear = (int)trim($value);
if ($uYear >= MIN_YEAR && $uYear <= MAX_YEAR)
{
$year = $uYear;
$hasActionParams = TRUE;
}
break;
case 'month':
$uMonth = (int)trim($value);
if ($uMonth >= 1 && $uMonth <= 12)
{
$month = $uMonth;
$hasActionParams = TRUE;
}
break;
}
}
}
// ***** (ACTION) PARAMETERS Interface *****
// ***** DERIVED VARIABLES *****
// derive which weekday the first is on
$datemonthfirst = sprintf('%4d-%02d-%02d',$year,$month,1);
$firstwday = strftime('%w',strtotime($datemonthfirst)); # i18n
// derive (locale-specific) caption text
$monthYear = strftime(LOC_MON_YEAR,strtotime($datemonthfirst)); # i18n
$summary = sprintf(FMT_SUMMARY, $monthYear); # i18n
// derive last day of month
$lastmday = $daysInMonth[$month - 1];
if (2 == $month) # correct for leap year if necessary
{
if (1 == date('L',strtotime(sprintf('%4d-%02d-%02d',$year,1,1)))) $lastmday++;
}
// build navigation variables - locale-specific (%B gets full month name)
// FIXME: @@@ take care we don't go over date limits for PHP
if (!$hasActionParams)
{
// previous month
$monthPrev = ($month-1 < 1) ? 12 : $month-1;
$yearPrev = ($month-1 < 1) ? $year-1 : $year;
$parPrev = "month=$monthPrev&year=$yearPrev";
$urlPrev = $this->Href('', '', $parPrev);
$titlePrev = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearPrev,$monthPrev,1)));# i18n
// current month
$parCur = 'month='.CUR_MONTH.'&year='.CUR_YEAR;
$urlCur = $this->Href('', '', $parCur);
$titleCur = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',CUR_YEAR,CUR_MONTH,1))); # i18n
// next month
$monthNext = ($month+1 > 12) ? 1 : $month+1;
$yearNext = ($month+1 > 12) ? $year+1 : $year;
$parNext = "month=$monthNext&year=$yearNext";
$urlNext = $this->Href('', '', $parNext);
$titleNext = strftime(LOC_MON_YEAR,strtotime(sprintf('%4d-%02d-%02d',$yearNext,$monthNext,1)));# i18n
}
// build array with names of weekdays (locale-specific)
$tmpTime = strtotime("this Sunday"); # get a starting date that is a Sunday
$tmpDate = date('d',$tmpTime);
$tmpMonth = date('m',$tmpTime);
$tmpYear = date('Y',$tmpTime);
for ($i=0; $i<=6; $i++)
{
$aWeekdaysShort[$i] = strftime('%a',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
$aWeekdaysLong[$i] = strftime('%A',mktime(0,0,0,$tmpMonth,$tmpDate+$i,$tmpYear));
}
// ***** END DERIVED VARIABLES *****
// ***** OUTPUT SECTION *****
?>
<table cellpadding="2" cellspacing="1" class="calendar" summary="<?php echo $summary;?>">
<caption><?php echo $monthYear;?></caption>
<thead>
<tr>
<?php
for ($i=0; $i<=6; $i++)
{
?>
<th scope="col" width="26" abbr="<?php echo $aWeekdaysLong[$i];?>"><?php echo $aWeekdaysShort[$i];?></th>
<?php
}
?>
</tr>
</thead>
<tbody class="face">
<?php
// start row for first week (if it doesn't start on Sunday)
if ($firstwday > 0)
{
echo " <tr>\n";
}
// fill start of first week with blank cells before start of month
for ($i=1; $i<=$firstwday; $i++)
{
echo ' <td> </td>'."\n";
}
// loop through all the days of the month
$day = 1;
$wday = $firstwday;
while ($day <= $lastmday)
{
// start week row
if ($wday == 0)
{
echo " <tr>\n";
}
// handle markup for current day or any other day
$calday = sprintf('%4d:%02d:%02d',$year,$month,$day);
$today = date("Y:m:d",mktime());
if ($calday == $today)
{
echo ' <td title="'.TODAY.'" class="currentday">'.$day."</td>\n";
}
else
{
echo ' <td>'.$day."</td>\n";
}
// end week row
if ($wday == 6)
{
echo " </tr>\n";
}
// next day
$wday = ++$wday % 7;
$day++;
}
// fill week with blank cells after end of month
if ($wday > 0)
{
for ($i=$wday; $i<=6; $i++)
{
echo ' <td> </td>'."\n";
}
}
// end row for last week
if ($wday < 6)
{
echo " </tr>\n";
}
?>
</tbody>
<?php
// generate navigation only for calendar without (valid) action parameters!
// FIXME: @@@ take care we don't go over date limits for PHP
if ($hasActionParams === FALSE)
{
?>
<tbody class="calnav">
<tr>
<td colspan="3" align="left" class="prevmonth"><a href="<?php echo $urlPrev;?>" title="<?php echo $titlePrev;?>"><<</a></td>
<td align="center" class="curmonth"><a href="<?php echo $urlCur;?>" title="<?php echo $titleCur;?>">=</a></td>
<td colspan="3" align="right" class="nextmonth"><a href="<?php echo $urlNext;?>" title="<?php echo $titleNext;?>">>></a></td>
</tr>
</tbody>
<?php
}
?>
</table>
<?php
// ***** END OUTPUT SECTION *****
?>%%
==Corresponding CSS code==
The code provides a nice layout for the data table and has some comments with hints for variants. To be added at the end of ##wikka.css##.
%%(css)
/* Calendar styling - added 2004-11-30 */
/* general styling */
table.calendar {
background-color: #CCCCCC; /* comment out to have space between cells same color as page background */
/*border-collapse: collapse;*/ /* would make single-width borders, ignoring cell-spacing */
}
table.calendar caption {
background-color: #CCCCCC;
font-weight: bold;
line-height: 1.6em;
}
table.calendar thead {
background-color: #CCCCCC;
}
table.calendar tbody.face {
background-color: #CCCCCC;
}
table.calendar tbody.calnav {
background-color: #CCCCCC;
}
/* styling for some specific elements */
table.calendar thead th {
/*border: 1px solid #000000;*/ /* uncomment to have border around day name headers (will be page background if table background is undefined) */
padding: 1px;
text-align: center;
font-size: 85%;
width: 26px;
}
table.calendar tbody.face td {
border: 1px solid #000000;
text-align: right;
}
table.calendar td.currentday {
color: #DD0000;
background-color: #AAAAAA;
font-weight: bold;
}
/* styling of calendar navigation */
table.calendar tbody.calnav {
font-weight: bold;
}
table.calendar td.prevmonth {
text-align: left;
font-size: 85%;
}
table.calendar td.curmonth {
text-align: center;
}
table.calendar td.nextmonth {
text-align: right;
font-size: 85%;
}
table.calendar a:link {
color: #DD0000;
text-decoration: none;
}
table.calendar a:visited {
color: #DD0000;
text-decoration: none;
}
table.calendar a:hover {
color: #DD0000;
}
table.calendar a:active {
color: #DD0000;
text-decoration: none;
}
%%
Deletions:
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
Additions:
The original (including the ultimate original which we've traced the code back to - see the comments on [[http://wikka.jsnx.com/GmBowenCalendar?showcomments=1#comments GmBowenCalendar]]) code uses a table to present the calendar but the markup is that for a "layout" table, not a **data** table.
===Different code===
==A pattern for an action==
The new code follows a specific "pattern" which I use for all my (new or modified) action code. In general it goes like this:
~1)**Constants section**: A section where all constants are defined; this includes constants used as defaults for possible optional acrion parameters as well as constants defining user-interface strings (so that whatever needs to be translated for i18n is grouped together), and any other constants needed. Also "alsmost" constants like lookup tables are defined here.
~1)**Parameters section**: A section where parameters are read, and validated, using whatever is defined as defaults for optional parameters (constants, or dynamic defaults such as "current month"). In this case, invalid or otherwise unusable parameters are silently ignored (using defaults instead); depending on the purpose of an action, an invalid parameter (or missing required parameter) may generate an error message instead.
~1)**Data preparation section**: A section where input is further processed into all variables needed to generate the output.
~1)**Output section**: At this point all data is prepared, so the final section does nothing but generate output: no new data is generated here, we only look up data prepared in the parameter or data preparation section (we can loop through an array though). (This is the same approach as with using a templating solution such as [[http://smarty.php.net/ Smarty]].)
This results in a clear and consistent logic of the code, and separation of process and data from presentation of the data.
==Dealing with multiple calendars==
The original code had a little function to derive the last day in a month; not only was this a somewhat inefficient, but having a function conflicted with including the code multiple times. The function has been replaced by (simpler) code in the data preparation section.
==Calendar generating logic==
Another difference is in the logic used for the start of the first week (when we may need to generate "blank" cells): this is taken out of the (original) loop, resulting in more logical and efficient code. The inverse of that is added at the end so that where necessary blank cells are added at the end of the table (having fewer cells in a row is not valid markup!).
==Internationalization==
Apart from two or three strings (defined in the constants section), the output produced by the action is completely internationalized: by using the appropriate functions the names for months and days (short and long) are already corresponding to the defined locale.
==Documentation==
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor∞]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
===Different code===
==A pattern for an action==
The new code follows a specific "pattern" which I use for all my (new or modified) action code. In general it goes like this:
~1)**Constants section**: A section where all constants are defined; this includes constants used as defaults for possible optional acrion parameters as well as constants defining user-interface strings (so that whatever needs to be translated for i18n is grouped together), and any other constants needed. Also "alsmost" constants like lookup tables are defined here.
~1)**Parameters section**: A section where parameters are read, and validated, using whatever is defined as defaults for optional parameters (constants, or dynamic defaults such as "current month"). In this case, invalid or otherwise unusable parameters are silently ignored (using defaults instead); depending on the purpose of an action, an invalid parameter (or missing required parameter) may generate an error message instead.
~1)**Data preparation section**: A section where input is further processed into all variables needed to generate the output.
~1)**Output section**: At this point all data is prepared, so the final section does nothing but generate output: no new data is generated here, we only look up data prepared in the parameter or data preparation section (we can loop through an array though). (This is the same approach as with using a templating solution such as [[http://smarty.php.net/ Smarty]].)
This results in a clear and consistent logic of the code, and separation of process and data from presentation of the data.
==Dealing with multiple calendars==
The original code had a little function to derive the last day in a month; not only was this a somewhat inefficient, but having a function conflicted with including the code multiple times. The function has been replaced by (simpler) code in the data preparation section.
==Calendar generating logic==
Another difference is in the logic used for the start of the first week (when we may need to generate "blank" cells): this is taken out of the (original) loop, resulting in more logical and efficient code. The inverse of that is added at the end so that where necessary blank cells are added at the end of the table (having fewer cells in a row is not valid markup!).
==Internationalization==
Apart from two or three strings (defined in the constants section), the output produced by the action is completely internationalized: by using the appropriate functions the names for months and days (short and long) are already corresponding to the defined locale.
==Documentation==
First, there is a documentation block at the start in [[http://www.phpdoc.org/ phpDocumentor∞]] format; from this documention block (combined with that for other Wikka code) we will not only be able to generate developer's documentation but also (with a special little Wikka parser) dynamic end user's documentation for the action.
In addtion, there are lots of other comments throughout the code; for instance, every line that contains code dealing with internationalization is marked with '##i18n##'.
Deletions:
Additions:
**Accessible** table code starts with proper **data table** markup, but requires a bit more. To start with, the ##table## should have a summary to explain what it's about; also, the header cells should actually be "linked" to the data cells they refer to, and obviously the navigation links (being just symbols here) need an explanation as well, which is added in the form of a title attribute. Similarly, the data cell for "today" (if shown) gets a title as well (since we should not depend on color alone to convey information). Finally a little trick suggested on an accessibility mailing list: using the ##abbr## attribute on the headers to //expand// the day names (an inverse of the original purpose of this attribute, but some screen readers used by people with a visual impairment can make use of this).
===Extra functionality===
=="Dynamic" and "static" calendars==
My variant of the Calendar action does all that the original GmBowenCalendar does, and one thing extra:
~-without any parameters, it generates a calendar for the current month, with navigation links, leading to:
~-it picks up on URL parameters for month and/or year, and shows the calendar for the specified month, also with navigation links
~-however, if parameters are provided in the action code itself a static calendar is shown for the month specified (taking the default for a missing parameter from the URL parameter(s) or the current month)
The result is that you can show a dynamic calendar which "reacts" to URL parameters //in addition to// any number of static calendars for a specific month.
I'll include code for a static month below - if my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}@@
(Until then you'd just see a calendar for the current month.)
===Extra functionality===
=="Dynamic" and "static" calendars==
My variant of the Calendar action does all that the original GmBowenCalendar does, and one thing extra:
~-without any parameters, it generates a calendar for the current month, with navigation links, leading to:
~-it picks up on URL parameters for month and/or year, and shows the calendar for the specified month, also with navigation links
~-however, if parameters are provided in the action code itself a static calendar is shown for the month specified (taking the default for a missing parameter from the URL parameter(s) or the current month)
The result is that you can show a dynamic calendar which "reacts" to URL parameters //in addition to// any number of static calendars for a specific month.
I'll include code for a static month below - if my proposed code is implemented, you should see here a calendar for March 2006:
@@{{calendar month="3" year="2006"}}@@
(Until then you'd just see a calendar for the current month.)