package HTML::Calendar::Simple;
$HTML::Calendar::Simple::VERSION = "0.04";
=head1 NAME
HTML::Calendar::Simple - A simple html calendar
=head1 SYNOPSIS
use HTML::Calendar::Simple;
my $cal = HTML::Calendar::Simple->new; # This month, this year
$cal = HTML::Calendar::Simple->new({ 'month' => $month }); # This year
$cal = HTML::Calendar::Simple->new({ 'month' => $month,
'year' => $year});
my $month = $cal->month;
my $year = $cal->year;
$cal->pin_up(a_picture_location);
$cal->daily_info({ 'day' => $day,
'day_link' => $location,
$type1 => $info1,
$type2 => $info2,
'link' => [$link, $tag],
});
print $cal; # stringifies to something like the output of cal
my $html = $cal->calendar_month;
my $html = HTML::Calendar::Simple->calendar_year;
$html = HTML::Calendar::Simple->calendar_year({ 'year' => $year });
$html = HTML::Calendar::Simple->calendar_year(
{ 'pin_up' => $where_to_find_the_picture,
'year' => $year,
$month => { $day1 => $link1,
$day2 => $link2, }
});
=head1 DESCRIPTION
This is a simple module which will make an HTML representation of a
given month. You can add links to individual days, or in fact, any
sort of information you want.
Yes, the inspiration for this came out of me looking at
HTML::CalendarMonthSimple, and thinking 'Hmmm. A bit too complicated
for what I want. I know, I will write a simplified version.' So I did.
=cut
use strict;
use Date::Simple;
use CGI;
use overload
'""' => '_stringify';
my %days = ( 'Sun' => 0, 'Mon' => 1,
'Tue' => 2, 'Wed' => 3,
'Thu' => 4, 'Fri' => 5,
'Sat' => 6 );
my %months = ( 1 => 'Jan', 2 => 'Feb', 3 => 'Mar',
4 => 'Apr', 5 => 'May', 6 => 'Jun',
7 => 'Jul', 8 => 'Aug', 9 => 'Sep',
10 => 'Oct', 11 => 'Nov', 12 => 'Dec' );
=head2 new
my $cal = HTML::Calendar::Simple->new;
my $cal = HTML::Calendar::Simple->new({ 'month' => $month });
my $cal = HTML::Calendar::Simple->new({ 'month' => $month,
'year' => $year });
This will make a new HTML::Calendar::Simple object.
=cut
sub new {
my $self = {};
bless $self, shift;
$self->_init(@_);
return $self;
}
sub _init {
my $self = shift;
# validate the args passed to new, if there were any.
my $valid_day = Date::Simple->new;
my $ref = shift;
if (defined $ref && ref $ref eq 'HASH') {
my $month = exists $ref->{month} ? $ref->{month} : $valid_day->month;
my $year = exists $ref->{year} ? $ref->{year} : $valid_day->year;
$valid_day = $self->_date_obj($year, $month, 1);
$valid_day = defined $valid_day ? $valid_day : Date::Simple->new;
}
$self->{month} = $valid_day->month;
$self->{year} = $valid_day->year;
$self->{the_month} = $self->_days_list($self->{month}, $self->{year});
$self;
}
=head2 month
my $month = $cal->month;
This will return the numerical value of the month.
=head2 year
my $year = $cal->year;
This will return the four-digit year of the calendar
=cut
sub month { $_[0]->{month} } # month in numerical format
sub year { $_[0]->{year} } # year in YYYY form
sub _spacer { return "" } # the filler for the first few entries
sub _the_month { @{ $_[0]->{the_month} } } # this is the list of hashrefs.
sub _cgi {
my $self = shift;
unless (exists $self->{cgi}) { $self->{cgi} = CGI->new; }
return $self->{cgi};
}
=head2 daily_info
$cal->daily_info({ 'day' => $day,
'day_link' => $location, # puts an href on the day
$type1 => $info1,
$type2 => $info2,
'link' => [$link, $tag],
});
This will record that fact that $info of $type happen(s|ed) on $day.
Now, if there is no method defined to cope with $type, then the information
pased as $info will just be text printed in the cell of $day. So, if you want
something special to happen to (say) a type of 'meeting', you would have to
define a method called _meeting.
For example:
$cal->daily_info({ 'day' => 12,
'meeting' => 'Meet swm' });
and somewhere else in this module...
sub _meeting {
my $self = shift;
return $self->_cgi->h1( shift );
}
So any day that had a meeting key in its hash would be displayed as
an
$info
Note: If you call daily_info again with the same day with the same type
BUT with different info, then the old info will get clobbered.
There is already one method in here, and that is _link. So, you can do:
$cal->daily_info({ 'day' => $day,
'link' => [$link, $tag],
});
Note that the key 'link' takes an array ref.
Also, if you don't pass valid uris as values of the keys 'link' and
'day_link', well, that is your out if they don't work!
=cut
sub daily_info {
my $self = shift;
my $ref = shift or return;
ref $ref eq 'HASH' or return;
my $day = $self->_date_obj($self->year, $self->month, $ref->{'day'})
or return;
my %info = %{ $ref };
delete $info{'day'};
foreach my $day_ref ($self->_the_month) {
next unless $day_ref && $day_ref->{date} == $day;
$day_ref->{$_} = $info{$_} foreach keys %info;
last;
}
}
# Glerg. Make each cell in the calendar table a table of its own. And each row
# of this table will contain a little snippet of information.
sub _row_elem {
my $self = shift;
my $ref = shift or return $self->_spacer;
return $ref if $ref eq $self->_spacer;
my $q = $self->_cgi;
my $day = exists $ref->{day_link}
? $q->a({ -href => $ref->{day_link} }, $ref->{date}->day)
: $ref->{date}->day;
my $elem = $q->start_table . $q->Tr($q->td($day));
my %info = %{ $ref };
foreach my $key (keys %info) {
next if ($key eq 'date' or $key eq 'day_link');
my $method = "_$key";
$elem .= $self->can($method)
? $q->Tr($q->td($self->$method($info{$key})))
: $q->Tr($q->td($info{$key}));
}
$elem .= $q->end_table;
return $elem;
}
sub _link {
my $self = shift;
my $ref = shift or return;
ref $ref eq 'ARRAY' or return;
my ($link, $tag) = @$ref;
return $self->_cgi->a({ -href => $link }, $tag);
}
sub _table_row {
my $self = shift;
my @week = @_; my @row;
push @row, $self->_row_elem($_) foreach @week;
return @row;
}
=head2 pin_up
$cal->pin_up(a_picture_with_location);
This will add a picture above the calendar month, just like the
calendar I have hanging up in my kitchen, (It is a cat calendar, if
you are interested, as my second son loves cats. As do I!)
This could be used to have a mechanic's garage Pirelli-style pr0n
calendar, but that would be your call. Mine would be something including
a Triumph Daytona 955i. Mmmm, nice.
=cut
sub pin_up {
my ($self, $pic) = @_;
return unless $pic;
$self->{picture} = $pic;
}
sub picture {
my $self = shift;
return exists $self->{picture} ? $self->{picture} : 0;
}
=head2 calendar_month
my $html = $cal->calendar_month;
This will return an html string of the calendar month in question.
=head2 html
my $html = $cal->html;
This will return an html string of the calendar month in question.
THIS CALL HAS BEEN DEPRECATED.
=cut
sub html { $_[0]->calendar_month }
sub calendar_month {
my $self = shift;
my @seq = $self->_the_month;
my $q = $self->_cgi;
my $mnth = $q->h3($months{$self->month} . " " . $self->year);
my $cal = $q->start_table({-border => 1})
. $q->th([sort { $days{$a} <=> $days{$b} } keys %days]);
while (@seq) {
my @week_row = $self->_table_row(splice @seq, 0, 7);
$cal .= $q->Tr($q->td([@week_row]));
}
$cal .= $q->end_table;
$cal = $q->start_table . $q->Tr($q->td({ align => 'center' }, $mnth))
. $q->Tr($q->td($cal)) . $q->end_table;
$cal = $self->_add_pic($cal) if $self->picture;
return $cal;
}
=head2 calendar_year
my $html = HTML::Calendar::Simple->calendar_year;
$html = HTML::Calendar::Simple->calendar_year({ 'year' => $year });
$html = HTML::Calendar::Simple->calendar_year(
{ 'pin_up' => $where_to_find_the_picture,
'year' => $year,
$month => { $day1 => $link1,
$day2 => $link2, }
});
This will return the an html string for every month in the year passed,
or the current year if nothing passed in.
This key of the hashref month is *another* hashref, where the key here
is the day in that month, and the value a link.
This is icky, I know, and now puts me in mind of making HTML::Calendar::Day,
HTML::Calendar::Month and HTML::Calendar::Year, and having an overarching
HTML::Calendar.
=cut
sub _generate_months {
my ($class, $year, $ref) = @_;
my @year;
for my $month (1 .. 12) {
my $cal = $class->new({ 'month' => $month, 'year' => $year });
if (defined $ref->{$month}) {
my %links = %{ $ref->{$month} };
foreach my $day (keys %links) {
$cal->daily_info({ 'day' => $day,
'day_link' => $links{$day},
});
}
}
push @year, $cal;
}
return @year;
}
sub calendar_year {
my ($class, $ref) = @_;
my $year = $ref->{year};
my $when = defined $year
? Date::Simple->new($year, 1, 1)
: Date::Simple->new;
$when = defined $when ? $when : Date::Simple->new;
$year = $when->year;
my @year = $class->_generate_months($year, $ref);
my $year_string;
my $q = CGI->new;
while (@year) {
my @qrtr = map { $_->calendar_month } splice @year, 0, 3;
s/$year//g for @qrtr;
$year_string .= $q->start_table . $q->Tr($q->td({valign => 'top'}, [@qrtr]))
. $q->end_table . $q->br;
}
my $pic = defined $ref->{'pin_up'} ? $ref->{'pin_up'} : "";
$pic = $q->Tr($q->td({ align => 'center' }, $q->img({ src => $pic }))) if $pic;
$year_string = $q->start_table . $pic . $q->th($year)
. $q->Tr($q->td($year_string))
. $q->end_table;
return $year_string;
}
sub _add_pic {
my ($self, $cal) = @_;
my $q = $self->_cgi;
return $q->start_table
. $q->Tr($q->td({ align => 'center' },
$q->img({ src => $self->picture })))
. $q->Tr($q->td($cal))
. $q->end_table;
}
sub _date_obj { Date::Simple->new($_[1], $_[2], $_[3]) }
# here is the format of what is returned from this call. Let us say a list of
# hashrefs, so that I can tag lots of things in with it. Ick, I know, but this
# is just a messing-about at the mo. And a hashref, mmmm, makes me think of
# an object is needed here. A Day object if I thieved an idea from somewhere else.
sub _days_list {
my $self = shift;
# Fill in a Date::Simple object for every day, Why not Date::Range object?
# Because I haven't installed it yet, and not sure it would be appropriate
# for the way I have set this up.
my ($month, $year) = @_;
my $start = $self->_date_obj($year, $month, 1);
my $end = $start + 31;
$end = $self->_date_obj($end->year, $end->month, 1);
my @seq = map $self->_spacer, (1 .. $days{$start->format("%a")});
push @seq, { 'date' => $start++ } while ($start < $end);
return \@seq;
}
sub _stringify {
my $self = shift;
my @month = $self->_the_month;
my $string = "\t\t\t" . $months{ $self->month } . " " . $self->year . "\n\n";
$string .= join "\t", sort { $days{$a} <=> $days{$b} } keys %days;
$string .= "\n";
while (@month) {
$string .= join "\t", map { $_ eq $self->_spacer ? "" : $_->{date}->day }
splice @month, 0, 7;
$string .= "\n";
}
return $string;
}
=head1 BUGS
None known
=head2 TODO
Oh....lots of things.
o Rip out the CGI stuff and put all the HTML in a template, so the user
can decide on the format of the calendar themselves.
o Allow for the setting of borders etc like HTML::CalendarMonthSimple.
o Format the output better if there is info in a daily cell.
o Perhaps overload '.' so you could add two calendars. Not sure.
o Check the links passed in are of format http://www.stray-toaster.co.uk
or something.
o Get rid of the days and months hashes and replace with something better.
o And if all that happens, it may as well be HTML::CalendarMonthSimple!!
o Make HTML::Calendar::Day, HTML::Calendar::Month and HTML::Calendar::Year
=head1 SHOWING YOUR APPRECIATION
There was a thread on london.pm mailing list about working in a vacumn
- that it was a bit depressing to keep writing modules but never get
any feedback. So, if you use and like this module then please send me
an email and make my day.
All it takes is a few little bytes.
(Leon wrote that, not me!)
=head1 AUTHOR
Stray Toaster EFE
=head2 With Thanks
o To swm EFE for some roadtesting!
o To F for the pin-up idea
=head1 COPYRIGHT
Copyright (C) 2002, mwk
This module is free software; you can redistribute it or modify it
under the same terms as Perl itself.
=cut
return qw/Now beat it you bother me/;