Skip to content

Instantly share code, notes, and snippets.

@cry
Created May 4, 2018 06:01
Show Gist options
  • Save cry/d93aecbe8e984bf75b914d54dbc199bb to your computer and use it in GitHub Desktop.
Save cry/d93aecbe8e984bf75b914d54dbc199bb to your computer and use it in GitHub Desktop.
//Bojangles client side program
//Initializing canvas and setting canvas variables
var canvas = new fabric.Canvas('timetable', {selection: false});
//Non stock fabric functionality, added in my customized version
canvas.pixelRatio = window.devicePixelRatio;
var width = 0;
var rows = 9;
var cols = 5;
var height = 0;
var widthScale = 0;
var heightScale = 0;
var yOffset = 16*canvas.pixelRatio;
var xOffset = 16*canvas.pixelRatio;
var startHour = 9;
var tempBlocks = [];
var generatedTimetables = [];
var courses = [];
var depObjects = [];
var currDisplayed = 0;
var textScale = 1;
canvas.renderOnAddRemove = false;
canvas.allowTouchScrolling = true;
canvas.backgroundColor = '#fff';
canvas.imageSmoothingEnabled = false;
var sortOrder = new Sortable(document.getElementById('sortable'));
$.getJSON('/data/bojangles/course_names.json', function(jsonIn){
$('#course-inpt').typeahead({source: jsonIn});
});
//Fetches a course from the server and adds it to the course array
var addSubject = function(courseName){
if(courses.length >= 6) return;
var contains = false;
var courseCode = courseName.split(' - ')[0];
for(var i = 0; !contains && i < courses.length; ++i){
if(courses[i].course_code === courseCode) contains = true;
}
if(!contains){
$.getJSON('/data/bojangles/'+ courseCode.toUpperCase() +
'.json', function(jsonIn){
courses.push(jsonIn);
$('#chosen').append('<li class="list-group-item class-inpt">' +
jsonIn.course_code + ' - ' + jsonIn.course_name +
'<button class="btn btn-danger btn-xs pull-right rm-course">'+
'Remove</btn></li>');
if(jsonIn.warnings){
throwWarning(jsonIn.warnings, false);
}
});
}
};
//Fills the canvas with the timetable labels and grid
var createTableGrid = function(){
var firstHour= 9;
var lastHour = 17;
var days = 4;
for(var i = 0; i < courses.length; ++i){
if(courses[i].earliest_time < firstHour){
earliest = courses[i].earliest_time;
}
if(courses[i].latest_time > lastHour){
lastHour = courses[i].latest_time;
}
if(courses[i].latest_day > days){
days = courses[i].latest_day;
}
}
days++;
canvas.clear();
width = $('#inpt-area').width();
cols = days;
rows = lastHour-firstHour+1;
//Setting minimum size so still usable on mobile phones
if(canvas.pixelRatio > 1){
var physicalWidth = width;
var physicalHeight = Math.ceil(physicalWidth*rows/(2*cols));
width *= canvas.pixelRatio;
height = Math.ceil(width*rows/(2*cols));
canvas.setHeight(height);
canvas.setWidth(width);
//Yes they are all required to avoid the canvas visually overflowing
$('#timetable').width(physicalWidth).height(physicalHeight);
$('.canvas-container').width(physicalWidth).height(physicalHeight);
$('.upper-canvas').width(physicalWidth).height(physicalHeight);
if(physicalWidth > 400){
textScale = canvas.pixelRatio;
} else{
textScale = 1.25;
}
} else if(width < 400){
canvas.setZoom(width/400);
var act_width = width;
width = 400;
height = Math.ceil(width*rows/(2*cols));
canvas.setHeight(Math.ceil(act_width*rows/(2*cols)));
canvas.setWidth(act_width);
} else{
canvas.setZoom(1);
height = Math.ceil(width*rows/(2*cols));
canvas.setHeight(height);
canvas.setWidth(width);
}
widthScale = Math.floor((width-yOffset)/cols);
heightScale = Math.ceil(widthScale/2);
var day = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Saturday', 'Sunday'];
//Plot days
for(var i = 0; i < days; ++i){
canvas.add(new fabric.Text(day[i],{
fontSize: 12*textScale,
fontFamily: 'arial, sans-serif',
fontWeight: 'bold',
textAlign: 'center',
originX: 'center',
originY: 'top',
selectable: false,
left: i*widthScale + widthScale/2 + xOffset,
top: 0
}));
}
//Plot times
for(var i = firstHour; i <= lastHour; ++i){
canvas.add(new fabric.Text(i+':00',{
fontSize: 12*textScale,
fontFamily: 'arial, sans-serif',
fontWeight: 'bold',
textAlign: 'center',
originX: 'center',
originY: 'top',
selectable: false,
left: 0,
top: (i-firstHour)*heightScale + heightScale/2 + yOffset,
angle: 270
}));
}
//Plot gridlines
//Lines point array [x1,y1,x2,y2]
for (var i = 0; i < cols+1; ++i) {
var line = new fabric.Line([xOffset+i*widthScale, 0,
xOffset+i*widthScale, height], {stroke: '#ccc', selectable: false});
canvas.add(line);
line.sendToBack();
}
for (var i = 0; i < rows+1; ++i) {
var line = new fabric.Line([0, yOffset+i*heightScale, width,
yOffset+i*heightScale], {stroke: '#ccc', selectable: false});
canvas.add(line);
line.sendToBack();
}
};
//Creates an individual class tile
var createClass = function(courseCode, classType, altPos, length, color,
dependants, thisPos){
var text = new fabric.Text(courseCode+'\n'+classType, {
fontSize: 14*textScale,
fontFamily: 'arial, sans-serif',
fontWeight: 'bold',
textAlign: 'center',
originX: 'center',
originY: 'center',
selectable: false
});
var container = new fabric.Rect({
width: widthScale,
height: heightScale*length,
fill: color,
originX: 'center',
originY: 'center',
stroke: 'black',
strokeWidth: 1,
selectable: false,
opacity: 0.8
});
var subject = new fabric.Group([container, text], {
hasControls: false
});
subject.courseCode = courseCode;
subject.classType = classType;
subject.thisPos = thisPos;
subject.classLen = length;
subject.altPos = altPos;
subject.dependants = dependants;
subject.currPos = [0, 0];
subject.prevPos = subject.currPos;
canvas.add(subject);
return subject;
};
//Adds all the classes contained in the courses array
//to the timetable
var addAllClasses = function(allowFull){
var colorOptions = ['#6B64DE', '#4BBD59', '#FF635B', '#FFD95B',
'#8D48DB', '#33E8B2'];
var classObjs = [];
for(var i = 0; i < courses.length; ++i){
classObjs = classObjs.concat(
addClasses(courses[i], colorOptions[i], allowFull));
}
var classes = [];
var seen = [];
for(var i = classObjs.length-1; i >= 0; --i){
if(seen.indexOf(classObjs[i].dependants[0]) < 0){
classes.push(classObjs[i].dependants[0]);
seen.push(classObjs[i].dependants[0]);
}
}
depObjects = classes;
return classes;
};
//Converts the date and time of subject to a coordinate for the
//grid displayed.
var dayTimeToGrid = function(day, time){
return [day, time-startHour];
};
//Adds class blocks to canvas
var addClasses = function(course, color, allowFull){
var uniClasses = Object.keys(course.class_types);
var addedClasses = [];
//Iterate over class types in this course
for(var i = 0; i < uniClasses.length; ++i){
var altPos = []; //Array of alternate positions for class and dependants
var classGroup = course.class_types[uniClasses[i]];
//Create class objects for the dependant classes
//Assumes that the first class represents the structure
//Of all class groups
var depClass = [];
for(var j = classGroup[0].length-1; j >= 0; --j){
var len = classGroup[0][j].end_time - classGroup[0][j].start_time;
var position = depClass.length;
var toAdd = createClass(course.course_code, uniClasses[i], altPos,
len, color, depClass,position);
depClass.push(toAdd);
addedClasses.push(toAdd);
}
//Iterate over the possible class times
for(var j = classGroup.length-1; j >= 0; --j){
depClass = [];
//Iterate over the dependant classes
for(var k = classGroup[j].length-1; k >= 0; --k){
if(!allowFull && classGroup[j][k].status === 'Full') continue;
//grid position of class
var positionLength = dayTimeToGrid(classGroup[j][k].class_day,
classGroup[j][k].start_time);
//Push length of class
positionLength.push(classGroup[j][k].end_time -
classGroup[j][k].start_time);
depClass.push(positionLength);
}
//Exclude empty lines(usually from closed classes)
//Exclude duplicate entries (improves performance of generator)
if(depClass.length > 0){
var valid = true;
for(var k = altPos.length-1; k >= 0; --k){
for(var l = depClass.length-1; l >= 0; --l){
if(depClass[l][0] === altPos[k][l][0] &&
depClass[l][1] === altPos[k][l][1] &&
depClass[l][2] === altPos[k][l][2]){
valid = false;
break;
}
}
}
if(valid && depClass.length > 0){
altPos.push(depClass);
}
}
}
//Randomize array
for (var j = altPos.length-1; j > 0; --j) {
var k = Math.floor(Math.random()*(j+1));
var temp = altPos[j];
altPos[j] = altPos[k];
altPos[k] = temp;
}
}
return addedClasses;
};
//Conditional snap to grid for objects
canvas.on('mouse:up', function(options){
canvas.allowTouchScrolling = true;
var group = options.target;
for(var i = tempBlocks.length-1; i >= 0; --i){
canvas.remove(tempBlocks[i]);
}
if(group && group.selectable){
var dependants = group.dependants;
//Remove dashed line to from dependant blocks
for(var i = dependants.length-1; i >= 0; --i){
dependants[i].getObjects()[0].strokeDashArray = null;
}
group.bringToFront();
var x = Math.round(group.left/widthScale);
var y = Math.round(group.top/heightScale);
var index = -1;
var altPos = group.altPos;
var thisPos = group.thisPos;
//Snap to grid
for(var i = altPos.length-1; i >= 0; --i){
if(altPos[i][thisPos][0] === x){
if(altPos[i][thisPos][1] === y){ //Match in first square
index = i;
//Break means a first square match overrides any other
break;
} else if(y < altPos[i][thisPos][1] + group.classLen &&
y > altPos[i][thisPos][1] - group.classLen){
index = i;
}
}
}
//If block isnt snapped to grid, outline remains white
group.getObjects()[0].stroke = 'white';
//Move any dependant classes
if(index >= 0){
for(var i = dependants.length-1; i >= 0; --i){
var x = altPos[index][i][0];
var y = altPos[index][i][1];
dependants[i].set({
left: xOffset+widthScale*x,
top: yOffset+heightScale*y
});
//Blocks snapped to grid have black outline
dependants[i].getObjects()[0].stroke = 'black';
dependants[i].setCoords();
dependants[i].prevPos = dependants[i].currPos;
dependants[i].currPos = [x, y];
updateTableArray(dependants[i]);
}
}
canvas.renderAll();
}
});
//Highlighting possible positions for class to go
canvas.on('mouse:down', function(options){
var group = canvas.findTarget(options.e);
if(group && group.selectable){
canvas.allowTouchScrolling = false;
group.bringToFront();
var altPos = group.altPos;
var thisPos = group.thisPos;
//Highlight alternative position blocks
//It is faster to make new blocks every time
//than to change the opacity
var color = group.getObjects()[0].fill;
var length = group.classLen;
for(var i = altPos.length-1; i >= 0; --i){
var temp_block = new fabric.Rect({
width: widthScale,
height: length*heightScale,
hasControls: false,
selectable: false,
fill: color,
originX: 'left',
originY: 'top',
stroke: 'black',
strokeWidth: 0.5,
opacity: 0.3,
left: yOffset+altPos[i][thisPos][0]*widthScale,
top: yOffset+altPos[i][thisPos][1]*heightScale
});
tempBlocks.push(temp_block);
canvas.add(temp_block);
temp_block.bringToFront();
}
//Add dashed line to all dependant blocks
for(var i = group.dependants.length-1; i >= 0; --i){
group.dependants[i].getObjects()[0].strokeDashArray = [5,5];
group.getObjects()[0].stroke = 'black'
}
group.setCoords();
canvas.renderAll();
}
});
//Calculate offset whenever canvas is moved
canvas.on('after:render', function(){canvas.calcOffset();});
//Generate a and load it to canvas
//Function will call a recursive backtracker to calculate a timetable
var generateTimetables = function(sortFuncs, clash){
var classes = depObjects.concat();
var timetable = [];
var solutions = [];
//Initialize array representation of timetable
for(var i = 0; i < cols; ++i){
timetable.push([]);
for(var j = 0; j < rows; ++j){
timetable[i].push([]);
}
}
//Check if any classes have only one option and place them if they do
//(improves performance when generating timetables)
for(var i = classes.length-1; i >= 0; --i){
if(classes[i].altPos.length === 1){
var toAdd = classes.splice(i, 1)[0];
var altPos = toAdd.altPos;
var dependants = toAdd.dependants;
for(var j = dependants.length-1; j >= 0; --j){
var x = altPos[0][j][0];
var y = altPos[0][j][1];
for(var k = dependants[j].classLen-1; k >= 0; --k){
if(timetable[x][y+k].length > 0){
--clash;
}
timetable[x][y+k].push([dependants[j], altPos[0][j][2]-k]);
}
}
}
}
//Recursive backtracking function
placeSubject(classes, timetable, solutions, clash);
if(solutions.length > 0){
generatedTimetables = sortTimetables(solutions, sortFuncs);
drawTableArray(generatedTimetables[0]);
}
//If neither works, display error
else{
canvas.clear();
generatedTimetables = [];
canvas.add(new fabric.Text('Could not generate\nTry again', {
fontSize: 20*canvas.pixelRatio,
fontFamily: 'arial, sans-serif',
fontWeight: 'bold',
textAlign: 'center',
originX: 'center',
originY: 'top',
left: canvas.getWidth()/2,
top: 0,
selectable: false
}));
canvas.renderAll();
$('#generate-fail').modal('show');
}
};
var sortTimetables = function(toBeSorted, sortFuncs){
if(sortFuncs.length === 0) return;
//Decorate tables for first round of sorting
for(var i = toBeSorted.length-1; i >= 0; --i){
toBeSorted[i].sortingData = [sortFuncs[0](toBeSorted[i])];
}
//Sort first round solutions
toBeSorted.sort(function(a,b){
if(a.sortingData[0] > b.sortingData[0]){
return 1;
} else if(a.sortingData[0] < b.sortingData[0]){
return -1;
} else{
return 0;
}
});
//Perform remaining rounds of sorting
for(var i = 1; i < sortFuncs.length; ++i){
for(var j = toBeSorted.length-1; j >= 0; --j){
toBeSorted[j].sortingData.push(sortFuncs[i](toBeSorted[j]));
}
}
toBeSorted.sort(function(a,b){
for(var i = 0; i < sortFuncs.length; ++i){
if(a.sortingData[i] > b.sortingData[i]){
return 1;
} else if(a.sortingData[i] < b.sortingData[i]){
return -1;
}
}
return 0;
});
return toBeSorted;
};
//Draws array timetable representation to canvas
var drawTableArray = function(table_arr){
if(!table_arr){
return;
}
for(var i = table_arr.length-1; i >= 0; --i){
for(var j = table_arr[i].length-1; j >= 0; --j){
var length = table_arr[i][j].length;
for(var k = length-1; k >= 0; --k){
var group = table_arr[i][j][k][0];
if(group.classLen === table_arr[i][j][k][1]){
group.set({
left: xOffset+widthScale*i,
top: yOffset+heightScale*j
});
group.setCoords();
group.currPos = [i, j];
group.prevPos = group.currPos;
}
//Make outline red for clashes
if(length > 1){
group.getObjects()[0].stroke = 'red';
}
}
}
}
canvas.renderAll();
};
//Updates the array representation of the timetable when a class moves
//Also updates clash display outline for courses
var updateTableArray = function(classObj){
var table = generatedTimetables[currDisplayed];
var currPos = classObj.currPos;
var prevPos = classObj.prevPos;
var setRed = false;
for(var i = classObj.classLen-1; i >= 0; --i){
var prevTime = table[prevPos[0]][prevPos[1]+i];
var currTime = table[currPos[0]][currPos[1]+i];
for(var j = prevTime.length-1; j >= 0; --j){
if(prevTime[j][0] === classObj){
var toMove = prevTime.splice(j, 1)[0];
currTime.push(toMove);
}
if(!setRed && prevTime.length === 1){
prevTime[0][0].getObjects()[0].stroke = 'black';
}
if(currTime.length > 1){
for(var k = currTime.length-1; k >= 0; --k){
currTime[k][0].getObjects()[0].stroke = 'red';
setRed = true;
}
}
}
}
};
//Recursively place courses until a timetable is finished
//and fill array of solutions until limit is reached (or no more sol possible)
//Note: clashes not yet supported
var placeSubject = function(remaining_classes, curr_timetable, ret_arr, clash){
//Cap number of solutions
if(ret_arr.length > 15000) return;
//Timetable is completed
if(remaining_classes.length === 0){
ret_arr.push(curr_timetable);
return;
}
//Choose random remaining class so that there is a wider
//variety of solutions
var classes = remaining_classes.concat();
var timetable = cloneTable(curr_timetable);
var uClass = classes.splice(Math.floor(Math.random()*classes.length), 1)[0];
var altPos = uClass.altPos;
var dependants = uClass.dependants;
for(var i = altPos.length-1; i >= 0; --i){ //Down list
var maxClash = clash;
for(var j = altPos[i].length-1; j >= 0; --j){ //Across list
var x = altPos[i][j][0];
var y = altPos[i][j][1];
for(var k = dependants[j].classLen-1; k >= 0; --k){
maxClash -= timetable[x][y+k].length;
}
}
if(maxClash >= 0){
//Apply changes to timetable
for(var j = dependants.length-1; j >= 0; --j){
var x = altPos[i][j][0];
var y = altPos[i][j][1];
for(var k = dependants[j].classLen-1; k >= 0; --k){
timetable[x][y+k].push([dependants[j], altPos[i][j][2]-k]);
}
}
placeSubject(classes, timetable, ret_arr, maxClash);
timetable = cloneTable(curr_timetable);
}
}
};
//Shallow clone multideminesional array
//Gets called frequently
var cloneTable = function(timetable){
var new_table = [];
for(var i = 0; i < cols; ++i){
new_table.push([]);
for(var j = 0; j < rows; ++j){
//Concat is faster than slice
new_table[i].push(timetable[i][j].concat());
}
}
return new_table;
};
//Calculates the number of days at uni for given timetable
var table_calc_days = function(timetable){
var days = 0;
for(var i = timetable.length-1; i >= 0; --i){
for(var j = timetable[0].length-1; j >= 0; --j){
if(timetable[i][j].length > 0){
++days;
break;
}
}
}
return days;
};
//Calculates the number of hours at uni for given timetable
var table_calc_hour = function(timetable){
var hours = 0;
for(var i = timetable.length-1; i >= 0; --i){
var start = 0;
var not_counting = true;
var finish = 0;
for(var j = timetable[0].length-1; j >= 0; --j){
if(timetable[i][j].length !== 0){
if(not_counting){
start = j+1;
not_counting = false;
} else{
finish = j;
}
}
}
hours += (start - finish);
}
return hours;
};
//Calculates sleepin time
var table_calc_sleepin = function(timetable){
var hours = 0;
for(var i = timetable.length-1; i >= 0; --i){
var len = timetable[0].length;
for(var j = 0; j < len; ++j){
if(timetable[i][j].length === 0){
--hours;
} else{
break;
}
}
}
return hours;
};
//Calculate afternoon free time
var table_calc_afternoons = function(timetable){
var hours = 0;
for(var i = timetable.length-1; i >= 0; --i){
for(var j = timetable[0].length-1; j >= 0; --j){
if(timetable[i][j].length === 0){
--hours;
} else{
break;
}
}
}
return hours;
};
//Writes current data to cookie
var writeCookie = function(){
document.cookie = 'bojanglesJSON='+ saveStateToJSON() +
'; expires=Fri, 31 Dec 2030 23:59:59 GMT';
};
//Reads subject data from cookie
var readCookie = function(){
var cookieData = document.cookie.split('bojanglesJSON=');
if(cookieData){
loadStateFromJSON(cookieData[1]);
}
};
//Saves the timetable and options to a JSON representation
var saveStateToJSON = function(){
if(generatedTimetables.length <= 0){
return;
}
try{
var jsonOut = {
courses: [],
clash: parseInt($('input:radio[name=clash-sel]:checked').val()),
allowFull: $('#incl-full').prop('checked'),
timetable: []
}
for(var i = 0; i < courses.length; ++i){
jsonOut.courses.push(courses[i].course_code);
}
for(var i = depObjects.length-1; i >= 0; --i){
dependants = depObjects[i].dependants;
var classObj = {
courseCode: dependants[0].courseCode,
classType: dependants[0].classType,
classTimes: []
}
jsonOut.timetable.push(classObj);
//Push subjects in same format as internal representation to json
//Start times are used in place of grid references for portability
for(var j = 0; j < dependants.length; ++j){
classObj.classTimes.push([
dependants[j].currPos[0],
dependants[j].currPos[1]+startHour,
dependants[j].classLen
]);
}
}
return JSON.stringify(jsonOut);
}
catch(err){
throwWarning('Unable to save timetable, be sure to save as an image' +
'if you would like to keep it', true);
return '';
}
};
//Adds a warning with the given text to the page. Adds a severe warning if
//severe is truthy
var throwWarning = function(warning, severe){
var html = '<div class="alert alert-'+ (severe ? 'danger' : 'warning') +
' alert-dismissible" role="alert"> <button type="button" class="close"' +
'data-dismiss="alert"><span aria-hidden="true">&times;</span><span class' +
'="sr-only">Close</span></button>' + warning + '</div>';
$('#inpt-area').prepend(html);
};
//Loads the timetable state from JSON
var loadStateFromJSON = function(jsonIn){
jsonIn = JSON.parse(jsonIn);
console.log(jsonIn);
var jsonDeferred = [];
var addCourse = function(courseIn){
courses.push(courseIn);
$('#chosen').append('<li class="list-group-item class-inpt">' +
courseIn.course_code + ' - ' + courseIn.course_name +
'<button class="btn btn-danger btn-xs pull-right rm-course">'+
'Remove</btn></li>');
if(jsonIn.warning){
throwWarning(JsonIn.warning, false);
}
};
for(var i = 0; i < jsonIn.courses.length; ++i){
jsonDeferred.push($.getJSON('/data/bojangles/'+ jsonIn.courses[i] +
'.json', addCourse));
}
//Wait until all ajax requests are served to start processing
$.when.apply($, jsonDeferred).done(function(){
createTableGrid();
var depObject = addAllClasses(jsonIn.allowFull);
console.log(depObject)
var inptTbl = jsonIn.timetable;
var timetable = [];
//Initialize array representation of timetable
for(var i = 0; i < cols; ++i){
timetable.push([]);
for(var j = 0; j < rows; ++j){
timetable[i].push([]);
}
}
//Each element in the saved timetable
for(var i = 0; i < inptTbl.length; ++i){
//Each independant object for the given subject
for(var j = depObject.length-1; j >= 0; --j){
if(depObject[j].courseCode === inptTbl[i].courseCode &&
depObject[j].classType === inptTbl[i].classType){
var altPos = depObject[j].altPos;
//Down the list of possible positions for the class
for(var k = altPos.length-1; k >= 0; --k){
if(altPos[k].length != inptTbl[i].classTimes.length){
continue;
}
var valid = true;
//Across the depedant classes list
for(var l = altPos[k].length-1; l >= 0; --l){
if(altPos[k][l][0] != inptTbl[i].classTimes[l][0] ||
altPos[k][l][1]+startHour !=
inptTbl[i].classTimes[l][1] ||
altPos[k][l][2] != inptTbl[i].classTimes[l][2]){
valid = false;
break;
}
}
//Must match all fields to be valid
if(valid){
var dependants = depObject[j].dependants;
//Across the dependant classes list
for(var l = dependants.length-1; l >= 0; --l){
var x = altPos[k][l][0];
var y = altPos[k][l][1];
//Add object block in timetable to each hour
for(var m=dependants[l].classLen-1; m >= 0;--m){
timetable[x][y+m].push([dependants[l],
altPos[k][l][2]-m]);
}
}
}
}
}
}
}
generatedTimetables.push(timetable);
drawTableArray(timetable);
});
$('#incl-full').prop('checked', jsonIn.allowFull);
};
$(document).ready(function(){
createTableGrid();
var clash = 0;
//Initialize tooltips
//Except on IOS devices where they do not work correctly
//due to problem in bootstrap.
//See: http://github.com/twbs/bootstrap/issues/6232
if(!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)){
$('#typeaheadtooltip').tooltip();
$('#sorting-opt').tooltip();
$('#generate').tooltip();
$('#reset').tooltip();
$('#timetable').tooltip();
$(document).on('mouseenter','[rel=tooltip]', function(){
$(this).tooltip('show');
});
}
//Initialize buttons
$('.btn').each(function() {
$(this).button();
});
//Add subject to list when add is pressed
$('#course-srch').on('click', function() {
addSubject($('#course-inpt').val());
$('#course-inpt').val('');
});
//Add subject to list when enter is pressed
$('#course-inpt').on('keypress', function(key){
if(key.keyCode === 13){
key.preventDefault();
addSubject($(this).val());
$(this).val('');
}
});
//Generate timetable when button is pressed
$('#generate').on('click', function(){
clash = parseInt($('input:radio[name=clash-sel]:checked').val());
createTableGrid();
var sortOrderArr = sortOrder.toArray();
var sortFuncs = [];
for(var i = 0; i < sortOrderArr.length; ++i){
if(sortOrderArr[i] === 'days'){
sortFuncs.push(table_calc_days);
} else if(sortOrderArr[i] === 'hours'){
sortFuncs.push(table_calc_hour);
} else if(sortOrderArr[i] === 'sleepin'){
sortFuncs.push(table_calc_sleepin);
} else if(sortOrderArr[i] === 'afternoon'){
sortFuncs.push(table_calc_afternoons);
} else if(sortOrderArr[i] === 'random'){
break;
}
}
$('#loading').fadeIn('fast');
$('#bojangle-area').fadeTo('fast', 0.3, function(){
addAllClasses($('#incl-full').prop('checked'));
generateTimetables(sortFuncs, clash);
$('#bojangle-area').fadeTo('fast', 1);
writeCookie();
$('#loading').fadeOut('fast');
});
});
//Save timetable as png
$('#save-btn').on('click', function(){
canvas.deactivateAll().renderAll();
var png = canvas.toDataURL('img/png');
var newWindow = window.open();
var html = '<html><head><title>timetable</title></head><body><img src='+
png + '></body></html>'
$(newWindow.document.body).html(html);
});
//Removing courses when button is pushed
$(document).on('click', '.rm-course', function(e){
e.preventDefault();
var $rm = $(this).parent();
$rm.fadeOut(400, function(){
var course_code = $rm.text().split(' - ')[0];
for(var i = 0; i < courses.length; ++i){
if(courses[i].course_code === course_code){
courses.splice(i, 1);
}
}
$rm.remove();
});
});
//Reset inputs and canvas when reset button pressed
$('#reset').on('click', function(){
createTableGrid(9,17,5);
courses = [];
$('.class-inpt').each(function() {
$(this).fadeOut(400, function(){
$(this).remove();
});
});
writeCookie();
});
//Next timetable button
$('#next-tt').on('click', function(e){
e.preventDefault();
if(currDisplayed < generatedTimetables.length-1){
++currDisplayed;
drawTableArray(generatedTimetables[currDisplayed]);
}
});
//Previous timetable button
$('#prev-tt').on('click', function(e){
e.preventDefault();
if(currDisplayed > 0){
--currDisplayed;
drawTableArray(generatedTimetables[currDisplayed]);
}
});
//Check cookies
readCookie();
});
//Save cookie on page unload
$(window).on('unload', function() {
writeCookie();
});
//Resize canvas as the panel resizes
$(window).on('resize', function(){
var new_width = $('#inpt-area').width();
if(canvas.pixelRatio > 1){
var new_height = Math.ceil(new_width*rows/(2*cols));
$('#timetable').width(new_width).height(new_height);
$('.canvas-container').width(new_width).height(new_height);
$('.upper-canvas').width(new_width).height(new_height);
} else{
canvas.setZoom(new_width/width);
canvas.setWidth(new_width);
canvas.setHeight(Math.ceil(new_width*rows/(2*cols)));
}
canvas.calcOffset();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment