annotate datebox.ur @ 4:377c11586999

Fully-functional Datebox
author Adam Chlipala <adam@chlipala.net>
date Tue, 08 Feb 2011 16:52:29 -0500
parents 8cab48efaff2
children 4385bc6a0d2d
rev   line source
adam@2 1 style calendar
adam@2 2 style prev
adam@2 3 style this
adam@2 4 style next
adam@2 5 style weekday
adam@2 6 style curday
adam@2 7 style otherday
adam@4 8 style selday
adam@2 9
adam@2 10 datatype month = Prev | This | Next
adam@2 11
adam@4 12 type date = {Year : int, Month : int, Day : int}
adam@4 13
adam@2 14 type t = {Month : source {ThisMonth : int, Year : int,
adam@2 15 ThisMonthLength : int, PrevMonthLength : int,
adam@2 16 MondayMonth : month, MondayDay : int,
adam@2 17 PrevMonthName : string, ThisMonthName : string, NextMonthName : string},
adam@4 18 Day : source date,
adam@4 19 Hide : source bool}
adam@2 20
adam@2 21 fun pad len n =
adam@2 22 let
adam@2 23 val s = show n
adam@2 24
adam@2 25 fun pad' len s =
adam@2 26 if len <= 0 then
adam@2 27 s
adam@2 28 else
adam@2 29 pad' (len-1) ("0" ^ s)
adam@2 30 in
adam@2 31 pad' (len - String.length s) s
adam@2 32 end
adam@2 33
adam@2 34 fun monthLen m =
adam@2 35 let
adam@2 36 fun f n tm =
adam@2 37 let
adam@2 38 val next = addSeconds tm (60 * 60 * 24)
adam@2 39 val nextMon = readError (timef "%m" next)
adam@2 40 in
adam@2 41 if nextMon = m.Month then
adam@2 42 f (n+1) next
adam@2 43 else
adam@2 44 n
adam@2 45 end
adam@2 46 in
adam@2 47 f 28 (readError (show m.Year ^ "-" ^ pad 2 m.Month ^ "-28 00:00:00"))
adam@2 48 end
adam@2 49
adam@2 50 fun weekdayToNum s =
adam@2 51 case s of
adam@2 52 "Mon" => 0
adam@2 53 | "Tue" => 1
adam@2 54 | "Wed" => 2
adam@2 55 | "Thu" => 3
adam@2 56 | "Fri" => 4
adam@2 57 | "Sat" => 5
adam@2 58 | "Sun" => 6
adam@2 59 | _ => error <xml>Datebox: Bad weekday name</xml>
adam@2 60
adam@2 61 fun timeOfMonth my =
adam@2 62 readError (show my.Year ^ "-" ^ pad 2 my.Month ^ "-01 00:00:00")
adam@2 63
adam@3 64 fun monthInfo' this =
adam@2 65 let
adam@2 66 val prev = if this.Month = 1 then
adam@2 67 {Month = 12, Year = this.Year - 1}
adam@2 68 else
adam@2 69 {Month = this.Month-1, Year = this.Year}
adam@2 70 val prevLen = monthLen prev
adam@2 71
adam@2 72 val next = if this.Month = 12 then
adam@2 73 {Month = 1, Year = this.Year + 1}
adam@2 74 else
adam@2 75 {Month = this.Month + 1, Year = this.Year}
adam@2 76
adam@2 77 val firstDow = weekdayToNum (timef "%a" (timeOfMonth this))
adam@2 78 in
adam@2 79 {ThisMonth = this.Month, Year = this.Year,
adam@2 80 ThisMonthLength = monthLen this,
adam@2 81 PrevMonthLength = prevLen,
adam@2 82 MondayMonth = if firstDow = 0 then This else Prev,
adam@2 83 MondayDay = if firstDow = 0 then 1 else prevLen - (firstDow - 1),
adam@2 84 PrevMonthName = timef "%b" (timeOfMonth prev),
adam@2 85 ThisMonthName = timef "%b" (timeOfMonth this),
adam@2 86 NextMonthName = timef "%b" (timeOfMonth next)}
adam@2 87 end
adam@2 88
adam@3 89 fun monthInfo this = return (monthInfo' this)
adam@3 90
adam@4 91 fun create tm =
adam@2 92 year <- return (readError (timef "%Y" tm));
adam@2 93 month <- return (readError (timef "%m" tm));
adam@4 94 minf <- source (monthInfo' {Month = month, Year = year});
adam@2 95 day <- return (readError (timef "%d" tm));
adam@4 96 day <- source {Year = year, Month = month, Day = day};
adam@4 97 hide <- source True;
adam@4 98 return {Month = minf, Day = day, Hide = hide}
adam@2 99
adam@4 100 fun render' t =
adam@4 101 minf <- signal t.Month;
adam@4 102 let
adam@4 103 fun rows year month day =
adam@4 104 let
adam@4 105 fun row year month day weekday =
adam@4 106 if weekday >= 7 then
adam@4 107 <xml/>
adam@4 108 else
adam@4 109 <xml>
adam@4 110 <dyn signal={let
adam@4 111 val thisDate = {Year = year,
adam@4 112 Month = case month of
adam@4 113 Prev => if minf.ThisMonth = 1 then 12 else minf.ThisMonth - 1
adam@4 114 | This => minf.ThisMonth
adam@4 115 | Next => if minf.ThisMonth = 12 then 1 else minf.ThisMonth + 1,
adam@4 116 Day = day}
adam@4 117 in
adam@4 118 cday <- signal t.Day;
adam@4 119 return (if Record.equal thisDate cday then
adam@4 120 <xml><td class={selday}>{[day]}</td></xml>
adam@4 121 else case month of
adam@4 122 This => <xml><td class={curday}
adam@4 123 onclick={set t.Day thisDate}>{[day]}</td></xml>
adam@4 124 | _ => <xml><td class={otherday}>{[day]}</td></xml>)
adam@4 125 end}/>
adam@4 126 {let
adam@4 127 val (year, month, day) =
adam@4 128 case month of
adam@4 129 Prev => if day = minf.PrevMonthLength then
adam@4 130 (if minf.ThisMonth = 1 then year + 1 else year, This, 1)
adam@4 131 else
adam@4 132 (year, Prev, day+1)
adam@4 133 | This => if day = minf.ThisMonthLength then
adam@4 134 (if minf.ThisMonth = 12 then year + 1 else year, Next, 1)
adam@4 135 else
adam@4 136 (year, This, day+1)
adam@4 137 | Next => (year, Next, day+1)
adam@4 138 in
adam@4 139 row year month day (weekday+1)
adam@4 140 end}
adam@4 141 </xml>
adam@4 142 in
adam@4 143 case month of
adam@4 144 Next => <xml/>
adam@4 145 | _ =>
adam@4 146 <xml>
adam@4 147 <tr>{row year month day 0}</tr>
adam@4 148 {let
adam@4 149 val next =
adam@4 150 case month of
adam@4 151 Prev => Some (if minf.ThisMonth = 1 then year + 1 else year, This, day + 7 - minf.PrevMonthLength)
adam@4 152 | This =>
adam@4 153 Some (if day + 7 > minf.ThisMonthLength then
adam@4 154 (if minf.ThisMonth = 12 then year + 1 else year, Next, day + 7 - minf.ThisMonthLength)
adam@4 155 else
adam@4 156 (year, This, day + 7))
adam@4 157 | Next => None
adam@2 158 in
adam@4 159 case next of
adam@4 160 None => <xml/>
adam@4 161 | Some (year, month, day) => rows year month day
adam@4 162 end}
adam@4 163 </xml>
adam@4 164 end
adam@4 165 in
adam@4 166 return <xml>
adam@4 167 <table class={calendar}>
adam@4 168 <tr>
adam@4 169 <th class={prev} colspan={2}
adam@4 170 onclick={minf <- rpc (monthInfo (if minf.ThisMonth = 1 then
adam@4 171 {Month = 12, Year = minf.Year - 1}
adam@4 172 else
adam@4 173 {Month = minf.ThisMonth - 1, Year = minf.Year}));
adam@4 174 set t.Month minf}>&lt;&lt; {[minf.PrevMonthName]}</th>
adam@4 175 <th class={this} colspan={3}>{[minf.ThisMonthName]} {[minf.Year]}</th>
adam@4 176 <th class={next} colspan={2}
adam@4 177 onclick={minf <- rpc (monthInfo (if minf.ThisMonth = 12 then
adam@4 178 {Month = 1, Year = minf.Year + 1}
adam@4 179 else
adam@4 180 {Month = minf.ThisMonth + 1, Year = minf.Year}));
adam@4 181 set t.Month minf}>{[minf.NextMonthName]} >></th>
adam@4 182 </tr>
adam@4 183 <tr class={weekday}> <th>M</th> <th>Tu</th> <th>W</th> <th>Th</th> <th>F</th> <th>Sa</th> <th>Su</th> </tr>
adam@4 184 {rows (if minf.ThisMonth = 1 && (case minf.MondayMonth of This => False | _ => True) then
adam@4 185 minf.Year - 1
adam@4 186 else
adam@4 187 minf.Year) minf.MondayMonth minf.MondayDay}
adam@4 188 </table>
adam@4 189 </xml>
adam@4 190 end
adam@2 191
adam@4 192 fun render t = <xml>
adam@4 193 <dyn signal={date <- signal t.Day;
adam@4 194 return <xml>{[date.Year]}-{[date.Month]}-{[date.Day]}</xml>}/>
adam@4 195 <dyn signal={hd <- signal t.Hide;
adam@4 196 if hd then
adam@4 197 return <xml><button value="Choose" onclick={set t.Hide False}/></xml>
adam@4 198 else
adam@4 199 main <- render' t;
adam@4 200 return <xml><button value="Hide" onclick={set t.Hide True}/><br/>{main}</xml>}/>
adam@4 201 </xml>
adam@4 202
adam@4 203 fun value t = signal t.Day