// (function() {
var CONFIG;
var SECTION = 'a';
var NOW = new Date();
var WEEK = 1;
var DEPENDENCIES = {
home: ['home.php', 'updates.xml', 'calendar.xml', 'lectures.xml', 'labs.xml'],
calendar: ['calendar.php', 'updates.xml', 'calendar.xml', 'lectures.xml', 'labs.xml'],
lectures: ['calendar.xml', 'lectures.xml', 'labs.xml', 'video.html'],
labs: ['calendar.xml', 'lectures.xml', 'labs.xml'],
homework: ['homeworks.xml', 'assignments.xml'],
dropbox: ['dropbox.php', 'calendar.xml', 'assignments.xml', 'lectures.xml', 'labs.xml', 'homeworks.xml', 'tags.txt', 'dropbox_feedback.php'],
syllabus: ['syllabus.php'],
software: ['software.php'],
staff: ['staff.php'],
forums: [],
submitted: []
};
var DATA = {};
var PAGES = {};
var SLIDERS = {
primary: ['home', 'calendar', 'lectures', 'labs', 'homework', 'syllabus', 'software', 'staff'],
dropbox: ['dropbox', 'submitted']
};
var PAGE;
var PATH;
$.ajaxSetup({
'cache': false,
'ifModified': true
});
// After the DOM and config file have finished loading, load dependencies for the specified
// internal page and then launch the skeleton framework.
$.when(
$.Deferred(function() { $(this.resolve); }).then(function() { console.log('DOM loaded'); }),
$.get(BASENAME + '/course.json').then(function(data) {
CONFIG = data;
setpath();
console.log('config loaded');
}).then(createSectionSelector).then(function() {
return load(PAGE);
})
).then(launch);
// Functions to dynamically generate page content using the appropriate XML data.
var initialize = {
// Lectures: Get and inject a list of all lectures, including minilabs, video play buttons,
// and lecture example files.
'lectures': function() {
var $page = page('lectures').append(header('Lectures'));
$(DATA['lectures.xml']).find('lectures > lecture').each(function(i, lecture) {
var $lecture = $(lecture);
$page.append(
// Generate a standard article for this lecture, plus minilab, video button,
// and lecture example files.
generate('lecture', $('<article>'), $lecture, {
modifiers: [
// Add minilab.
function(data, $calendar_lecture, $container) {
return $container.append(generate('minilab', $('<aside>'), $lecture.children('minilab')));
},
// Add video play button if recording available.
function(data, $calendar_lecture, $container) {
var $video = $('<a>');
if ($calendar_lecture.attr('recorded') == 'true') {
$video.attr('href', "javascript:playlecture('" + data.id + "','" + data.date + "','" + SECTION + "')");
$video.addClass('videolink');
$video.append($('<span>').addClass('play').text('▶'));
$video.append($('<span>').addClass('textonly').text('Play lecture'));
$video = $('<div>').append($video);
}
if ($video.children().length) {
$container.append($video);
}
return $container;
},
// Add lecture example files, if any.
function(data, $calendar_lecture, $container) {
var $files = $('<dl>').addClass('fileset');
$calendar_lecture.children('fileset').each(function(i, fileset) {
var $fileset = $(fileset);
var label = $fileset.attr('label');
$files.append($('<dt>').text(label));
var $ul = $('<ul>');
$fileset.children('file').each(function(j, file) {
var $file = $(file);
var name = $file.attr('name');
var label = $file.attr('label');
var $a = $('<a>');
if (/^(\w+:)?\/\/?/.test(name)) { // protocol:// absolute url
$a.attr('href', name);
} else {
$a.attr('hhref', BASENAME + '/lectures/' + data.id + '/files/' + SECTION + '/' + name);
}
$a.text(name.split('/').pop());
var $li = $('<li>').append($a);
if (label) {
$li.append(' ' + label);
}
if ($file.attr('supporting') == 'true') {
$li.addClass('supporing');
}
$ul.append($li);
});
$files.append($('<dd>').append($ul));
});
if ($files.children().length) {
$container.append($files);
}
return $container;
}
]
})
);
});
},
// Labs: Get and inject a list of labs, skipping all labs past the first marked "tentative",
// if any.
'labs': function() {
var $labs = $(DATA['labs.xml']).find('labs lab');
var $page = page('labs').append(header('Labs'));
if ($labs.length) {
$labs.each(function(i, lab) {
var $article = generate('lab', $('<article>'), $(lab));
$page.append($article);
// If this lab is tentative, return false to "break" out of this $.each() iteration,
// so that labs are displayed only up until the first tentative lab.
if ($article.hasClass('tentative')) {
return false;
}
});
} else {
$page.append(noneyet('labs'));
}
},
// Homework: Get and inject a list of all assignments whose open dates have passed (and
// should thus be shown), or a "none yet" message if none.
'homework': function() {
var shown = [];
var $homeworks = $(DATA['homeworks.xml']).find('homeworks homework');
$.each($homeworks.get().reverse(), function(i, homework) {
shown.push(generate('homework', $('<article>'), $(homework)));
});
var $page = page('homework').append(header('Homework'));
if (shown.length) {
$page.append(shown);
} else {
$page.append(noneyet('homework assignments'));
}
},
// Dropbox:
'dropbox': function() {
$('#submitting').hide();
},
// Home: Scavenge most recent updates and this week's calendar row from the calendar page,
// ensuring that page gets loaded first.
'home': function() {
load('calendar').then(function() {
create('calendar');
// Copy this week's calendar row.
page('home').find('table.calendar').empty().append(page('calendar').find('table.calendar tr:nth-child(' + WEEK + ')').clone());
// Extract first 6 updates, then prune if they're too long.
var $home_updates = page('home').find('.updates dl').empty();
var charcount = 0;
page('calendar').find('.updates dl > *').slice(0, 6).each(function(i, elem) {
if (elem.nodeName == 'DT') {
$home_updates.append($(elem).clone());
} else {
var text = elem.textContent.replace(/\s+/g, ' ');
charcount += text.length;
if (charcount > 500) {
// Undo addition of the dt.
$home_updates.children().last().remove();
} else {
$home_updates.append($(elem).clone());
}
}
});
});
},
// Calendar: Generate calendar table (with lectures, labs, and minlabs) and list of
// recent updates.
'calendar': function() {
// Generate calendar table.
var $table = page('calendar').find('table.calendar').empty();
var $calendar = $(DATA['calendar.xml']);
$calendar.find('week').each(function(i, week) {
var $tr = $('<tr>');
$(week).find('section[id=' + SECTION + '] > *').each(function(j, calendar_entry) {
var $calendar_entry = $(calendar_entry);
// console.log($calendar_entry[0]);
var $td = $('<td>').addClass(['lecture-a', 'lab', 'lecture-b'][j]);
var date = $calendar_entry.attr('date');
var id = $calendar_entry.attr('id');
if ($calendar_entry.attr('blank') != 'true') {
if (id) {
var type = calendar_entry.nodeName;
var $type_data = $(DATA[type + 's.xml']);
var $type_entry = $type_data.find(type + '[id="' + id + '"]');
$td = generate(type, $td, $type_entry, { '$calendar_entry': $calendar_entry });
if (type == 'lecture') {
var $minilab = $type_entry.children('minilab');
if ($minilab.length) {
$td.append(generate('minilab', $('<aside>'), $minilab));
} else {
$td.addClass('noaside');
}
}
} else { // !id - generate from title/description in calendar, not lecture/lab
$td = generate('calendar', $td, $calendar_entry, { '$calendar_entry': $calendar_entry });
$td.addClass('noaside');
}
} else {
// console.log('(blank calendar entry; no type entry)');
}
$td.html('<div class="wrapper">' + $td.html() + '</div>');
$table.append($tr.append($td));
// console.log($td[0]);
});
});
// Populate Recent Updates section.
var $dl = page('calendar').find('.updates dl').empty();
$(DATA['updates.xml']).find('item').each(function(i, item) {
var $item = $(item);
var date = new Date($item.children('pubDate').text());
var month = zeropad(date.getMonth() + 1);
var day = zeropad(date.getDate());
var $dt = $('<dt>').text(month + '.' + day);
var title = $item.children('title').text();
var description = $item.children('description').text();
var link = $item.children('link').text();
if (link) {
title = '<a href="' + link + '">' + title + '</a>';
}
var content;
if (title.replace(/<\/?[^>]+>/g, '').replace(/[^\w]/g, '') != description.replace(/<\/?[^>]+>/g, '').replace(/[^\w]/g, '')) {
content = '<p>' + title + '</p>' +
'<aside>' + description + '</aside>';
} else {
content = title;
}
var $dd = $('<dd>').html(content);
$dl.append($dt).append($dd);
});
// Set height of calendar wrapper; will be overridden by .single when applied.
page('calendar').find('calendar.wrapper').css({
'height': $table.outerHeight()
});
// Pushstate-ify "show all" link.
page('calendar').find('header nav.show a.showall').click(function(event) {
event.preventDefault();
event.stopPropagation();
navigate('calendar', this.href);
});
// // TODO: pushstate-ify all links
// $('.page.calendar a').each(function(i, elem) {
// pushstateify(elem);
// });
}
}
// Functions to configure a page for viewing based on the given path. Called immediately
// before the page is navigated to using that path.
var show = {
// Calendar:
'calendar': function(path) {
console.log(path);
var showall = page('calendar').find('header nav.show a.showall')[0];
switch (path[1]) {
case 'full':
showall.innerHTML = 'show only this week';
showall.href = BASENAME + '/calendar/';
// show full calendar
page('calendar').find('nav.week').hide();
page('calendar').find('.calendar.wrapper').removeClass('single');
page('calendar').find('table.calendar').css('transform', 'none');
break;
case 'week':
showall.innerHTML = 'show entire quarter';
showall.href = BASENAME + '/calendar/full/';
showweek('calendar', parseInt(path[2]));
break;
default:
showall.innerHTML = 'show entire quarter';
showall.href = BASENAME + '/calendar/full/';
showweek('calendar', WEEK);
break;
}
}
}
// Extract the id, title, and description from the given $entry, and use them to generate
// and return a "standard article" for the data type. Most types return a header with h3 & h4,
// followed by a paragraph description, with some variations.
function generate(type, $container, $entry, options) {
var id = $entry.attr('id');
var title = $entry.children('title').text();
var description = $entry.children('description').text();
if (type == 'minilab') {
if (id && title) {
var content = '<strong>' + title + ':</strong>';
if (description) content += '<span class="space"> </span>' + description;
if (id) {
var num = id.replace('minilab-', '');
$container.append($('<a>').attr('href', BASENAME + '/minilabs/' + num + '/').html(content));
} else {
$container.append(content);
}
}
} else {
var subtitle, after = '', tentative = false;
var $date_entry;
if (type == 'homework') {
var $assignments = $(DATA['assignments.xml']);
var $turnin = $assignments.find('homework[id="' + id + '"] turnin');
var opens = new Date($turnin.attr('opens'));
var closes = new Date($turnin.attr('closes'));
var shown = (opens <= NOW);
if (!shown) {
console.log('skipping assignment ', $entry[0], ' with open date ', opens, ' > ', NOW);
return $();
}
var open = (NOW <= closes);
var closemonth = zeropad(closes.getMonth() + 1);
var closeday = zeropad(closes.getDate());
subtitle = closemonth + '.' + closeday;
after = '<p class="duedate">Due: <time>' + datestring(closes) + '</time></p>'
$date_entry = $turnin;
} else {
var $calendar_entry;
if (options && options.$calendar_entry) {
$calendar_entry = options.$calendar_entry;
} else {
$calendar_entry = $(DATA['calendar.xml']).find('section[id="' + SECTION + '"] ' + type + '[id="' + id + '"]');
}
var date = $calendar_entry.attr('date');
tentative = $calendar_entry.attr('tentative') == 'true';
subtitle = date.substring(5, 7) + '.' + date.substring(8);
$date_entry = $calendar_entry;
}
var content = '<header>' +
(type == 'calendar' ? '<h3>' : '<h3 id="' + id + '">') + title + '</h3>' +
'<h4>' + subtitle + '</h4>' +
'</header>' +
'<p>' + description + '</p>' + after;
if (tentative) {
$container.addClass('tentative');
$container.html(content);
} else {
var num = type == 'lecture' ? id : id.replace(/(homework|lab)-/, '');
$container.html('<a href="' + BASENAME + '/' + type + (type != 'homework' ? 's' : '') + '/' + num + '/">' + content + '</a>');
}
if (options && options.modifiers) {
$.each(options.modifiers, function(i, mod) {
$container = mod({
id: id, title: title, description: description
}, $date_entry, $container);
});
}
}
return $container;
}
function header(pagename) {
return $('<header>').append($('<h2>').text(pagename));
}
// Load the specified page's dependencies (using cache unless 'force' is truthy),
// and populate and initialize the page.
function load(pagename, force) {
console.log((force ? 'FORCE ' : '') + "loading dependencies for page '" + pagename + "'");
var $loading = $.Deferred();
var remaining = DEPENDENCIES[pagename].length;
var modified = [];
if (remaining) {
// Counting semaphore funtion, called after each dependency is loaded (or failed).
// If any dependencies were updated, marks their dependent pages as dirty.
// TODO: array of Promises instead?
function finish() {
if (--remaining == 0) {
// if any dependencies were updated, mark other depdendent pages as dirty
$.each(modified, function(i, file) {
$.each(DEPENDENCIES, function(pg, deplist) {
if (deplist.indexOf(file) != -1) {
console.log(pg + ' is now dirty');
if (PAGES[pg]) PAGES[pg] = 'dirty';
}
});
});
$loading.resolve();
}
}
// load all dependencies for this page
$.each(DEPENDENCIES[pagename], function(i, file) {
var parts = file.split('.');
var pagename = parts[0];
var ext = parts[1];
var path = '';
if (ext == 'txt') {
dir = '.';
path = file;
} else if (ext == 'html' || ext == 'php') {
if (['home', 'calendar', 'syllabus', 'software', 'staff', 'dropbox'].indexOf(pagename) != -1) {
dir = '';
path = (pagename == 'home' ? '' : '/' + pagename) + '/?fragment';
} else {
dir = 'inc';
path = file;
}
} else {
dir = ext;
path = file;
}
if (ext != 'html' && ext != 'php' || ['home', 'calendar', 'syllabus', 'software', 'staff'].indexOf(pagename) == -1) {
dir = '/' + dir;
path = '/' + path;
}
var requestfile = BASENAME + dir + path;
if (DATA[file] && !force) {
console.log('dependency ' + requestfile + ' already loaded');
finish();
} else {
console.log('loading dependency ' + requestfile);
$.get(requestfile).done(function(data, msg, jqXHR) {
if (jqXHR.status == 304) {
console.log('dependency ' + file + ' unchanged');
} else {
DATA[file] = data;
modified.push(file);
console.log('dependency ' + file + ' loaded');
}
}).always(finish);
}
});
} else {
$loading.resolve();
}
return $loading;
}
// Return a string representation of the given date, in the format: Sunday, January 1, 2013, 1:00 PM
function datestring(date) {
var weekday = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
var month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][date.getMonth()];
var day = date.getDate();
var year = date.getFullYear();
var hours = date.getHours();
var minutes = zeropad(date.getMinutes());
var am_pm = (hours >= 0 && hours < 12) ? 'AM' : 'PM';
hours %= 12;
return weekday + ', ' + month + ' ' + day + ', ' + year + ', ' + hours + ':' + minutes + ' ' + am_pm;
}
// Pad the given num with a 0, if necessary, to make it 2 digits wide.
function zeropad(num) {
return (num < 10 ? '0' : '') + num;
}
// Return a paragraph with the message "No <things> posted yet." Parameter 'things' should be plural.
function noneyet(things) {
return $('<p>').text('No ' + things + ' posted yet.');
}
// Create and inject the section selector.
function createSectionSelector() {
// var sections = ['a'];
// $('#course_id').append($('<ul>').attr('id', 'section'));
// $.each(sections, function(i, section) {
// var $a = $('<a>').text(section.toUpperCase());
// // $a.click(sectionChange);
// var $li = $('<li>').append($a);
// // if (SECTION == section) {
// $li.addClass('selected');
// // }
// $('#section').append($li);
// });
// var offset = sections.indexOf(SECTION);
// $('#section').children().first().css('margin-top', (-offset * .85) + 'em');
$('#course_id').append($('<ul>').attr('id', 'section').append($('<li>').text('a')));
}
// Display the specified week in the single-height calendar on the given page ('home' or 'calendar').
function showweek(pagename, week) {
page('calendar').find('.calendar.wrapper').addClass('single').children('table.calendar').css({
'-webkit-transform': 'translate(0, -' + (week-1) * 8.5 + 'em)',
'transform': 'translate(0, -' + (week-1) * 8.5 + 'em)'
});
if (pagename == 'calendar') {
page(pagename).find('nav.week span.number').text('Week ' + week);
arrows(week);
}
}
// Relink the up/down navigation arrows on the calendar page, centered on the specified week.
function arrows(week) {
page('calendar').find('nav.week').show();
page('calendar').find('header nav.week a.week').each(function(i, link) {
var chg = i == 0 ? -1 : 1;
var newweek = week + chg;
if (newweek > 0 && newweek < page('calendar').find('table.calendar tr').length + 1) {
link.href = BASENAME + '/calendar/week/' + newweek;
$(link).unbind('click').hover(
function(event) {
$(link).children('canvas.hover').show();
},
function(event) {
$(link).children('canvas.hover').hide();
}
).click(function(event) {
event.preventDefault();
event.stopPropagation();
history.replaceState(null, null, link.href);
showweek('calendar', newweek);
}).children('canvas.disabled').hide();
} else {
link.href = 'javascript:';
$(link).unbind('click').unbind('hover').children('canvas.disabled').show();
}
});
}
// Parse and return the portions of the URL relevant to internal navigation.
function splitpath(path) {
console.log("splitpath: ", path);
path = path.replace(BASENAME + '/', '');
var parts = path.split('#');
path = parts[0];
var hash = '#' + parts[1];
path = path.replace(/\/$/, ''); // remove trailing slash to avoid empty entry
path = path.split('/');
path.push(hash);
console.log("Path: ", path);
return path;
}
// Set the PAGE and PATH based on the current URL.
function setpath() {
PATH = splitpath(location.pathname + location.hash);
PAGE = PATH[0] || 'home';
console.log('PATH:', PATH, 'PAGE:', PAGE);
}
// Navigate to the specified internal page, optionally pushState-ing the given URL. Calculate
// the page height necessary and handle animation if transitioning between internal pages.
function navigate(newpage, href) {
console.log("navigating to page '" + newpage + "' with url '" + href + "'");
var oldpage = PAGE;
var transition = oldpage != newpage;
if (transition) $(document.body).removeClass(oldpage);
$(document.body).addClass(newpage);
if (show[newpage]) {
var path = splitpath(href ? href.replace(/^\w+:\/\/[^\/]+/, '') : location.pathname + location.hash);
show[newpage](path);
}
// Update URL if new one provided.
if (href) {
history.pushState(null, null, href);
setpath();
}
if (!transition) {
// Not coming from a previous page (e.g., this is initial pageload). Just
// position everything properly.
var height = pageheight(newpage);
var slider = SLIDERS.dropbox.indexOf(newpage) != -1 ? 'dropbox' : 'primary';
var index = slider == 'primary' ? SLIDERS.primary.indexOf(newpage) : 0;
console.log(height, slider, index);
// Translate slider so that appropriate page is in view (unanimated)
if (slider == 'primary') {
$('.slider.primary').css({
'height': height,
'transform': 'translate(-' + (index * 55) + 'em, 0)'
});
$('.slider.dropbox').css('visibility', 'hidden');
} else if (slider == 'dropbox') {
$('.slider.dropbox').css({
'height': height,
'transform': 'translate(0, -10em)'
});
$('.slider.primary').css('visibility', 'hidden');
}
$('#main').height(height);
// Position masthead at x-position according to page's index in slider, and y-position
// according to which slider it's on.
$('header.masthead').css('transform', 'translate(-' + (index * .25) + 'em, ' + (slider == 'dropbox' ? '-.35em' : '0') + ')');
} else {
// Navigating to new internal page. Prefetch, stage, and finally transition
// to new slider / page.
var newheight = pageheight(newpage);
var oldheight = pageheight(oldpage == newpage ? null : oldpage);
var oldslider = SLIDERS.dropbox.indexOf(oldpage) != -1 ? 'dropbox' : 'primary';
var newslider = SLIDERS.dropbox.indexOf(newpage) != -1 ? 'dropbox' : 'primary';
if (newslider == 'dropbox') {
var newindex = SLIDERS.dropbox.indexOf(newpage);
if (oldslider == 'primary') {
// Going from primary → dropbox.
var displacement = (parseFloat(oldheight) + parseFloat($('.slider.dropbox').css('margin-top'))) + 'px';
console.log(displacement, oldheight);
// Stage dropbox slider: reposition slider to new page's index WITHOUT animating.
animate('off', ['.slider.dropbox', '.slider.primary']);
$('.slider.dropbox').css({
'height': newheight,
'transform': 'translate(-' + (newindex * 55) + 'em, 0)'
});
$('.slider.primary').css({
'height': oldheight,
'transform': 'translate(-' + (oldindex * 55) + 'em, -' + displacement + ')'
});
// Transition to new slider / page (animated).
setTimeout(function() {
// Turn on animation.
animate('on', ['.slider.dropbox', '.slider.primary', 'header.masthead']);
// Show dropbox slider.
$('.slider.dropbox').css('visibility', 'visible');
// Move both sliders up.
var oldindex = SLIDERS.primary.indexOf(oldpage);
$('.slider.dropbox').css('transform', 'translate(-' + (newindex * 55) + 'em, -' + displacement + ')');
$('.slider.primary').css('transform', 'translate(-' + (oldindex * 55) + 'em, -' + displacement + ')');
// Hide primary slider when it's finished sliding up.
setTimeout(function() {
$('.slider.primary').css('visibility', 'hidden');
}, 500); // FIXME: make constant
// Slide masthead up at same x-position.
$('header.masthead').css('transform', 'translate(-' + (oldindex * .25) + 'em, -.35em)');
}, 1);
} else {
// Going from dropbox → dropbox.
// Slide dropbox slider to new page's index.
$('.slider.dropbox').css({
'height': newheight,
'transform': 'translate(-' + (newindex * 55) + 'em, -' + newheight + ')'
});
// Slide masthead to new x-position.
$('header.masthead').css('transform', 'translate(-' + (newindex * .25) + 'em, -.35em)');
}
} else if (newslider == 'primary') {
var newindex = SLIDERS.primary.indexOf(newpage);
if (oldslider == 'dropbox') {
// Going from dropbox → primary.
var displacement = (parseFloat(newheight) + parseFloat($('.slider.dropbox').css('margin-top'))) + 'px';
var oldindex = SLIDERS.dropbox.indexOf(oldpage);
// Stage primary slider: reposition slider to new page's index WITHOUT animating.
animate('off', ['.slider.primary', '.slider.dropbox']);
$('.slider.primary').css({
'height': newheight,
'transform': 'translate(-' + (newindex * 55) + 'em, -' + displacement + ')'
});
$('.slider.dropbox').css('transform', 'translate(-' + (oldindex * 55) + 'em, -' + displacement + ')');
// Transition to new slider / page (animated).
setTimeout(function() {
// Turn on animation.
animate('on', ['.slider.primary', '.slider.dropbox', 'header.masthead']);
// Show primary slider.
$('.slider.primary').css('visibility', 'visible');
// Move both sliders down.
$('.slider.primary').css('transform', 'translate(-' + (newindex * 55) + 'em, 0)');
$('.slider.dropbox').css('transform', 'translate(-' + (oldindex * 55) + 'em, 0)');
// Hide dropbox slider when it's finished sliding down.
setTimeout(function() {
$('.slider.dropbox').css('visibility', 'hidden');
}, 500); // FIXME: make constant
// Slide masthead down and to new x-position.
$('header.masthead').css('transform', 'translate(-' + (newindex * .25) + 'em, 0)');
}, 1);
} else {
// Going from primary → primary.
// Slide dropbox slider to new page's index.
$('.slider.primary').css({
'height': newheight,
'transform': 'translate(-' + (newindex * 55) + 'em, 0)'
});
// Slide masthead to new x-position.
$('header.masthead').css('transform', 'translate(-' + (newindex * .25) + 'em, 0)');
}
}
$('#main').height(newheight);
}
}
// Calculate and return the specified page's occupied height (the height #main should be set
// to in order to exactly encompass the page's contents).
function pageheight(pagename) {
if (!pagename) return '0px';
var $page = page(pagename);
var height = 0;
// if (pagename == 'home') {
// height = '45em';
// } else if (pagename == 'calendar') {
if (pagename == 'calendar') {
page('calendar').children().each(function(i, elem) {
var $elem = $(elem);
var childheight = 0;
if (i == 1) {
var $cal = $elem.children('table.calendar');
if ($elem.hasClass('single')) {
childheight = $cal.find('tr:first-child').outerHeight()
// + parseFloat($cal.css('margin-top')) + parseFloat($cal.css('margin-bottom'))
+ parseFloat($cal.css('padding-top')) + parseFloat($cal.css('padding-bottom'));
// + parseFloat($cal.css('border-top')) + parseFloat($cal.css('border-bottom'))
} else {
childheight = $cal.outerHeight();
}
childheight += parseFloat($elem.css('margin-top')) + parseFloat($elem.css('margin-bottom'));
} else {
childheight = $elem.outerHeight(true);
}
height += childheight;
});
height += parseFloat(page('calendar').css('margin-top'));
height += 'px';
} else if (pagename == 'dropbox') {
height = $page.outerHeight() + parseFloat($page.css('margin-top')) + 'px';
} else {
height = $page.outerHeight(true) + 'px';
}
return height;
}
// Enable animated transitions on the masthead, #main, #content, body, and calendar.
function animate(state, elements) {
var easeOutSine = 'cubic-bezier(0.39, 0.575, 0.565, 1)';
var timing = '.5s';
var transitions = {
'header.masthead': 'transform ' + timing + ' linear',
'#main': 'height ' + timing + ' ' + easeOutSine,
'.slider.primary': 'transform ' + timing + ' ' + easeOutSine,
'.slider.dropbox': 'transform ' + timing + ' ' + easeOutSine,
'body': 'transform ' + timing + ' ' + easeOutSine + ', opacity ' + timing + ' ' + easeOutSine,
'.page.calendar .calendar.wrapper': 'height .25s ' + easeOutSine,
'.page.calendar table.calendar': 'transform .2s ' + easeOutSine
}
state = state || 'on';
if (!elements) elements = Object.keys(transitions);
else if (!$.isArray(elements)) elements = [ elements ];
console.log('turning animation ' + state + ' for:', elements);
if (state == 'on') {
$.each(elements, function(i, elem) {
$(elem).css({
'transition': transitions[elem],
'-webkit-transition': transitions[elem].replace('transform', '-webkit-transform')
});
});
} else {
$.each(elements, function(i, elem) {
$(elem).css({
'transition': 'none',
'-webkit-transition': 'none'
})
});
}
}
// Fetch and return the page of the given name.
function page(name) {
return $('.page.' + name);
}
// (Re-)create the specified page.
function create(pagename) {
if (DATA[pagename + '.php']) {
page(pagename).html(DATA[pagename + '.php']);
}
if (initialize[pagename]) initialize[pagename]();
PAGES[pagename] = 'initialized';
}
// Set up the page skeleton, attaching pushState-ified click and prefetching mouseover handlers
// to navigation links, showing the current PAGE, and enabling animated transitions.
function launch() {
console.log("page load complete");
var i = 0;
$.each(DEPENDENCIES, function(pagename, deps) {
// Create page and add to appropriate slider.
var $pg = $('<div>').addClass('page ' + pagename);
if (pagename == 'dropbox') {
$pg.appendTo('.slider.dropbox');
} else {
$pg.appendTo('.slider.primary');
$pg.css('left', (i * 55) + 'em');
// $pg.data('nav-index', i);
i++;
}
});
$('<div>').addClass('page submitted').css({
'left': '55em'
}).appendTo('.slider.dropbox');
// Handle forums click differently.
$('#nav_forums a').click(function(event) {
event.preventDefault();
event.stopPropagation();
$(document.body).css({
'transform': 'translate(-200%)',
'opacity': 0
});
window.location.href = event.target.href;
});
var $loading = deferred().resolve();
// PushState-ify main navigation links (excl. dropbox and forums), and make mouse hovers
// prefetch content.
$('.branding a, .masthead nav ul li:not(#nav_dropbox):not(#nav_forums) a').each(function(i, elem) {
$(elem).click(navclick).mouseover(primaryover);
});
$('#nav_dropbox a').click(navclick).mouseover(dropboxover);
// Go to the page specified in the URL, and make everything visible.
create(PAGE);
navigate(PAGE);
$(document.body).css('visibility', 'visible');
// Enable animated transitions.
setTimeout(animate, 10);
// ===== LOCAL FUNCTIONS =====
// Create and return a new jQuery Deferred object, stamped with a unique id for concurrency
// testing.
function deferred() {
var $def = $.Deferred();
Object.defineProperty($def, '__id', {
value: new Date().getTime()
});
return $def;
}
// Navigate to the clicked page.
function navclick(event) {
event.preventDefault();
event.stopPropagation();
var pagename = event.delegateTarget.parentNode.id ? event.delegateTarget.parentNode.id.replace('nav_', '') : 'home';
var url = event.delegateTarget.href;
console.log(event.delegateTarget, pagename, url, $loading.state());
$loading.then(function() {
navigate(pagename, url);
});
}
// Preload the target page of the hovered-over navigation link, as well as all intervening
// pages between that and the current page in the horizontal page "slider".
function primaryover(event) {
// if ($loading.state() == 'pending') {
// $loading.resolve();
// }
$loading = $.Deferred();
// var loadid = $loading.__id;
var pagename = event.delegateTarget.parentNode.id ? event.delegateTarget.parentNode.id.replace('nav_', '') : 'home';
var to_load = [];
var dest = SLIDERS.primary.indexOf(pagename);
if (PAGE == 'dropbox' || PAGE == 'submitted') {
to_load.push(SLIDERS.primary.indexOf(pagename));
console.log('on dropbox, going to load', to_load, pagename);
} else {
var cur = SLIDERS.primary.indexOf(PAGE);
var m = Math.min(dest, cur);
var n = Math.max(dest, cur);
for (var i = m; i <= n; i++) {
var pg = SLIDERS.primary[i];
if (!PAGES[pg] || i == dest) to_load.push(i);
}
console.log(dest, m, n, to_load);
}
if (to_load.length) {
$.each(to_load, function(i, index) {
var pg = SLIDERS.primary[index];
console.log('load:', pg);
// if (!PAGES[pg]) {
load(pg, index == dest).then(function() {
if (!PAGES[pg] || (index == dest && PAGES[pg] == 'dirty')) {
create(pg);
}
// to_load.pop();
// console.log('remaining to load: ', to_load);
if (i == to_load.length - 1) {
// if ($loading.__id == loadid) {
$loading.resolve();
// console.log('resolving fresh loader');
// } else {
// console.log('stale loader; won\'t resolve');
// }
}
});
// } else {
// console.log('load: PAGES[pg] exists:', PAGES[pg]);
// }
});
} else {
$loading.resolve();
}
}
// Prelaod the dropbox page.
function dropboxover(event) {
$loading = $.Deferred();
var pagename = 'dropbox';
// if (!PAGES[pagename]) {
// console.log('dropbox: before load');
load(pagename, true).then(function() {
console.log('dropbox: load done');
if (!PAGES[pagename] || PAGES[pagename] == 'dirty') {
console.log('dropbox: going to create');
create(pagename);
console.log('dropbox: created');
}
console.log('dropbox: going to resolve');
$loading.resolve();
});
// }
}
}
// })();