vue-course-requisite
Version:
Vue plugin for displaying, configuring, and modifying course pre and co requisites.
139 lines (117 loc) • 4.93 kB
JavaScript
// The exposed API is used to validate a course prereqs / coreqs against some
// set of courses distributed into terms
// @param {Object} course: should be in the following form with optional prereqs / coreqs
// refer to test/unit/fixtures/courses.js for examples of how the prereqs / coreqs
// should be formed:
// {
// id: 1,
// prereq: {/*...*/},
// coreq: {/*...*/}
// }
// @param {Number} index: the index in which the course is located in the terms
// @param {Array} terms: should be formatted as follows:
// [
// // term 1 with courses
// [
// { id: 1 },
// { id: 2 },
// { id: 3 }
// ],
// // term 2 with courses
// [
// { id: 4 },
// { id: 5 },
// { id: 6 }
// ]
// ]
export default function (course, index, terms) {
const prereq = JSON.parse(JSON.stringify(course.prereq || null))
const coreq = JSON.parse(JSON.stringify(course.coreq || null))
return {
prereq: prereq ? validateRequisites(prereq, index, terms, 'prereq') : { code: 'valid' },
coreq: coreq ? validateRequisites(coreq, index, terms, 'coreq') : { code: 'valid' }
}
}
// Validates a requisite tree
// @param {Object} req: the requisite operand
// @param {Number} index: index of original course occurance
// @param {Array} terms: 2d array of courses by terms
// @param {String} type: 'prereq' or 'coreq'
function validateRequisites (req, index, terms, type) {
const offendingCourses = []
const missingCourses = []
if (validateOperand(req, index, terms, type, offendingCourses, missingCourses)) {
return { code: 'valid', reportedRequisite: req, offendingCourses, missingCourses }
} else if (offendingCourses.length) {
return { code: 'error', reportedRequisite: req, offendingCourses, missingCourses }
} else {
return { code: 'warning', reportedRequisite: req, offendingCourses, missingCourses }
}
}
// Validates an operand based on the operand.type
// @param {Object} operand: the requisite operand
// @param {Number} index: index of original course occurance
// @param {Array} terms: 2d array of courses by terms
// @param {String} type: 'prereq' or 'coreq'
// @param {Array} offendingCourses: array that will contain a list of courses that invalidate
// the pre/co req (those that come on or after the term)
// @param {Array} missingCourses: array that will contain a list of pre/co req courses that are missing
function validateOperand (operand, index, terms, type, offendingCourses, missingCourses) {
switch (operand.type) {
case 'and':
return operand.operands
.map(o => validateOperand(o, index, terms, type, offendingCourses, missingCourses))
.every(b => b)
case 'or':
const ops = operand.operands.filter(o => ['or', 'and', 'course'].indexOf(o.type) > -1)
return !ops.length || ops
.map(o => validateOperand(o, index, terms, type, offendingCourses, missingCourses))
.some(b => b)
case 'course':
return validateCourseOperand(operand, index, terms, type, offendingCourses, missingCourses)
default:
return true
}
}
// Validates a course operand
// @param {Object} operand: the requisite operand
// @param {Number} index: index of original course occurance
// @param {Array} terms: 2d array of courses by terms
// @param {String} type: 'prereq' or 'coreq'
// @param {Array} offendingCourses: array that will contain a list of courses that invalidate
// the pre/co req (those that come on or after the term)
// @param {Array} missingCourses: array that will contain a list of pre/co req courses that are missing
function validateCourseOperand (operand, index, terms, type, offendingCourses, missingCourses) {
let validTerms = []
let invalidTerms = []
if (type === 'prereq') {
validTerms = terms.slice(0, index)
}
if (type === 'coreq' || operand.concurrency_ind) {
validTerms.push(terms[index])
}
invalidTerms = remaining(terms, validTerms)
let valid = validTerms.some(t => t.some(c => c.id === operand.course.id))
let offending = false
if (!valid) {
offending = invalidTerms.some(t => t.some(c => c.id === operand.course.id))
}
if (valid) {
operand.status = 'valid'
} else if (offending) {
operand.status = 'error'
operand.statusMessage = `${type} is out of place`
offendingCourses.push(operand.course)
} else {
operand.status = 'warning'
operand.statusMessage = `${type} is missing`
missingCourses.push(operand.course)
}
return valid
}
// Return the remaining array elements from a1 that are not in a2
// @param {Array} a1: array containing all elements
// @param {Array} a2: array containing used elements
function remaining (a1, a2) {
return a1.filter(e => !a2.indexOf(e) > -1)
}