js

// --- Configuration ----------------------------------------------------

// Time range to display for timetable
const startTime = "7:00";
const endTime = "19:00";

// Labels for days of the week. The timetable will have one
// column for every entry here.
const headersArray = [
    "MON",
    "TUE",
    "WED",
    "THU",
    "FRI"
]

// Two-dimensional array representing events.
// Array format: [ event name, day of week, start time, end time, color ]
const eventArray = [
    [ "Example event", 1, "11:00", "14:00", "#3366AA"],
    [ "Thursday 1", 3, "9:30", "12:00", "#668811"],
    [ "Thursday 2", 3, "12:00", "13:30", "#668811"]
];

// Labels for each 30-minute block. By default, blocks are labeled in
// one hour intervals.
const timesArray = [
    "0:00", "",
    "1:00",  "",
    "2:00",  "",
    "3:00",  "",
    "4:00",  "",
    "5:00",  "",
    "6:00",  "",
    "7:00",  "",
    "8:00",  "",
    "9:00",  "",
    "10:00",  "",
    "11:00",  "",
    "12:00",  "",
    "13:00",  "",
    "14:00",  "",
    "15:00",  "",
    "16:00",  "",
    "17:00",  "",
    "18:00",  "",
    "19:00",  "",
    "20:00",  "",
    "21:00",  "",
    "22:00",  "",
    "23:00",  "",
    "00:00",  ""
];




// ---- Code ---------------------------------------------------------------

// A block's position in a day's column is used to locate said block. 
// So if the schedule starts at 7:00, then 7:00-7:30 will be block 0 
// and 7:30-8:00 will be block 1.

// Two-dimensional array representing X and Y 
// coordinates of blocks on the timetable.
var blockArray = [];

createTable();
addEvents();

// Create the empty timetable with columns for each day (and one
// column for the time).
function createTable() {

    var table = document.createElement("table");
    table.id = "timetable";
    document.getElementById("schedule-wrapper").appendChild(table);

    // Table header
    var header = document.createElement("tr");
    header.className = "schedule-header";
    addToTable(header);

    // Time column header
    var timehead = document.createElement("th");
    timehead.className = "schedule-corner";
    header.appendChild(timehead);

    // Add labels for days of the week
    for (let i = 0; i < headersArray.length; i++) {
        var heading = document.createElement("th");
        heading.className = "schedule-heading";
        heading.appendChild(document.createTextNode(headersArray[i]));
        header.appendChild(heading);
    }

    var tableBlocks = timeToBlockCount(startTime, endTime);

    // Cells
    for (let i = 0; i < tableBlocks; i++) {
        var row = makeRow(i);
        addToTable(row);
        blockArray[i] = [];

        // Time column
        var cell = document.createElement("td");
        cell.className = "schedule-time";
        // Create time label
        var label = timesArray[ Number(startTime.split(":")[0]) * 2 + i ];
        cell.appendChild(document.createTextNode(label));
        row.appendChild(cell);

        for (let j = 0; j < headersArray.length; j++) {
            var cell = makeCell(i, j);
            row.appendChild(cell);
            blockArray[i][j] = cell;
        }
    }
}

// Add events from the global events array to the timetable.
function addEvents() {

    // Iterate through each event and add to table
    for (let event of eventArray) {

        // Retrieve starting cell
        var cell = blockArray[ timeToBlockPosition(event[2]) ][ event[1] ];

        // Apply styling
        expandBlock(cell, timeToBlockCount(event[2], event[3]));
        cell.style.backgroundColor = event[4];
        cell.appendChild(document.createTextNode(event[0]));

    }
}



// ---- Helper Functions -------------------------------------------------

// Returns the position of a block found at a given time (in)
function timeToBlockPosition(time) {
    var parts = time.split(":");
    return(parts[0] - startTime.split(":")[0]) * 2 + (parts[1] / 30);
}

// Returns how many 30-minute blocks the given hour range needs
function timeToBlockCount(start, end) {
    var startParts = start.split(":");
    var endParts = end.split(":");
    var minutesSum;
    if (startParts[1] == endParts[1]) 
        minutesSum = 0;
     else if (startParts[1] > endParts[1]) 
        minutesSum = -1;
     else 
        minutesSum = 1;
    
    return(((endParts[0] - startParts[0]) * 2) + minutesSum);
}

function addToTable(element) {
    document.getElementById("timetable").appendChild(element);
}

function makeCell(x, y) {
    var element = document.createElement("td");
    element.id = x + "," + y;
    element.className = "schedule-cell";
    return element;
}

function makeRow(id) {
    var element = document.createElement("tr");
    element.id = id;
    element.className = "schedule-row";
    return element;
}

function deleteCell(cell) {
    cell.parentNode.removeChild(cell);
}

function getBlockCol(cell) {
    return Number(cell.id.split(",")[1]);
}

function getBlockPos(cell) {
    return Number(cell.id.split(",")[0]);
}

// Merges the given cell with X amount of cells downward
function expandBlock(cell, amount) {
    cell.setAttribute("rowspan", amount);

    var pos = getBlockPos(cell);
    var col = getBlockCol(cell);
    for (let i = 1; i < amount; i++) {
        deleteCell(blockArray[pos + i][col]);
    }
}