inderpartap/Bunk-O-Meter

View on GitHub
static/timetable/js/colorchange.js

Summary

Maintainability
D
1 day
Test Coverage
timeTableStorage = [{
    "id": 0,
    "name": "Table Default",
    "data": [],
}];

activeTable = timeTableStorage[0];

var highlighted = {
    0: [],
    "highlight": function (id) {
        if (highlighted[id]) {
            highlighted[id].forEach(function (slot) {
                $("#timetable ." + slot).addClass("highlight");
                if ($(".quick-selection ." + slot + "-tile")) {
                    $(".quick-selection ." + slot + "-tile").addClass("highlight");
                }
            });
            $(".quick-selection button").not("[disabled]").each(function () {
                if ($("#timetable ." + this.classList[0].split('-')[0]).not(".highlight").length === 0) {
                    $(this).addClass("highlight");
                }
            });
        } else {
            highlighted[id] = [];
        }
    }
};

$(function () {
    // load localForage data
    (function () {
        localforage.getItem('timeTableStorage').then(function (storedValue) {
            timeTableStorage = storedValue || timeTableStorage;
            activeTable = timeTableStorage[0];

            fillPage(activeTable.data);
            updateTableDropdownLabel(activeTable.name);

            timeTableStorage.slice(1).forEach(function (table) {
                addTableDropdownButton(table.id, table.name);
            });
        });
    })();

    // addColorChangeEvents(); quick visualization disabled

    // Disable On Click Selection
    $("#toggleClickToSelect").click(function () {
        if ($(this).attr("data-state") === "enabled") {
            $(this).text("Enable Quick Visualization");
            $(this).attr("data-state", "disabled");
            $('.quick-selection *[class*="-tile"]').off();
            $("#timetable .TimetableContent").off();
            $('.quick-selection').hide(500);
        } else {
            $(this).text("Disable Quick Visualization");
            addColorChangeEvents();
            $(this).attr("data-state", "enabled");
            $('.quick-selection').show(500);
        }
    });

    // Toggle extra fields in slot selection area
    $("#slot-sel-area-toggle-fields-btn").click(function () {
        $("#slot-sel-area-toggle-fields").fadeToggle();
    });

    $('#slot-sel-area #addCourseBtn').click(function () {
        var courseCode = $('#inputCourseCode').val().trim();
        var courseTitle = $('#inputCourseTitle').val().trim();
        var faculty = $('#inputFaculty').val().trim();
        var slotString = $('#inputSlotString').val().toUpperCase().trim();
        var venue = $('#inputVenue').val().trim();
        var credits = $('#inputCourseCredits').val().trim();
        var isProject = $('#inputIsProject').val();
        $('#inputIsProject').val('false'); // Reset the value once we read it.

        if (slotString === '') {
            $('#inputSlotString').focus();
            return;
        }

        var slotArray = (function () {
            var arr = [];
            try {
                slotString.split(/\s*\+\s*/).forEach(function (el) {
                    if (el && $('.' + el)) {
                        arr.push(el);
                    }
                });
            } catch (error) {
                arr = [];
            }
            return arr;
        })();

        // Add new course to the end of the array.
        var courseId;
        if (activeTable.data.length === 0) {
            courseId = 0;
        } else {
            var lastAddedCourse = activeTable.data[activeTable.data.length - 1];
            courseId = lastAddedCourse[0] + 1;
        }

        // [0: courseId, 1: courseCode, 2:courseTitle, 3: faculty, 4: slotArray, 5: venue, 6: credits, 7: isProject]
        activeTable.data.push([courseId, courseCode, courseTitle, faculty, slotArray, venue, credits, isProject]);

        addCourseToTimetable(courseId, courseCode, venue, slotArray, isProject);
        insertCourseToCourseListTable(courseId, courseCode, courseTitle, faculty, slotArray, venue, credits, isProject);
        checkSlotClash();
        updateLocalForage();
    });

    // Load course again in the panel
    $("#courseListTable table").on("dblclick", "tr", function () {
        var slotString = $(this).find("td").not("[colspan]").eq(0).text();
        var courseCode = $(this).find("td").eq(1).text();
        var courseTitle = $(this).find("td").eq(2).text();
        var faculty = $(this).find("td").eq(3).text();
        var venue = $(this).find("td").eq(4).text();
        var credits = $(this).find("td").eq(5).text();

        $('#inputCourseCode').val(courseCode).trigger("change");
        $('#inputCourseTitle').val(courseTitle).trigger("change");
        $('#inputFaculty').val(faculty).trigger("change");
        $('#inputSlotString').val(slotString).trigger("change");
        $('#inputVenue').val(venue).trigger("change");
        $('#inputCourseCredits').val(credits).trigger("change");

        try {
            // Function may not work if autocomplete is not loaded
            addSlotButtons(courseCode);
        } catch (error) {

        }

        $(this).find(".close").click();

        // scroll back to panel
        $('html, body').animate({
            scrollTop: $("#slot-sel-area").offset().top
        });
    });

    // delete course from table
    $("#courseListTable table").on("click", ".close", removeCourse);

    // Reset current table not all tables
    $('#resetButton').click(function () {
        clearPage();
        activeTable.data = [];
        updateLocalForage();
        highlighted[activeTable.id] = [];
    });

    // Clear course from panel
    $("#clearCourseBtn").click(function () {
        $('#slot-sel-area input').val("");
        $('#insertCourseSelectionOptions').html("");
    });

    // switch table menu option on click
    $("#saved-tt-picker").on("click", "a", function () {
        var selectedTableId = Number($(this).data("table-id"));
        switchTable(selectedTableId);
    });

    // Remove table
    $("#saved-tt-picker").on("click", ".tt-picker-remove", function (e) {
        e.preventDefault();
        e.stopPropagation();
        var tableId = Number($(this).closest("a").data("table-id"));
        $(this).closest("li").remove();
        removeTable(tableId);
    });

    // Rename table button
    $("#saved-tt-picker").on("click", ".tt-picker-edit-button", function (e) {
        e.preventDefault();
        e.stopPropagation();
        var tableName = $(this).closest("a").children(".tt-table-name").text().trim();
        $(this).closest("a").siblings("input").val(tableName);
        $(this).closest("a").siblings("input").show().focus();
        $(this).closest("a").siblings(".tt-picker-edit-ok").show();
        $(this).closest("a").hide();
    });

    // Rename input focus out
    $("#saved-tt-picker").on("focusout", ".tt-picker-edit-input", function (e) {
        e.preventDefault();
        e.stopPropagation();
        var tableId = Number($(this).siblings("a").data("table-id"));
        var tableName = $(this).val();
        $(this).siblings("a").children(".tt-table-name").text(tableName);
        $(this).siblings("a").show();
        $(this).hide();
        $(this).siblings(".tt-picker-edit-ok").hide();
        renameTable(tableId, tableName);
    });

    $("#saved-tt-picker").on("keydown", ".tt-picker-edit-input", function (e) {
        // enter or Esc key
        if (e.which === 13 || e.which === 27) {
            $(this).blur();
        }
    });

    // Add table button
    $("#saved-tt-picker-add").click(function () {
        var newTableId = timeTableStorage[timeTableStorage.length - 1].id + 1;
        var newTableName = "Table " + newTableId;
        timeTableStorage.push({
            "id": newTableId,
            "name": newTableName,
            "data": []
        });
        addTableDropdownButton(newTableId, newTableName);
        switchTable(newTableId);
        updateLocalForage();
        highlighted[newTableId] = [];
    });

    // load course data with autocomplete
    loadCourseData();
});

function addColorChangeEvents() {
    $("#timetable .TimetableContent:not([disabled])").click(function () {
        if ((!$(this).hasClass("clash")) && $(this).children("div").length === 0) {
            $(this).toggleClass("highlight");
            if (!$(this).hasClass("highlight")) {
                $(".quick-selection ." + this.classList[1] + "-tile").removeClass("highlight");
                // remove slots from highlighted
                var index = highlighted[activeTable.id].indexOf(this.classList[2]);
                highlighted[activeTable.id].splice(index, 1);
                return;
            } else {
                // add slots to highlighted
                if (this.classList.length === 3) {
                    // some course may only have lab slot
                    highlighted[activeTable.id].push(this.classList[1]);
                } else {
                    highlighted[activeTable.id].push(this.classList[2]);
                }

            }
            if ($("#timetable ." + this.classList[1]).not(".highlight").length === 0) {
                $(".quick-selection ." + this.classList[1] + "-tile").addClass("highlight");
            }
        }
    });
    $('.quick-selection *[class*="-tile"]').click(function () {
        if ((!$("#timetable ." + this.classList[0].split('-')[0]).hasClass("clash")) && ($("#timetable ." + this.classList[0].split('-')[0]).children("div").length === 0)) {
            if ($(this).hasClass("highlight")) {
                $("#timetable ." + this.classList[0].split('-')[0]).removeClass("highlight");
                // remove slots from highlighted
                var index = highlighted[activeTable.id].indexOf(this.classList[0].split('-')[0]);
                highlighted[activeTable.id].splice(index, 1);
            } else {
                $("#timetable ." + this.classList[0].split('-')[0]).addClass("highlight");
                // add slots to highlighted
                highlighted[activeTable.id].push(this.classList[0].split('-')[0]);
            }
            $(this).toggleClass("highlight");
        }
    });
}

function addCourseToTimetable(courseId, courseCode, venue, slotArray, isProject) {
    slotArray.forEach(function (slot) {
        var $divElement = $('<div data-course="' + 'course' + courseId + '" data-is-project="' + isProject + '">' + courseCode + '-' + venue + '</div>');
        $('#timetable tr .' + slot).addClass('highlight').append($divElement);
        if ($(".quick-selection ." + slot + "-tile")) {
            $(".quick-selection ." + slot + "-tile").addClass("highlight");
        }
    });
}

function insertCourseToCourseListTable(courseId, courseCode, courseTile, faculty, slotArray, venue, credits, isProject) {
    var $trElement = $('<tr data-course="' + 'course' + courseId + '" data-is-project="' + isProject + '">' +
        '<td>' + slotArray.join('+') + '</td>' +
        '<td>' + courseCode + '</td>' +
        '<td>' + courseTile + '</td>' +
        '<td>' + faculty + '</td>' +
        '<td>' + venue + '</td>' +
        '<td>' + credits + '</td>' +
        '<td><span class="close">&times;</span></td>' +
        '</tr>');

    $('#courseListTable tbody #totalCreditsTr').before($trElement);

    // update credits
    updateCredits();
}

function updateCredits() {
    var totalCredits = 0;
    $('#courseListTable tbody tr').not('#totalCreditsTr').each(function () {
        // 6th column is credits column
        totalCredits += Number($(this).children('td').eq(5).text());
    });
    $('#totalCredits').text(totalCredits);
}

function checkSlotClash() {
    // Remove danger class (shows clashing) form tr in course list table.
    $('#courseListTable tbody tr').removeClass('danger');
    $('#timetable tr .hidden').removeClass('hidden');

    // Check clash from timetable in each slot area
    $('#timetable tr .highlight').each(function () {
        let $highlightedCell = $(this);
        let $highlightedCellDivs = $(this).children('div[data-course]');

        if ($highlightedCellDivs.length > 1) {
            let isClashing = true;

            // Check if there are two dissimilar courses or if there is a J
            // component course and a sibling in this cell.
            if ($highlightedCellDivs.length === 2) {
                let $firstCellDiv = $highlightedCellDivs.eq(0),
                    $secondCellDiv = $highlightedCellDivs.eq(1);

                let isFirstCourseJComp = $firstCellDiv.data('is-project'),
                    isSecondCourseJComp = $secondCellDiv.data('is-project');
                
                if (isFirstCourseJComp && isSecondCourseJComp) {} // Two J components in the same slot is a clash.
                else if (isFirstCourseJComp || isSecondCourseJComp) { // Otherwise, check for similarity.
                    let firstCourseId = +$firstCellDiv.data('course').split(/(\d+)/)[1];
                    let secondCourseId = +$secondCellDiv.data('course').split(/(\d+)/)[1];

                    let firstCourseIdx = getIndexByCourseId(firstCourseId);
                    let secondCourseIdx = getIndexByCourseId(secondCourseId);

                    let firstCourse = activeTable.data[firstCourseIdx];
                    let secondCourse = activeTable.data[secondCourseIdx];

                    // Check to see if two courses are similar.
                    if (firstCourse[1] === secondCourse[1] && // Course Code
                        firstCourse[2] === secondCourse[2]       // Course Title
                    ) {
                        // console.log('Hidden setting');
                        $highlightedCell.removeClass('clash');
                        let $projectDiv = isFirstCourseJComp ? $firstCellDiv : $secondCellDiv;
                        $projectDiv.addClass('hidden');
                        isClashing = false;
                    }
                }
            }

            if (isClashing) {
                // clash
                // remove, add clash in timetable
                $(this).addClass('clash');
                // show clash in course list table
                $(this).children('div[data-course]').each(function () {
                    var dataCourse = $(this).attr("data-course");
                    // Add danger class to tr of clashing course list table.
                    $('#courseListTable tbody tr[data-course="' + dataCourse + '"]').addClass('danger');
                });
            }
        } else if ($highlightedCellDivs.length === 1) {
            // no clash
            $(this).removeClass('clash').addClass("highlight");
        } else {
            // no course present
            $(this).removeClass("clash highlight");
            $(".quick-selection ." + this.classList[1] + "-tile").removeClass("highlight");
        }
    });
}

function removeCourse(e) {
    e.stopPropagation();
    var dataCourse = $(this).closest('tr').attr('data-course');

    $('#timetable tr td div[data-course="' + dataCourse + '"]').remove();
    $('#courseListTable tbody tr[data-course="' + dataCourse + '"]').remove();

    checkSlotClash();
    updateCredits();

    var courseId = Number(dataCourse.split(/(\d+)/)[1]);
    for (var i = 0; i < activeTable.data.length; ++i) {
        if (activeTable.data[i][0] == courseId) {
            activeTable.data.splice(i, 1);
            break;
        }
    }

    updateLocalForage();
}

function getIndexByCourseId(courseId) {
    return activeTable.data.findIndex(function(elem) {
        return elem[0] === courseId;
    });
}

// Simply clears all the added content in the page but doesn't reset the data in memory.
function clearPage() {
    $('#timetable .TimetableContent').removeClass("highlight clash");
    $('.quick-selection *[class*="-tile"]').removeClass("highlight");
    $('#slot-sel-area input').val("");
    if ($('#timetable tr div[data-course]')) {
        $('#timetable tr div[data-course]').remove();
    }
    if ($('#courseListTable tbody tr[data-course]')) {
        $('#courseListTable tbody tr[data-course]').remove();
    }
    $('#insertCourseSelectionOptions').html("");
    updateCredits();
}

// Fills the page with the courses (array) passed.
function fillPage(data) {
    $.each(data, function (index, arr) {
        var courseId = arr[0];
        var courseCode = arr[1];
        var courseTile = arr[2];
        var faculty = arr[3];
        var slotArray = arr[4];
        var venue = arr[5];
        var credits = arr[6];
        var isProject = arr[7];

        // index is basically courseId
        addCourseToTimetable(courseId, courseCode, venue, slotArray, isProject);
        insertCourseToCourseListTable(courseId, courseCode, courseTile, faculty, slotArray, venue, credits, isProject);
    });
    checkSlotClash();
}

function switchTable(tableId) {
    clearPage();

    for (var i = 0; i < timeTableStorage.length; i++) {
        if (tableId == timeTableStorage[i].id) {
            activeTable = timeTableStorage[i];
            updateTableDropdownLabel(activeTable.name);
            fillPage(activeTable.data);
            // return;
            break;
        }
    }
    highlighted.highlight(tableId);
}

function updateTableDropdownLabel(tableName) {
    $("#saved-tt-picker-label .btn-text").text(tableName);
}

function removeTable(tableId) {
    for (var i = 0; i < timeTableStorage.length; ++i) {
        if (timeTableStorage[i].id == tableId) {
            // If it is the active table, change activeTable.
            if (activeTable.id == tableId) {
                switchTable(timeTableStorage[i - 1].id);
            }
            timeTableStorage.splice(i, 1);
            updateLocalForage();
            return;
        }
    }
}

function renameTable(tableId, tableName) {
    for (var i = 0; i < timeTableStorage.length; i++) {
        if (timeTableStorage[i].id == tableId) {
            timeTableStorage[i].name = tableName;
            updateLocalForage();
            // If active table is renamed
            if (activeTable.id == tableId) {
                updateTableDropdownLabel(tableName);
            }
            return;
        }
    }
}

function addTableDropdownButton(tableId, tableName) {
    $("#saved-tt-picker").append(
        '<li>' +
        '<input class="tt-picker-edit-input" style="display:none;" type="text">' +
        '<button title="Ok" type="button" class="close tt-picker-edit-ok" style="display:none;" aria-label="Ok"><span aria-hidden="true">&#10004;</span></button>' +
        '<a href="JavaScript:void(0);" data-table-id="' + tableId + '">' +
        '<span class="tt-table-name">' + tableName + '</span>' +
        '<button title="Remove" type="button" class="close tt-picker-remove" aria-label="Remove"><span aria-hidden="true">&#10008;</span></button>' +
        '<button title="Rename" type="button" class="close tt-picker-edit-button" aria-label="Rename"><span aria-hidden="true">&#9998;</span></button>' +
        '</a>' +
        '</li>'
    );
}

// save data through localForage
function updateLocalForage() {
    localforage.setItem('timeTableStorage', timeTableStorage);
    return;
}

// load course data with autocomplete
function loadCourseData() {
    var isDataAvailable = true;

    function createScript() {
        var scriptTag = document.createElement("script");
        scriptTag.async = false;
        document.body.appendChild(scriptTag);
        return scriptTag;
    }

    function loadAssets(callback) {
        var scripts = [
            "https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/jquery.easy-autocomplete.min.js",
            "static/timetable/js/autocomplete_course.js"
        ];

        var stylesheets = [
            "https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/easy-autocomplete.min.css"
        ];

        stylesheets.forEach(function (link) {
            $('<link rel="stylesheet" href="' + link + '">').appendTo("head");
        });

        scripts.slice(0, -1).forEach(function (src) {
            var scriptTag = createScript();
            scriptTag.src = src;
        });

        var scriptTag = createScript();
        scriptTag.addEventListener("load", callback);
        scriptTag.src = scripts[scripts.length - 1];
    }

    if (isDataAvailable) {
        var prepareGetRequest = function (url) {
            return $.get(url, {
                dataType: "json"
            });
        };

        var $getRequests = [
            "static/timetable/data/all_data.json",
            "static/timetable/data/unique_courses.json"
        ].map(function (url) {
            return prepareGetRequest(url);
        });

        $.when.apply($, $getRequests)
            .done(function (args1, args2) {
                all_data = args1[0];
                unique_courses = args2[0];

                loadAssets(function loadAssetsHandler() {
                    initAutocomplete(all_data, unique_courses);
                });
            })
            .fail(function () {
                console.error("Failed to load course data.");
            });
    }
}