Skip to content

Instantly share code, notes, and snippets.

@mockee
Last active November 19, 2020 07:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mockee/b22bbc87ea85a6bd987a to your computer and use it in GitHub Desktop.
Save mockee/b22bbc87ea85a6bd987a to your computer and use it in GitHub Desktop.
Facebook Puzzle — Canlendar
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Puzzle — Lay Out Day</title>
<style>
html,body,div,h3,p { margin: 0; padding: 0; }
body {
font: 12px 'lucida grande', tahoma, verdana, arial, sans-serif;
}
.calendar {
position: relative;
margin: 20px;
}
.times-range {
position: absolute;
top: -8px;
left: 0;
width: 76px;
color: #313131;
text-align: right;
vertical-align: top;
}
.times-range .time {
display: block;
color: #959595;
height: 30px;
font-size: 10px;
}
.times-range .time b {
font-size: 12px;
color: #585858;
margin-right: 5px;
}
.timeline {
position: relative;
width: 620px;
height: 720px;
padding: 0 10px;
margin-left: 85px;
margin-top: 8px;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #ececec;
border-left: 1px solid #d9d9d9;
}
.event-item {
position: absolute;
left: 0;
top: 0;
cursor: pointer;
border-width: 1px 1px 1px 4px;
border-style: solid;
border-color: #d5d5d5 #d5d5d5 #d5d5d5 #5173aa;
background: #fff;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
word-wrap: break-word;
}
.event-item:hover {
background: #eff2f7;
}
.event-item .title {
font-weight: bold;
font-size: 13px;
color: #5173aa;
margin: 8px 0 0 8px;
}
.event-item .location {
font-size: 11px;
color: #8b8b8b;
margin: 0 0 0 8px;
}
.event-item .title,
.event-item .location {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<script>
// Creating `time` element for IE8.
;(function(doc) { doc.createElement('time') }(document))
</script>
</head>
<body>
<div class="calendar">
<div class="times-range"></div>
<div class="timeline"></div>
</div>
</body>
<script id="tmpl-time-range" type="text/template">
<time class="time">{%= time %}</time>
</script>
<script id="tmpl-event-item" type="text/template">
<div class="event-item"
style="width:{%= width %};height:{%= height %};top:{%= top %};left:{%= left %};">
<h3 class="title">Event Item</h3>
<p class="location">Location</p>
</div>
</script>
<script>
;(function(exports, doc) {
"use strict";
// `isArray`, `every`, `reduce` `filter` for IE8
var isObject = function(obj) {
return obj === Object(obj)
}
, isArray = Array.isArray || function(obj) {
return ({}).toString.call(obj) === '[object Array]'
}
, every = function(obj, iterator, context) {
if (Array.prototype.every) {
return obj.every(iterator, context)
}
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && !iterator.call(context, obj[i], i, obj)) {
return false
}
}
return true
}
, reduce = function(obj, iterator) {
var hasInitValue = false
, value
if (arguments.length > 2) {
hasInitValue = true
value = arguments[2]
}
if (Array.prototype.reduce) {
return hasInitValue
? obj.reduce(iterator, value)
: obj.reduce(iterator)
}
for (var i = 0, l = obj.length; i < l; i++) {
if (hasInitValue) {
value = iterator(value, obj[i], i, obj)
} else {
value = obj[i]
hasInitValue = true
}
}
return value
}
, filter = function(obj, iterator, context) {
var results = []
if (Array.prototype.filter) {
return obj.filter(iterator, context)
}
for (var i = 0, l = obj.length; i < l; i++) {
if (iterator.call(context, obj[i], i, obj)) {
results.push(obj[i])
}
}
return results
}
, hasOwnProp = function(obj, key) {
return ({}).hasOwnProperty.call(obj, key)
}
, isEmpty = function(obj) {
if (obj === null) { return true }
if (isArray(obj)) { return obj.length === 0 }
for (var key in obj) {
if (hasOwnProp(obj, key)) { return false }
}
return true
}
// Micro-template
, cache = {}
, template = function(str, data) {
var fn = cache[str] = cache[str] ||
new Function("obj", "var p=[];with(obj){p.push('" +
str.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^%]*%})/g,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/{%=(.+?)%}/g, "',$1,'")
.split("{%").join("');")
.split("%}").join("p.push('")
+ "');}return p.join('');")
return fn(data)
}
function _renderTimesRange() {
var hourTime = 12
, initTime = 9
, time, clock, minute
, timesStrings = ''
, isAfterMoon
, isEvenItem
, elTimesRange = doc.querySelector('.times-range')
, tmplRange = doc.getElementById('tmpl-time-range').innerHTML
for (var i = 0; i <= 24; i++) {
isEvenItem = i % 2 === 0
clock = initTime += (i !== 0 && isEvenItem ? 1 : 0)
isAfterMoon = clock > hourTime
clock = isAfterMoon ? clock - hourTime : clock
minute = ':' + (isEvenItem ? '00' : '30')
// hh:mm AM / PM
time = isEvenItem ? '<b>' + clock + minute + '</b>'
+ (isAfterMoon ? 'PM' : 'AM') : clock + minute
timesStrings += template(tmplRange, { time: time })
}
elTimesRange.innerHTML = timesStrings
}
function renderEventItems(data) {
var tmplEventItem = doc.getElementById('tmpl-event-item').innerHTML
, elTimeline = doc.querySelector('.timeline')
, timelineString = ''
, eventGroup
for (var i = 0, l = data.length; i < l; i++) {
eventGroup = data[i]
eventGroup.maxOrder =
eventGroup.sort(function(a, b) {
return b.order - a.order
})[0].order
for (var j = 0, k = eventGroup.length; j < k; j++) {
var item = eventGroup[j]
, itemWidth = 599 / eventGroup.maxOrder
, itemTop = item.start < 0 ? 0 : item.start
, itemHeight = (item.end > 720 ? 720 : item.end) - itemTop
timelineString += template(tmplEventItem, {
width: itemWidth + 'px'
, height: itemHeight + 'px'
, left: 10 + (item.order - 1) * itemWidth + 'px'
, top: itemTop + 'px'
})
}
}
elTimeline.innerHTML = timelineString
}
function dataTransform(events) {
var initSingleEvt = function(evt) {
evt.order = 1
evt.point = evt.end
return evt
}
if (events.length === 1) {
return [[initSingleEvt(events[0])]]
}
var result = []
, lastEvent = {}
, eventsIdx = events.length - 1
events = events.sort(function(a, b) {
return a.start - b.start
})
reduce(events, function(prev, curr, idx) {
if (!isArray(prev)) {
prev = [initSingleEvt(prev)]
}
lastEvent = prev[prev.length - 1]
if (curr.start < lastEvent.point) {
curr.point = Math.min(lastEvent.point, curr.end)
curr.order = prev.sort(function(a, b){
return b.order - a.order
})[0].order + 1
prev.push(curr)
if (idx === eventsIdx) {
result.push(prev)
}
return prev
} else if (curr.start < lastEvent.end) {
var subEvents = filter(prev, function(v) {
return v.end < curr.start
})
curr.point = lastEvent.end
curr.order = subEvents.length ?
subEvents.sort(function(a, b) {
return b.order - a.order
})[0].order : 1
prev.push(curr)
if (idx === eventsIdx) {
result.push(prev)
}
return prev
} else {
var subEvents = filter(prev, function(v) {
return v.end > curr.start
})
if (subEvents.length) {
var near = subEvents[0]
curr.point = Math.min(near.point, curr.end)
curr.order = near.order + 1
prev.push(curr)
if (idx === eventsIdx) {
result.push(prev)
}
return prev
} else {
if (prev) { result.push(prev) }
if (idx === eventsIdx) {
result.push([initSingleEvt(curr)])
}
return curr
}
}
})
return result
}
/**
* @public Lay out events day
* @param {array} events list or {object} single event
*
* @note
* layoutday([
* { start: 30, end: 150 }
* , { start: 540, end:600 }
* ])
* layoutday({ start: 30, end: 150 })
*
*/
function layOutDay(events) {
if (isEmpty(events)) {
throw Error('No event here.')
}
if (isObject(events) && !isArray(events)) {
events = [events]
}
var isTimesValid = every(events, function(item) {
return item.end > item.start
})
if (!isTimesValid) {
throw Error('End value must greater than start.')
}
var data = dataTransform(events)
renderEventItems(data)
}
_renderTimesRange()
exports.layOutDay = layOutDay
}(window, document))
// Init default events
;(function(win) {
var defaultEvents = [
{ start: 30, end: 150 }
, { start: 540, end: 600 }
, { start: 560, end: 620 }
, { start: 610, end: 670 }
]
win.layOutDay(defaultEvents)
}(window))
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment