Mercurial > hg > index.fcgi > www > www-1
changeset 81:256b8df1c686
add life_calendar
author | paulo |
---|---|
date | Fri, 17 Jun 2016 22:24:17 -0700 |
parents | ff0878207f0e |
children | d7d67887102f |
files | life_calendar/happy.js life_calendar/index.html life_calendar/tooltip.js |
diffstat | 3 files changed, 578 insertions(+), 0 deletions(-) [+] |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/life_calendar/happy.js Fri Jun 17 22:24:17 2016 -0700 1.3 @@ -0,0 +1,134 @@ 1.4 +/*global $*/ 1.5 +(function happyJS($) { 1.6 + function trim(el) { 1.7 + return (''.trim) ? el.val().trim() : $.trim(el.val()); 1.8 + } 1.9 + $.fn.isHappy = function isHappy(config) { 1.10 + var fields = [], item; 1.11 + var pauseMessages = false; 1.12 + 1.13 + function isFunction(obj) { 1.14 + return !!(obj && obj.constructor && obj.call && obj.apply); 1.15 + } 1.16 + function defaultError(error) { //Default error template 1.17 + var msgErrorClass = config.classes && config.classes.message || 'unhappyMessage'; 1.18 + return $('<span id="' + error.id + '" class="' + msgErrorClass + '" role="alert">' + error.message + '</span>'); 1.19 + } 1.20 + function getError(error) { //Generate error html from either config or default 1.21 + if (isFunction(config.errorTemplate)) { 1.22 + return config.errorTemplate(error); 1.23 + } 1.24 + return defaultError(error); 1.25 + } 1.26 + function handleSubmit() { 1.27 + var i, l; 1.28 + var errors = false; 1.29 + for (i = 0, l = fields.length; i < l; i += 1) { 1.30 + if (!fields[i].testValid(true)) { 1.31 + errors = true; 1.32 + } 1.33 + } 1.34 + if (errors) { 1.35 + if (isFunction(config.unHappy)) config.unHappy(); 1.36 + return false; 1.37 + } else if (config.testMode) { 1.38 + if (isFunction(config.happy)) return config.happy(); 1.39 + if (window.console) console.warn('would have submitted'); 1.40 + return false; 1.41 + } 1.42 + if (isFunction(config.happy)) return config.happy(); 1.43 + } 1.44 + function handleMouseUp() { 1.45 + pauseMessages = false; 1.46 + } 1.47 + function handleMouseDown() { 1.48 + pauseMessages = true; 1.49 + $(window).bind('mouseup', handleMouseUp); 1.50 + } 1.51 + function processField(opts, selector) { 1.52 + var field = $(selector); 1.53 + var error = { 1.54 + message: opts.message || '', 1.55 + id: selector.slice(1) + '_unhappy' 1.56 + }; 1.57 + var errorEl = $(error.id).length > 0 ? $(error.id) : getError(error); 1.58 + var handleBlur = function handleBlur() { 1.59 + if (!pauseMessages) { 1.60 + field.testValid(); 1.61 + } else { 1.62 + $(window).bind('mouseup', field.testValid.bind(this)); 1.63 + } 1.64 + }; 1.65 + 1.66 + fields.push(field); 1.67 + field.testValid = function testValid(submit) { 1.68 + var val, gotFunc, temp; 1.69 + var el = $(this); 1.70 + var errorTarget = (opts.errorTarget && $(opts.errorTarget)) || el; 1.71 + var error = false; 1.72 + var required = !!el.get(0).attributes.getNamedItem('required') || opts.required; 1.73 + var password = (field.attr('type') === 'password'); 1.74 + var arg = isFunction(opts.arg) ? opts.arg() : opts.arg; 1.75 + var fieldErrorClass = config.classes && config.classes.field || 'unhappy'; 1.76 + 1.77 + // handle control groups (checkboxes, radio) 1.78 + if (el.length > 1) { 1.79 + val = []; 1.80 + el.each(function(i,obj) { 1.81 + val.push($(obj).val()); 1.82 + }); 1.83 + val = val.join(','); 1.84 + } else { 1.85 + // clean it or trim it 1.86 + if (isFunction(opts.clean)) { 1.87 + val = opts.clean(el.val()); 1.88 + } else if (!password && typeof opts.trim === 'undefined' || opts.trim) { 1.89 + val = trim(el); 1.90 + } else { 1.91 + val = el.val(); 1.92 + } 1.93 + 1.94 + // write it back to the field 1.95 + el.val(val); 1.96 + } 1.97 + 1.98 + // get the value 1.99 + gotFunc = ((val.length > 0 || required === 'sometimes') && isFunction(opts.test)); 1.100 + 1.101 + // check if we've got an error on our hands 1.102 + if (submit === true && required === true && val.length === 0) { 1.103 + error = true; 1.104 + } else if (gotFunc) { 1.105 + error = !opts.test(val, arg); 1.106 + } 1.107 + 1.108 + if (error) { 1.109 + errorTarget.addClass(fieldErrorClass).after(errorEl); 1.110 + return false; 1.111 + } else { 1.112 + temp = errorEl.get(0); 1.113 + // this is for zepto 1.114 + if (temp.parentNode) { 1.115 + temp.parentNode.removeChild(temp); 1.116 + } 1.117 + errorTarget.removeClass(fieldErrorClass); 1.118 + return true; 1.119 + } 1.120 + }; 1.121 + field.bind(opts.when || config.when || 'blur', handleBlur); 1.122 + } 1.123 + 1.124 + for (item in config.fields) { 1.125 + processField(config.fields[item], item); 1.126 + } 1.127 + 1.128 + $(config.submitButton || this).bind('mousedown', handleMouseDown); 1.129 + 1.130 + if (config.submitButton) { 1.131 + $(config.submitButton).click(handleSubmit); 1.132 + } else { 1.133 + this.bind('submit', handleSubmit); 1.134 + } 1.135 + return this; 1.136 + }; 1.137 +})(this.jQuery || this.Zepto);
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/life_calendar/index.html Fri Jun 17 22:24:17 2016 -0700 2.3 @@ -0,0 +1,386 @@ 2.4 +<html> 2.5 + <head> 2.6 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 2.7 + <title>Life Calendar</title> 2.8 + <style type="text/css"> 2.9 + body { 2.10 + padding: 0; 2.11 + margin: 0; 2.12 + position: relative; 2.13 + font-family: sans-serif; 2.14 + background-size: cover; 2.15 + } 2.16 + 2.17 + a, a:visited { 2.18 + color: #70BCF9; 2.19 + } 2.20 + 2.21 + table { 2.22 + width: 100%; 2.23 + height: 100%; 2.24 + } 2.25 + 2.26 + table, td { 2.27 + border: 1px solid; 2.28 + border-collapse: collapse; 2.29 + padding: 0; 2.30 + margin: 0; 2.31 + } 2.32 + 2.33 + div#welcome { 2.34 + display: none; 2.35 + 2.36 + width: 50vw; 2.37 + margin: 8vh auto 0; 2.38 + } 2.39 + 2.40 + div#welcome h1 { 2.41 + text-align: center; 2.42 + text-transform: uppercase; 2.43 + font-family: serif; 2.44 + font-size: 4em; 2.45 + } 2.46 + div#welcome form { 2.47 + border: 1px solid #999999; 2.48 + padding: 10px 20px; 2.49 + } 2.50 + 2.51 + div#welcome .settings { 2.52 + margin-left: 5px; 2.53 + padding: 5px; 2.54 + } 2.55 + 2.56 + div#welcome div#footer { 2.57 + color: #a6a6a6; 2.58 + padding-top: 50px; 2.59 + text-align: center; 2.60 + } 2.61 + 2.62 + #settingsForm span.unhappyMessage { 2.63 + font-size: 0.8em; 2.64 + padding-left: 10px; 2.65 + } 2.66 + 2.67 + div#tooltip { 2.68 + background-color: #5070D0; 2.69 + padding: 0.5em; 2.70 + } 2.71 + 2.72 + div#tooltip h1 { 2.73 + color: #DFDFDF; 2.74 + font-size: 1.25em; 2.75 + margin: 1px; 2.76 + } 2.77 + 2.78 + div#tooltip h2 { 2.79 + color: #AAAAAA; 2.80 + font-size: 0.75em; 2.81 + font-weight: normal; 2.82 + margin: 1px; 2.83 + } 2.84 + 2.85 + td { 2.86 + border-color: #FFFFFF; 2.87 + } 2.88 + 2.89 + table { 2.90 + border-color: #FFFFFF; 2.91 + } 2.92 + 2.93 + tr.previous td { 2.94 + background-color: #BBBBBB; 2.95 + } 2.96 + 2.97 + tr.current td.partial { 2.98 + background-color: #CCDDCC; 2.99 + } 2.100 + 2.101 + tr.current td.today { 2.102 + background-color: #DDDDCC; 2.103 + } 2.104 + 2.105 + tr.current td.future { 2.106 + background-color: #DDCCCC; 2.107 + } 2.108 + 2.109 + tr.future { 2.110 + background-color: #DDDDDD; 2.111 + } 2.112 + 2.113 + </style> 2.114 + <script src="jquery-2.1.3.min.js"></script> 2.115 + <script src="happy.js"></script> 2.116 + <script src="tooltip.js"></script> 2.117 + 2.118 + <script type="text/javascript"> 2.119 + var nWeeks = 52; 2.120 + var nMsPerDay = 3600 * 24 * 1000; 2.121 + var nMsPerWeek = 7 * nMsPerDay; 2.122 + var QSParams; 2.123 + 2.124 + var birthday; 2.125 + var birthdayString; 2.126 + var birthdayDate; 2.127 + 2.128 + var lifespan; 2.129 + 2.130 + var age; 2.131 + var ageYears; 2.132 + var remainder; 2.133 + 2.134 + var previousYears; 2.135 + var futureYears; 2.136 + var remainderWeeks; 2.137 + var restOfYearWeeks; 2.138 + 2.139 + function validateBirthday(birthday) { 2.140 + var birthdayPattern = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; 2.141 + return birthdayPattern.test( $("#birthday").val()); 2.142 + } 2.143 + 2.144 + function validateLifespan(lifespan) { 2.145 + var lifespanPattern = /^[0-9]{1,3}$/; 2.146 + return lifespanPattern.test( $("#lifespan").val()); 2.147 + } 2.148 + 2.149 + // Read a page's GET URL variables and return them as an associative array. 2.150 + // From http://jquery-howto.blogspot.ca/2009/09/get-url-parameters-values-with-jquery.html 2.151 + function getQSParams() 2.152 + { 2.153 + var vars = [], hash; 2.154 + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); 2.155 + for(var i = 0; i < hashes.length; i++) 2.156 + { 2.157 + hash = hashes[i].split('='); 2.158 + vars.push(hash[0]); 2.159 + vars[hash[0]] = hash[1]; 2.160 + } 2.161 + return vars; 2.162 + } 2.163 + 2.164 + function isLeapYear(year) { 2.165 + var d = new Date(year, 1, 28); 2.166 + d.setDate(d.getDate() + 1); 2.167 + return d.getMonth() == 1; 2.168 + } 2.169 + 2.170 + function getAge(date) { 2.171 + var d = new Date(date), now = new Date(); 2.172 + var years = now.getFullYear() - d.getFullYear(); 2.173 + d.setFullYear(d.getFullYear() + years); 2.174 + if (d > now) { 2.175 + years--; 2.176 + d.setFullYear(d.getFullYear() - 1); 2.177 + } 2.178 + var days = (now.getTime() - d.getTime()) / nMsPerDay; 2.179 + return years + days / (isLeapYear(now.getFullYear()) ? 366 : 365); 2.180 + } 2.181 + 2.182 + function getCalendar(d, week, year) { 2.183 + var c = new Date(d); 2.184 + c.setFullYear(d.getFullYear() + year); 2.185 + c.setTime(c.getTime() + (week * nMsPerWeek)); 2.186 + return c; 2.187 + } 2.188 + 2.189 + function getWeekYearCal(e, week) { 2.190 + var yearParent = e.parentNode; 2.191 + var calParent = yearParent.parentNode; 2.192 + var year; 2.193 + for (year = 0; year < calParent.childNodes.length; year++) { 2.194 + if (calParent.childNodes[year] === yearParent) { 2.195 + break; 2.196 + } 2.197 + } 2.198 + var cal = getCalendar(birthdayDate, week, year); 2.199 + return { 2.200 + week: week, 2.201 + year: year, 2.202 + cal: cal, 2.203 + } 2.204 + } 2.205 + 2.206 + function loop(x, f) { 2.207 + for (var i = 0; i < x; f(i++)); 2.208 + } 2.209 + 2.210 + function mapLoop(x, f) { 2.211 + var ret = []; 2.212 + for (var i = 0; i < x; i++) { 2.213 + ret.push(f(i)); 2.214 + } 2.215 + return ret; 2.216 + } 2.217 + 2.218 + function cycleColors(e) { 2.219 + var colors = [ 2.220 + undefined, 2.221 + "black", 2.222 + "red", 2.223 + "green", 2.224 + "blue", 2.225 + "white", 2.226 + ]; 2.227 + 2.228 + var i = 0; 2.229 + for (; i < colors.length, colors[i] != e._fill_color; i++); 2.230 + e._fill_color = colors[(i + 1) % colors.length]; 2.231 + if (e._fill_color != undefined) { 2.232 + e.setAttribute("style", "background-color: " + e._fill_color); 2.233 + } else { 2.234 + e.setAttribute("style", undefined); 2.235 + } 2.236 + } 2.237 + 2.238 + function _createElement(tagName, className) { 2.239 + var e = document.createElement(tagName); 2.240 + if (className) { 2.241 + e.className = className; 2.242 + } 2.243 + return e; 2.244 + } 2.245 + 2.246 + function weekElem(className) { 2.247 + return _createElement("td", className); 2.248 + } 2.249 + 2.250 + function yearElem(className) { 2.251 + return _createElement("tr", className); 2.252 + } 2.253 + 2.254 + function yearWeekElems() { 2.255 + return mapLoop(nWeeks, function(i) { 2.256 + var e = weekElem(); 2.257 + addWeekMouseEvents(e, i); 2.258 + return e; 2.259 + }); 2.260 + } 2.261 + 2.262 + function addWeekMouseEvents(e, i) { 2.263 + e.addEventListener("mouseover", function(evt) { 2.264 + var wyc = getWeekYearCal(e, i); 2.265 + var calYear = wyc.cal.getFullYear(); 2.266 + var calMonth = wyc.cal.getMonth() + 1; 2.267 + var calDay = wyc.cal.getDate(); 2.268 + createTooltip(evt, calYear + '-' + calMonth + '-' + calDay, "(Week: " + wyc.week + ", Year: " + wyc.year + ")"); 2.269 + }); 2.270 + e.addEventListener("click", function() { 2.271 + cycleColors(e); 2.272 + }); 2.273 + } 2.274 + 2.275 + $( document ).ready(function() { 2.276 + $( "#settingsForm" ).isHappy({ 2.277 + fields: { 2.278 + '#birthday': { 2.279 + required: true, 2.280 + test: validateBirthday, 2.281 + message: 'Please enter your birthday (like YYYY-MM-DD).' 2.282 + }, 2.283 + '#lifespan': { 2.284 + required: true, 2.285 + test: validateLifespan, 2.286 + message: 'Please enter your expected lifespan (like 90)' 2.287 + } 2.288 + } 2.289 + }); 2.290 + 2.291 + QSParams = getQSParams(); 2.292 + 2.293 + if(!("birthday" in QSParams) | !("lifespan" in QSParams)) { 2.294 + // Show the form 2.295 + $("#welcome").css("display", "block"); 2.296 + 2.297 + // Give the first field focus 2.298 + $("#birthday").focus(); 2.299 + return; 2.300 + } 2.301 + 2.302 + $("body").append( '<table id="calendar"></table>' ); 2.303 + 2.304 + birthday = QSParams["birthday"]; 2.305 + birthdayString = birthday + "T00:00:00"; 2.306 + birthdayDate = new Date(birthdayString); 2.307 + 2.308 + lifespan = QSParams["lifespan"]; 2.309 + 2.310 + age = getAge(birthdayDate); 2.311 + ageYears = Math.floor(age); 2.312 + remainder = age - ageYears; 2.313 + 2.314 + previousYears = (ageYears).toFixed(0); 2.315 + 2.316 + if( lifespan > ageYears) { 2.317 + futureYears = lifespan - ageYears - 1; 2.318 + remainderWeeks = Math.floor(remainder * nWeeks); 2.319 + restOfYearWeeks = Math.ceil(nWeeks - remainderWeeks) - 1; 2.320 + } else { 2.321 + futureYears = 0; 2.322 + remainderWeeks = nWeeks; 2.323 + restOfYearWeeks = 0; 2.324 + } 2.325 + 2.326 + // Fill in lived years 2.327 + loop(previousYears, function() { 2.328 + var tr = yearElem("previous"); 2.329 + $(tr).append(yearWeekElems()); 2.330 + $("#calendar").append(tr); 2.331 + }) 2.332 + 2.333 + // Fill in the current birth-year (the number of weeks elapsed since the most recent birthday.) 2.334 + var current_tr = yearElem("current"); 2.335 + loop(remainderWeeks, function(i) { 2.336 + var e = weekElem("partial"); 2.337 + addWeekMouseEvents(e, i); 2.338 + $(current_tr).append(e); 2.339 + }) 2.340 + var e = weekElem("today"); 2.341 + addWeekMouseEvents(e, remainderWeeks); 2.342 + $(current_tr).append(e); 2.343 + loop(restOfYearWeeks, function(i) { 2.344 + var e = weekElem("future"); 2.345 + addWeekMouseEvents(e, remainderWeeks + 1 + i); 2.346 + $(current_tr).append(e); 2.347 + }) 2.348 + $("#calendar").append(current_tr); 2.349 + 2.350 + // Fill in future years 2.351 + loop(futureYears, function() { 2.352 + var tr = yearElem("future"); 2.353 + $(tr).append(yearWeekElems()); 2.354 + $("#calendar").append(tr); 2.355 + }) 2.356 + }); 2.357 + </script> 2.358 + </head> 2.359 + <body> 2.360 + <div id="welcome"> 2.361 + <h1>Life Calendar</h1> 2.362 + <p> 2.363 + A minimalist life calendar. Shows the number of weeks you've lived and the number of weeks you 2.364 + have left. 2.365 + </p> 2.366 + <form method="get" action="" id="settingsForm"> 2.367 + <p> 2.368 + <label for="birthday">Birthday: 2.369 + <input class="settings" id="birthday" name="birthday" placeholder="1985-01-01"/> 2.370 + </label> 2.371 + </p> 2.372 + <p> 2.373 + <label for="lifespan">Life expectancy: 2.374 + <input class="settings" id="lifespan" name="lifespan" placeholder="90"/> 2.375 + </label> 2.376 + </p> 2.377 + <p> 2.378 + <input type="submit" id="submit" value="Show calendar" /> 2.379 + </p> 2.380 + </form> 2.381 + <div id="footer"> 2.382 + <p> 2.383 + Based on <a href="http://waitbutwhy.com/2014/05/life-weeks.html">Your Life in Weeks</a> 2.384 + and <a href="http://count.life">count.life</a>. 2.385 + </p> 2.386 + </div> 2.387 + </div> 2.388 + </body> 2.389 +</html>
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/life_calendar/tooltip.js Fri Jun 17 22:24:17 2016 -0700 3.3 @@ -0,0 +1,58 @@ 3.4 +// Based on http://matthias-schuetz.js.org/tooltip.js/ 3.5 + 3.6 +var _tt_options = { 3.7 + tooltipId: "tooltip", 3.8 + offsetDefault: 15 3.9 +}; 3.10 + 3.11 +function getTooltipElm() { 3.12 + return document.querySelector("#" + _tt_options.tooltipId); 3.13 +} 3.14 + 3.15 +function adjustTooltip(evt, tooltipElm, title, text) { 3.16 + var offset = _tt_options.offsetDefault; 3.17 + var scrollY = window.scrollY || window.pageYOffset; 3.18 + var scrollX = window.scrollX || window.pageXOffset; 3.19 + var tooltipTop = evt.pageY + offset; 3.20 + var tooltipLeft = evt.pageX + offset; 3.21 + 3.22 + tooltipTop = (tooltipTop - scrollY + tooltipElm.offsetHeight + 20 >= window.innerHeight ? (tooltipTop - tooltipElm.offsetHeight - 20) : tooltipTop); 3.23 + tooltipLeft = (tooltipLeft - scrollX + tooltipElm.offsetWidth + 20 >= window.innerWidth ? (tooltipLeft - tooltipElm.offsetWidth - 20) : tooltipLeft); 3.24 + 3.25 + tooltipElm.style.top = tooltipTop + "px"; 3.26 + tooltipElm.style.left = tooltipLeft + "px"; 3.27 + 3.28 + setTooltipText(tooltipElm, title, text); 3.29 +} 3.30 + 3.31 +function removeTooltip() { 3.32 + document.querySelector("body").removeChild(getTooltipElm()); 3.33 +} 3.34 + 3.35 +function createTooltip(evt, title, text) { 3.36 + var tooltipElm = getTooltipElm(); 3.37 + 3.38 + if (!tooltipElm) { 3.39 + tooltipElm = document.createElement("div"); 3.40 + tooltipElm.appendChild(document.createElement("h1")); 3.41 + tooltipElm.appendChild(document.createElement("h2")); 3.42 + 3.43 + tooltipElm.style.position = "absolute"; 3.44 + tooltipElm.setAttribute("id", _tt_options.tooltipId); 3.45 + 3.46 + document.querySelector("body").appendChild(tooltipElm); 3.47 + } 3.48 + 3.49 + adjustTooltip(evt, tooltipElm, title, text); 3.50 +} 3.51 + 3.52 +function setTooltipText(tooltipElm, title, text) { 3.53 + var eTitle = tooltipElm.children[0]; 3.54 + var eText = tooltipElm.children[1]; 3.55 + if (eTitle && title) { 3.56 + eTitle.textContent = title; 3.57 + } 3.58 + if (eText && text) { 3.59 + eText.textContent = text; 3.60 + } 3.61 +}