-module(html_calendar).
-export([cal/2,extract_date/1]).
-import(lists,[map/2,foldl/3]).
% The macro's below can be user defined %%%%%%%%%%%%%%%%%%%%%%%%%%
% but are server dependent, i.e., defined once, all calendars on that
% server will look the similar (depending on type setting commands
% in the style file.
-define(FIRSTDAY,{1997,8,25}).
-define(LASTDAY,element(1,calendar:local_time())).
-define(OPENDAY,element(1,calendar:local_time())).
-define(PreviousButton,"Previous").
-define(NoPreviousButton," ").
-define(NextButton,"Next").
-define(NoNextButton," ").
-define(LeftButton,"<").
-define(NoLeftButton," ").
-define(RightButton,">").
-define(NoRightButton," ").
-define(CAL,{class,"calendar"}). % typeset with calendar class
%
% the tags to define in the class definition in html are,
% either all, or table, tr, td and a
%
% EXAMPLE:
%
% The macro's above can be user defined %%%%%%%%%%%%%%%%%%%%%%%%%%
% Do not change the macro's below in the code, they can be
% manipulated by the function call, such that different
% calendars may exist on the same server.
-define(MONTHS,["Jan","Feb","Mar","Apr","May","Jun","Jul",
"Aug","Sep","Oct","Nov","Dec"]).
% The system assumes that MONDAY is day 1 (inherited from calendar.erl)
-define(WEEKDAYS,["m","t","w","t","f","s","s"]).
% cal(yaws_record(),Options) -> {ehtml,ehtml()}.
% where Options =
% [{monday_color,color_string()} | ... |
% {sunday_color,color_string()} |
% {monday_bgcolor,color_string() | ... |
% {sunday_bgcolor,color_string() |
% {today_color,color_string()} |
% {today_bgcolor,color_string()} |
% {months,[month_string()]} |
% {weekdays,[weekday_string()]}]
%
% EXAMPLE:
% out(A) -> html_calendar:cal(A,
% [{sunday_bgcolor,"#FF0000"},
% {today_color,"#FFFF00"},
% {weekdays,["må","ti","on","to",fr","lö","sö"]}]).
cal(Arg,Options) ->
Date =
extract_date(Arg),
Months =
alternative(Options,months,?MONTHS),
Weekdays =
alternative(Options,weekdays,?WEEKDAYS),
Colors =
days_to_numbers(Options),
{ehtml,{table,[?CAL],
[{tr,[?CAL],[
{td,[?CAL,{align,"left"},{colspan,"3"}],
ref_yesterday(Date)},
{td,[?CAL,{align,"right"},{colspan,"4"}],
ref_tomorrow(Date)}
]},
{tr,[?CAL],[ {td,[?CAL,{colspan,"7"}]," "}
]},
{tr,[?CAL],[
{td,[?CAL,{colspan,"2"},{align,"right"}],
ref_lastmonth(Date)},
{td,[?CAL,{colspan,"3"},{align,"center"}],
lists:nth(month(Date),Months)},
{td,[?CAL,{colspan,"2"},{align,"left"}],
ref_nextmonth(Date)}
]},
{tr,[?CAL],[
{td,[?CAL,{colspan,"2"},{align,"right"}],
ref_lastyear(Date)},
{td,[?CAL,{colspan,"3"},{align,"center"}],
integer_to_list(year(Date))},
{td,[?CAL,{colspan,"2"},{align,"left"}],
ref_nextyear(Date)}
]},
{tr,[?CAL],[{td,[?CAL,{colspan,"7"}]," "}]},
{tr,[?CAL],
foldl(fun(D,TDs) ->
TDs++
[{td,[?CAL,{align,"right"}|
check_colors(Colors,length(TDs)+1,false)],
{text,
check_colors(Colors,length(TDs)+1,false),D}}]
end,[],Weekdays)},
map(fun(Week) ->
{tr,[?CAL],the_days(Week,Date,Colors,1)}
end, weeks(Date))
]}}.
extract_date(Arg) ->
PostedDate =
case catch lists:keysearch(date,1,yaws_api:parse_query(Arg)) of
{value, {date, [Y1,Y2,Y3,Y4,M1,M2,D1,D2]}} ->
Year = list_to_integer([Y1,Y2,Y3,Y4]),
Month = list_to_integer([M1,M2]),
Day = list_to_integer([D1,D2]),
case calendar:valid_date(Year,Month,Day) of
true ->
{Year,Month,Day};
false ->
?OPENDAY
end;
_ ->
?OPENDAY
end.
ref_yesterday(Date) ->
case Date > ?FIRSTDAY of
true ->
{a,[?CAL,date_to_ref(yesterday(Date))],
?PreviousButton};
false ->
?NoPreviousButton
end.
ref_tomorrow(Date) ->
case Date < ?LASTDAY of
true ->
{a,[?CAL,date_to_ref(tomorrow(Date))],
?NextButton};
false ->
?NoNextButton
end.
ref_lastyear(Date) ->
case lastyear(Date) > ?FIRSTDAY of
true ->
{a,[?CAL,date_to_ref(lastyear(Date))],
?LeftButton};
false ->
?NoLeftButton
end.
ref_nextyear(Date) ->
case nextyear(Date) < ?LASTDAY of
true ->
{a,[?CAL,date_to_ref(nextyear(Date))],
?RightButton};
false ->
?NoRightButton
end.
ref_lastmonth(Date) ->
case lastmonth(Date) > ?FIRSTDAY of
true ->
{a,[?CAL,date_to_ref(lastmonth(Date))],
?LeftButton};
false ->
?NoLeftButton
end.
ref_nextmonth(Date) ->
case nextmonth(Date) < ?LASTDAY of
true ->
{a,[?CAL,date_to_ref(nextmonth(Date))],
?RightButton};
false ->
?NoRightButton
end.
the_days([],_,_,_) ->
[];
the_days([0|Ds],Date,Colors,WeekDay) ->
[{td,[?CAL],""}|the_days(Ds,Date,Colors,WeekDay+1)];
the_days([D|Ds],Date,Colors,WeekDay) ->
{YY,MM,_} = Date, % ugly abstraction violation
Today = ({YY,MM,D} == element(1,calendar:local_time())),
[{td,[?CAL,{align,right}|check_colors(Colors,WeekDay,Today)],
case (?FIRSTDAY =< {YY,MM,D}) and ({YY,MM,D} =< ?LASTDAY) of
true ->
{a,[?CAL,date_to_ref({YY,MM,D})|
check_colors(Colors,WeekDay,Today)],
integer_to_list(D)};
false ->
{text,check_colors(Colors,WeekDay,Today),
integer_to_list(D)}
end}|the_days(Ds,Date,Colors,WeekDay+1)].
check_colors(Colors,N,Today) ->
FG =
case Today of
true ->
case check_color(Colors,0) of
[] ->
check_color(Colors,N);
FGColor ->
FGColor
end;
false ->
check_color(Colors,N)
end,
BG =
case Today of
true ->
case check_bgcolor(Colors,0) of
[] ->
check_bgcolor(Colors,N);
BGColor ->
BGColor
end;
false ->
check_bgcolor(Colors,N)
end,
case join(FG++BG,"; ") of
"" ->
[];
Style ->
[{style,Style}]
end.
check_color(Colors,N) ->
case lists:keysearch(N,1,[ {D,V} || {color,D,V}<-Colors ]) of
{value,{_,FGColor}} ->
["color:"++FGColor];
_ ->
[]
end.
check_bgcolor(Colors,N) ->
case lists:keysearch(N,1,[ {D,V} || {bgcolor,D,V}<-Colors ]) of
{value,{_,BGColor}} ->
["background-color:"++BGColor];
_ ->
[]
end.
% destructors %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
year({Y,M,D}) -> Y.
month({Y,M,D}) -> M.
day({Y,M,D}) -> D.
% constructors %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
lastyear({Year,Month,Day}) ->
{Year-1,Month,Day}.
nextyear({Year,Month,Day}) ->
{Year+1,Month,Day}.
lastmonth({Year,Month,Day}) ->
case Month==1 of
true ->
{Year-1,12,Day};
false ->
NrDays = calendar:last_day_of_the_month(Year,Month-1),
case Day>NrDays of
true ->
{Year,Month-1,NrDays};
false ->
{Year,Month-1,Day}
end
end.
nextmonth({Year,Month,Day}) ->
case Month==12 of
true ->
{Year+1,1,Day};
false ->
NrDays = calendar:last_day_of_the_month(Year,Month+1),
case Day>NrDays of
true ->
{Year,Month+1,NrDays};
false ->
{Year,Month+1,Day}
end
end.
yesterday({Year,Month,Day}) ->
case Day==1 of
true ->
lastmonth({Year,Month,31});
false ->
{Year,Month,Day-1}
end.
tomorrow({Year,Month,Day}) ->
case calendar:last_day_of_the_month(Year,Month) == Day of
true ->
nextmonth({Year,Month,1});
false ->
{Year,Month,Day+1}
end.
weeks(Date) ->
{Year,Month,Day} = Date,
Days =
nodays(calendar:day_of_the_week(Year,Month,1))++
lists:seq(1,calendar:last_day_of_the_month(Year,Month)),
splitweek(Days).
nodays(1) ->
[];
nodays(N) when N>1 ->
[0|nodays(N-1)].
splitweek([]) ->
[];
splitweek(Days) ->
[heads(7,Days)|splitweek(tails(7,Days))].
heads(N,[]) ->
[];
heads(0,Xs) ->
[];
heads(N,[X|Xs]) ->
[X|heads(N-1,Xs)].
tails(N,[]) ->
[];
tails(0,Xs) ->
Xs;
tails(N,[X|Xs]) ->
tails(N-1,Xs).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
date_to_ref(Date) ->
{href,"?date="++
integer_to_list(year(Date))++
case month(Date)<10 of
true ->
"0";
false ->
""
end ++ integer_to_list(month(Date))++
case day(Date)<10 of
true ->
"0";
false ->
""
end ++integer_to_list(day(Date))}.
join([],Sep) ->
"";
join([S],Sep) ->
S;
join([S|Ss],Sep) ->
S++Sep++join(Ss,Sep).
alternative(KeyList,Key,Default) ->
case lists:keysearch(Key,1,KeyList) of
{value,{_,Value}} ->
if is_list(Value) and is_list(Default) ->
case length(Value)==length(Default) of
true ->
Value;
false ->
Default
end;
true ->
Value
end;
_ ->
Default
end.
% The price to pay for readable yaws pages
%
days_to_numbers(Options) ->
foldl(fun({K,V},Cs) ->
case K of
today_color -> [{color,0,V}|Cs];
today_bgcolor -> [{bgcolor,0,V}|Cs];
monday_color -> [{color,1,V}|Cs];
monday_bgcolor -> [{bgcolor,1,V}|Cs];
tuesday_color -> [{color,2,V}|Cs];
tuesday_bgcolor -> [{bgcolor,2,V}|Cs];
wednesday_color -> [{color,3,V}|Cs];
wednesday_bgcolor -> [{bgcolor,3,V}|Cs];
thursday_color -> [{color,4,V}|Cs];
thursday_bgcolor -> [{bgcolor,4,V}|Cs];
friday_color -> [{color,5,V}|Cs];
friday_bgcolor -> [{bgcolor,5,V}|Cs];
saterday_color -> [{color,6,V}|Cs];
saterday_bgcolor -> [{bgcolor,6,V}|Cs];
sunday_color -> [{color,7,V}|Cs];
sunday_bgcolor -> [{bgcolor,7,V}|Cs]
end
end,[],Options).