diff --git a/both/collections/_schemas.js b/both/collections/_schemas.js index 92578641..8575d733 100644 --- a/both/collections/_schemas.js +++ b/both/collections/_schemas.js @@ -1 +1,2 @@ Schema = {}; +QuizzesSchema = {}; diff --git a/both/collections/questions.js b/both/collections/questions.js new file mode 100644 index 00000000..a400e921 --- /dev/null +++ b/both/collections/questions.js @@ -0,0 +1,91 @@ +Questions = new Mongo.Collection('questions'); + +QuestionSchema = new SimpleSchema({ + id: { + type: String, + optional: false, + unique: true + }, + questionType: { + type:String, + optional: false + }, + quizId: { + type:String, + optional: false + }, + title: { + type:String, + optional: false + }, + description : { + type:String, + optional: false + }, + numberOfOptions: { + + type: Number, + optional: true, + max: 10, + min: 2 + }, + optionTitles: { + label: "Options", + type: [Object], + optional: false, + autoform: { + type: "radio-with-text-input", + options: function(){ + var optionsArray = []; + + for ( var i=0; i < 8; i++){ + var option = Quiz.generateAnswerOption("", false, i) + optionsArray.push(option); + } + return optionsArray; + } + }, + custom: function() { + // This custom function renders an error, if this field is not equal to + // the new Password field supplied in the form. + var titlesValid = true; + var selectionFound = false; + for (var i = 0; i < this.value.length; i++){ + var obj = this.value[i]; + if (!obj.title){ + titlesValid = false; + return "invalidQuestionTitles"; + } + if (obj.isSelected){ + selectionFound = true; + } + } + if (!selectionFound){ + return "invalidQuestionSelection"; + } + } + }, + + "optionTitles.$.title": { + type:String, + optional: false + }, + + "optionTitles.$.isSelected": { + type:Boolean, + optional: false + }, + "optionTitles.$.index":{ + type:Number, + optional: false + }, + + + options:{ + label: "Options", + type: [Object], + } +}) + + +Questions.attachSchema(QuestionSchema); diff --git a/both/collections/quizzes.js b/both/collections/quizzes.js new file mode 100644 index 00000000..3b2533bf --- /dev/null +++ b/both/collections/quizzes.js @@ -0,0 +1,35 @@ +Quizzes = new Mongo.Collection('quizzes'); + +QuizzesSchema.AnswerOptionSchema = new SimpleSchema({ + title: { + type:String, + optional: true, + defaultValue: "" + }, + isCorrect: { + type: Boolean, + defaultValue: false + }, +}); + +//This schema will validate the initial creation of a quiz +QuizzesSchema.QuizzesSchema = new SimpleSchema({ + title: { + type:String, + label: "Quiz Title", + min: 4, + max: 140 + }, + questions: { + type: [Object], + optional: true + }, + + lessonID: { + type:String, + }, +}); + +Quizzes.attachSchema(QuizzesSchema.QuizzesSchema); + + diff --git a/both/common.js b/both/common.js index 8730a228..2e5ff4bb 100644 --- a/both/common.js +++ b/both/common.js @@ -1,3 +1,6 @@ SimpleSchema.messages({ - "passwordMismatch": "Passwords do not match" + "passwordMismatch": "Passwords do not match", + "invalidQuestionSettings": "Invalid question settings", + "invalidQuestionTitles": "All options should be filled in", + "invalidQuestionSelection": "Please select an answer" }); \ No newline at end of file diff --git a/both/quizModel.js b/both/quizModel.js new file mode 100644 index 00000000..a80270aa --- /dev/null +++ b/both/quizModel.js @@ -0,0 +1,197 @@ +Quiz = function(){ + var quiz = this; + this.addNewQuestion = function(val){ + //check if the question already exists + if (quiz.questions == undefined){ + quiz.questions = []; + } + for (var q in quiz.questions){ + + }; + + quiz.questions.push(val); + }; + +}; + +Quiz.convertToQuizObject = function(object){ + + if (object == undefined) return; + var quiz = new Quiz(); + + quiz._id = object._id; + quiz.title = object.title; + quiz.lessonID = object.lessonID; + quiz.questions = object.questions; + quiz.userAttempts = object.userAttempts; + + return quiz; +}; + +Quiz.generateQuestion = function(questionType, quizId ){ + var question = new Object(); + question.quizId = quizId; + question.id = Random.id(); //assign an id to the question - need this for checking and validating answers + question.questionType = questionType; + + //if the question type is true-or-false, populate the answer options + if (question.questionType == QuizOptions.TRUE_OR_FALSE){ + question.optionTitles = []; + var trueOption = Quiz.generateAnswerOption("True", false, 0); + + question.optionTitles.push(trueOption); + var falseOption = Quiz.generateAnswerOption("False", false, 1); + question.optionTitles.push(falseOption); + }; + + question.description = ""; + question.title = ""; + question.options = []; + return question; +}; + +Quiz.generateAnswerOption = function (title, isSelected, index){ + var option = {}; + option.title = title; + option.isSelected = isSelected; + option.index = index; + + return option +} + + +Object.defineProperty(Quiz, "_title", { + get: function title(){ + return this._title; + }, + set: function title(val){ + this._title = val; + } +}); +Object.defineProperty(Quiz, "_questions", { + get: function question(){ + return this._questions; + }, + set: function questions(val){ + this._questions = val; + } +}); + +Object.defineProperty(Quiz, "_lessonID", { + get: function lessonID(){ + return this._lessonIDs + }, + set: function lessonID(val){ + this._lessonID = val; + } +}); + +Object.defineProperty(Quiz, "_userAttempts", { //an array of objects containig user ids, date and result + get: function userAttempts(){ + return this._userAttempts; + }, + set: function userAttempts(val){ + this._userAttempts = val; + } +}) + + + + +QuizOptions = {}; + +QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER = "Multiple Choice - single answer"; +QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS = "Multiple Choice - multiple answers"; +QuizOptions.TRUE_OR_FALSE = "True or False"; +QuizOptions.QUESTION_TYPES = + [ QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER, + QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS, + QuizOptions.TRUE_OR_FALSE + ]; + + + +QuizQuestion = function() {}; +Object.defineProperty(QuizQuestion, "_questionType", { + get: function questionType() { + return this._quiztype; + }, + set: function questionType(val) { + this._quiztype = val; + } +}); + +//array of lesson IDs - the quiz can be used in more than one lesson +Object.defineProperty(QuizQuestion, "_quizId", { + get: function quizId(){ + return this._quizId + }, + set: function quizId(val){ + this._lessonIDs = val; + } +}); + +Object.defineProperty(QuizQuestion, "_title", { + get: function title(){ + return this._title; + }, + set: function title(val){ + this._title = val; + } +}); + +Object.defineProperty(QuizQuestion, "_description", { + get: function description(){ + return this._description; + }, + set: function description(val){ + this._description = val; + } +}); + +Object.defineProperty(QuizQuestion, "_options", { + get: function options(){ + return this._options; + }, + set: function options(val){ + this._questions = val; + } +}); + +Object.defineProperty(QuizQuestion, "_saved",{ + get: function saved(){ + return this._saved; + }, + set: function saved(val){ + this._saved = val; + } +}); + +Object.defineProperty(QuizQuestion, "_answered", { + get: function answered(){ + return this._answered; + }, + set: function answered(val){ + this._answered = val; + } +}) + +Object.defineProperty(QuizQuestion, "_isMultipleAnswer", { + get: function isMultipleAnswer(){ + return this._questionType == QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS; + } +}); + +Object.defineProperty(QuizQuestion, "_isSingleAnswer", { + get: function isSingleAnswer(){ + return this._questionType == QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER; + } +}); + +Object.defineProperty(QuizQuestion, "_isTrueOrFalse", { + get: function isTrueOrfalse(){ + return this._questionType == QuizOptions.TRUE_OR_FALSE; + } +}) + + diff --git a/client/helpers/editMode.js b/client/helpers/editMode.js index 21399f52..4836f40d 100644 --- a/client/helpers/editMode.js +++ b/client/helpers/editMode.js @@ -2,3 +2,20 @@ Template.registerHelper('editMode', function () { // get edit mode session variable return Session.get('editMode'); }); + +Template.registerHelper('isEditingCurrentCourse', function() { + // Get reference to current router + var router = Router.current(); + + // Get Course ID from router + var currentCourseId = router.params._id; + + // Get value of editing course session variable + var editingCourseId = Session.get('editingCourseId') + + // See if user is editing current course + var editingCurrentCourse = (editingCourseId === currentCourseId); + + // return true if user is editing this course + return editingCurrentCourse; +}); \ No newline at end of file diff --git a/client/templates/course/course.js b/client/templates/course/course.js index 8a90503a..266c023b 100644 --- a/client/templates/course/course.js +++ b/client/templates/course/course.js @@ -13,6 +13,10 @@ Template.course.created = function () { // Set the empty active lesson ID variable activeLessonID = new ReactiveVar(undefined); + + //Set an ampty active quiz Id var + activeQuizID = new ReactiveVar(undefined); + }; Template.course.helpers({ @@ -24,5 +28,6 @@ Template.course.helpers({ var course = Courses.findOne(instance.courseId); return course; - } + }, + }); diff --git a/client/templates/course/lesson/lesson.css b/client/templates/course/lesson/lesson.css new file mode 100644 index 00000000..e9c1c296 --- /dev/null +++ b/client/templates/course/lesson/lesson.css @@ -0,0 +1,56 @@ + +.add-quiz-text { + cursor: pointer; + font-size: 11px; +} + +.add-quiz-btn { + color: #FFFFFF; + background-color: #5CB85C; + border: #bbb 4px solid; + border-radius: 4px; + margin: 5px; + margin-right: 10px; + width: 100%; + padding: 3px; +} + +.add-quiz-btn:hover, +.add-quiz-btn:focus, +.add-quiz-btn:active, +.add-quiz-btn.active, +.open .dropdown-toggle.add-quiz-btn { + color: #FFFFFF; + background-color: #5CB85C; + border-color: #aaa; +} + +.add-quiz-btn:active, +.add-quiz-btn.active, +.open .dropdown-toggle.add-quiz-btn { + background-image: none; +} + +.add-quiz-btn.disabled, +.add-quiz-btn[disabled], +fieldset[disabled] .add-quiz-btn, +.add-quiz-btn.disabled:hover, +.add-quiz-btn[disabled]:hover, +fieldset[disabled] .add-quiz-btn:hover, +.add-quiz-btn.disabled:focus, +.add-quiz-btn[disabled]:focus, +fieldset[disabled] .add-quiz-btn:focus, +.add-quiz-btn.disabled:active, +.add-quiz-btn[disabled]:active, +fieldset[disabled] .add-quiz-btn:active, +.add-quiz-btn.disabled.active, +.add-quiz-btn[disabled].active, +fieldset[disabled] .add-quiz-btn.active { + background-color: #5CB85C; + border-color: #aaa; +} + +.add-quiz-btn .badge { + color: #5CB85C; + background-color: #FFFFFF; +} diff --git a/client/templates/course/lesson/lesson.html b/client/templates/course/lesson/lesson.html index b7072804..2c6bc7f6 100644 --- a/client/templates/course/lesson/lesson.html +++ b/client/templates/course/lesson/lesson.html @@ -1,14 +1,20 @@ diff --git a/client/templates/course/lesson/lesson.js b/client/templates/course/lesson/lesson.js index 107ed361..652ccfe7 100644 --- a/client/templates/course/lesson/lesson.js +++ b/client/templates/course/lesson/lesson.js @@ -7,5 +7,12 @@ Template.lesson.helpers({ var lesson = Lessons.findOne({_id: lessonID}); return lesson; + }, + + 'activeQuiz': function(){ + var quizId = activeQuizID.get(); + + var quiz = Quiz.convertToQuizObject(Quizzes.findOne({ _id: quizId })); + return quiz; } }); diff --git a/client/templates/course/lesson/lesson.less b/client/templates/course/lesson/lesson.less index e69de29b..ec4d88aa 100644 --- a/client/templates/course/lesson/lesson.less +++ b/client/templates/course/lesson/lesson.less @@ -0,0 +1,9 @@ +#addQuizBtn{ + margin-left: 12px; + margin-top: 5px; +} + +.add-quiz-text{ + cursor: pointer; + font-size: 11px; +} \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/autoform.js b/client/templates/course/lesson/quiz/questions/autoform.js new file mode 100644 index 00000000..2f8add36 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/autoform.js @@ -0,0 +1,26 @@ + +var addQuestionFormHooks = { + before: { + method: function(doc, params){ + var editedQuestion = Session.get("currentQuestionToBuild"); + editedQuestion.title = doc.title; + editedQuestion.description = doc.description; + return editedQuestion; + } + }, + onSuccess: function(operation, result, template) { + + //clear the session cache of the currently edited question + Session.set("currentQuestionToBuild", undefined); + Session.set("currentQuestionTypeChanged", undefined); + delete Session.keys["currentQuestionToBuild"]; + delete Session.keys["currentQuestionTypeChanged"]; + return sAlert.success("question added"); + }, + onError: function(operation, error, template) { + return sAlert.error(error); + }, +} + + +AutoForm.addHooks('addQuestionForm', addQuestionFormHooks); \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.html b/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.html new file mode 100644 index 00000000..1fa6db70 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.js b/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.js new file mode 100644 index 00000000..f6826d71 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/autoform/afQuestionOptionsGroup.js @@ -0,0 +1,123 @@ +Template.afFieldQuestionOptionsGroup.helpers({ + + displayedOptions: function(){ + var question = Session.get("currentQuestionToBuild"); + + var displayedOptions = []; + var allOptions = Template.instance().data.selectOptions; + + var totalQuestions = question && question.optionTitles ? question.optionTitles.length : 0; + + for (var i= 0; i< totalQuestions; i++){ + displayedOptions.push (allOptions[i]); + } + + return displayedOptions; + }, + + isHidden: function(){ + var question = Session.get("currentQuestionToBuild"); + //the true-or-false questions will always have only two options + if (question.questionType == QuizOptions.TRUE_OR_FALSE){ + return this.index > 1; + } + + //for the multiple choice - check the number of options selected in the GUI + var totalAnswers = question.optionTitles; + if (totalAnswers && parseInt(this.index) < totalAnswers.length ){ + //this is required to make sure that the input field doesn't contain any stale data + //seems to be an issue when the onscreen template is rewired + var inputField = $('.js-answer-option-input')[this.index] + if (inputField) { + inputField.value = totalAnswers[this.index].title; + } + + return false; + } + return true; + }, + + isSingleAnswer: function(){ + var question = Session.get("currentQuestionToBuild"); + return question.questionType == QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER + }, + + isMultipleAnswer: function(){ + var question = Session.get("currentQuestionToBuild"); + return question.questionType == QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS; + }, + + isTrueOrFalse: function(){ + var question = Session.get("currentQuestionToBuild"); + return question.questionType == QuizOptions.TRUE_OR_FALSE; + }, + + trueOrFalseLabel: function(){ + var label; + if (this.index < 2){ + return this.index == 0 ? "True" : "False"; + } + return label; + }, + + answerIndex: function(){ + return this.index + 1; + }, + answerSelected: function(){ + return this.isSelected; + } +}); + +Template.afFieldQuestionOptionsGroup.rendered = function(){ + $('.js-answer-option-radio').attr("checked", false); +} + +// + +Template.afFieldQuestionOptionsGroup.events({ + 'blur .js-answer-option-input': function(event){ + var question = Session.get("currentQuestionToBuild"); + var editedOption = this; + var options = question.optionTitles; + + for (var i= 0; i< options.length; i++){ + if (options[i].index.toString() == editedOption.index.toString()){ + options[i].title = $(event.target).val(); + //update the session var + Session.set("currentQuestionToBuild", question); + } + } + + }, + + 'change .js-answer-option-radio': function(event){ + var question = Session.get("currentQuestionToBuild"); + var editedOption = this; + + //update the questions + var options = question.optionTitles; + + for (var i= 0; i< options.length; i++){ + if (options[i].index.toString() == editedOption.index.toString()){ + options[i].isSelected = $(event.target).val() == "on"; + }else{ + options[i].isSelected = false; + } + } + Session.set("currentQuestionToBuild", question); + }, + 'change .js-answer-option-checkbox': function(event){ + var question = Session.get("currentQuestionToBuild"); + var editedOption = this; + + //update the questions + var options = question.optionTitles; + + for (var i= 0; i< options.length; i++){ + if (options[i].index.toString() == editedOption.index.toString()){ + options[i].isSelected = $(event.target).val() == "on"; + } + } + Session.set("currentQuestionToBuild", question); + } +}); \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.html b/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.html new file mode 100644 index 00000000..37a87b8d --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.js b/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.js new file mode 100644 index 00000000..b861c1c3 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/partials/deleteQuizQuestionButton.js @@ -0,0 +1,16 @@ +Template.deleteQuizQuestionButton.events({ + 'click .delete-template-button': function(event){ + var question = Template.currentData().question; + + //create a custom event + //use document.createEvent as the CustomEvent is not supported by IE + var deleteQuestionEvent = document.createEvent("HTMLEvents"); + deleteQuestionEvent.initEvent("deleteQuestion", true, true); + deleteQuestionEvent.question = question; + + //dispatch the event from the target button + var btn = event.target; + btn.dispatchEvent(deleteQuestionEvent); + + } +}); \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/partials/questionContainer.html b/client/templates/course/lesson/quiz/questions/partials/questionContainer.html new file mode 100644 index 00000000..02bc7a6c --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/partials/questionContainer.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/partials/questionContainer.js b/client/templates/course/lesson/quiz/questions/partials/questionContainer.js new file mode 100644 index 00000000..90634bd5 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/partials/questionContainer.js @@ -0,0 +1,28 @@ +Template.questionContainer.helpers({ + questionType: function(){ + return this.question.questionType; + }, + + options: function(){ + return this.question.optionTitles; + }, + + isMultipleAnswer: function(){ + var question = Template.parentData().question; + return question.questionType == "Multiple Choice - multiple answers"; + }, + + optionTitle: function(){ + return this.title; + }, + + optionSelected: function(){ + Session.get("currentQuestionTypeChanged"); + return this.isSelected; + }, + + questionTitle: function(){ + var question = Template.parentData().question; + return question.title; + } +}); \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/questionWrapper.css b/client/templates/course/lesson/quiz/questions/questionWrapper.css new file mode 100644 index 00000000..72a41d8f --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/questionWrapper.css @@ -0,0 +1,3 @@ +.delete-template-button{ + float: right; +} \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/questionWrapperTitle.html b/client/templates/course/lesson/quiz/questions/questionWrapperTitle.html new file mode 100644 index 00000000..05e3a57b --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/questionWrapperTitle.html @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/questionWrapperTitle.js b/client/templates/course/lesson/quiz/questions/questionWrapperTitle.js new file mode 100644 index 00000000..92056a73 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/questionWrapperTitle.js @@ -0,0 +1,27 @@ +Template.questionWrapperTitle.helpers({ + isMultipleAnswer: function(){ + var question = this.question; + return question? question.questionType == QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS : false; + }, + isSingleAnswer: function(){ + var question = this.question; + return question? question.questionType == QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER : false; + }, + isTrueOrFalse: function(){ + + var question = this.question; + return question ? question.questionType == QuizOptions.TRUE_OR_FALSE : false; + }, + + questionIndex: function(){ + return Template.currentData().index + 1; + } +}); + +Template.questionWrapperTitle.events({ + 'deleteQuestion .question-wrapper-row': function(event){ + var question = Template.currentData().question; + Meteor.call("removeQuestion", question); + } +}); + diff --git a/client/templates/course/lesson/quiz/questions/quiz-questions.html b/client/templates/course/lesson/quiz/questions/quiz-questions.html new file mode 100644 index 00000000..2331add1 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/quiz-questions.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/questions/quiz-questions.js b/client/templates/course/lesson/quiz/questions/quiz-questions.js new file mode 100644 index 00000000..b9ca36b4 --- /dev/null +++ b/client/templates/course/lesson/quiz/questions/quiz-questions.js @@ -0,0 +1,8 @@ +Template.quizQuestions.helpers({ + questions: function(){ + if (!this.quiz) return; + var id = this.quiz._id; + var questions = Questions.find({"quizId": id}).fetch(); + return questions; + } +}); \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/quiz-content.css b/client/templates/course/lesson/quiz/quiz-content.css new file mode 100644 index 00000000..09f0d5b9 --- /dev/null +++ b/client/templates/course/lesson/quiz/quiz-content.css @@ -0,0 +1,29 @@ +.save-quiz-btn{ + float: right; +} + +.quiz-content{ +} +.question-wrapper{ + border: 1px dotted #dc681d; + border-radius: 4px; + background-color: #f5f5f5; + padding: 10px; + margin-top: 7px; +} + +.new-question-wrapper{ + background-color: #FBFBF1; +} + +.form-group-sm { + margin-bottom: 5px; +} + +.form-control-label{ + font-weight: normal !important; +} + +.footer-section{ + padding: 30px; +} \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/quiz-content.html b/client/templates/course/lesson/quiz/quiz-content.html new file mode 100644 index 00000000..4700a0e8 --- /dev/null +++ b/client/templates/course/lesson/quiz/quiz-content.html @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/client/templates/course/lesson/quiz/quiz-content.js b/client/templates/course/lesson/quiz/quiz-content.js new file mode 100644 index 00000000..79e27055 --- /dev/null +++ b/client/templates/course/lesson/quiz/quiz-content.js @@ -0,0 +1,180 @@ +AutoForm.addInputType("selector-with-text-input", { + template: "afFieldQuestionOptionsGroup", + valueIn: function(val){ + return val; + }, + contextAdjust: function(context){ + return context; + } +}); + +Template.quizContent.created = function(){ + this.addQuestionButtonDisabled = new ReactiveVar(true); + this.quizQuestions = new ReactiveVar([]); + this.quizValidator = new ReactiveVar({}); +}; + +AutoForm.debug(); + +Template.quizContent.helpers({ + currentQuiz: function(){ + return Template.currentData().activeQuiz; + }, + + questionTypes: function(){ + return QuizOptions.QUESTION_TYPES; + }, + + currentQuestionToBuild:function(){ + return Session.get("currentQuestionToBuild") != undefined; + }, + + isMultipleAnswer: function(){ + + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.questionType == QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS; + } + return false; + + }, + isSingleAnswer: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.questionType == QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER; + } + return false; + }, + isTrueOrFalse: function(){ + + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.questionType == QuizOptions.TRUE_OR_FALSE;; + } + return false; + }, + + selectedQuestionType: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.questionType; + } + }, + + quizId: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.quizId + } + return null; + }, + questionId: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.id; + } + return null; + }, + + qType: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + if (editedQuestion) + { + return editedQuestion.questionType; + } + return null; + }, + + selectDropdownOptions: function(){ + return [ + {label: "2 choices", value: 2}, + {label: "3 choices", value: 3}, + {label: "4 choices", value: 4}, + {label: "5 choices", value: 5}, + {label: "6 choices", value: 6}, + {label: "7 choices", value: 7}, + {label: "8 choices", value: 8} + ] + }, + numOfChoices: function() { + var formId = AutoForm.getFormId(); + var selection = AutoForm.getFieldValue("numberOfOptions", formId); + var selectionDropDown = $('.js-number-of-options'); + var numOfOptions = parseInt(selectionDropDown.val()) || 0; + + var editedQuestion = Session.get("currentQuestionToBuild"); + var existingOptions = []; + if (editedQuestion && editedQuestion.optionTitles && numOfOptions > 0){ + existingOptions = editedQuestion.optionTitles; + } + + var isNewType = Session.get("currentQuestionTypeChanged") || Session.get("currentQuestionTypeChanged") == undefined; + + if(existingOptions && existingOptions.length == numOfOptions && !isNewType){ + //the number of options hasn't changed + return existingOptions; + } + + + var optionsArray; + console.log("is new type : " + isNewType); + if (numOfOptions > 0){ + optionsArray = []; + for (var i=0; i < numOfOptions; i++){ + var option; + if(existingOptions && existingOptions.length > i){ + option = existingOptions[i]; + }else{ + option = Quiz.generateAnswerOption("", false, i) + } + optionsArray.push(option); + } + var editedQuestion = Session.get("currentQuestionToBuild"); + editedQuestion.optionTitles = optionsArray; + Session.set("currentQuestionToBuild", editedQuestion); + } + + if (isNewType){ + Session.set("currentQuestionTypeChanged", false); + } + return optionsArray; + }, + + /* selectedQuestionType: function(){ + var editedQuestion = Session.get("currentQuestionToBuild"); + $('#questionTypesSelector').val(editedQuestion.questionType); + return editedQuestion.questionType; + }*/ + +}); + +Template.quizContent.events({ + 'change #questionTypesSelector': function(event){ + Template.instance().addQuestionButtonDisabled.set(false); + var activeQuiz = Template.currentData().activeQuiz; + var questionType = $('#questionTypesSelector').val(); + var question = Quiz.generateQuestion(questionType, activeQuiz._id); + Session.set("currentQuestionToBuild", question); + Session.set("currentQuestionTypeChanged", true); + }, + + 'deleteQuestion .question-content': function(event){ + + //set the reactive var to update the list of questions + Template.instance().quizQuestions.set(Template.currentData().activeQuiz.questions); + }, + + 'click #cancelNewQuestionBtn': function(event){ + Session.set("currentQuestionToBuild", undefined); + Session.set("currentQuestionTypeChanged", undefined); + delete Session.keys["currentQuestionToBuild"]; + delete Session.keys["currentQuestionTypeChanged"]; + $('#questionTypesSelector').prop("selectedIndex", 0); + } +}); \ No newline at end of file diff --git a/client/templates/course/sidebar/section/add-quiz/add-quiz.css b/client/templates/course/sidebar/section/add-quiz/add-quiz.css new file mode 100644 index 00000000..e69de29b diff --git a/client/templates/course/sidebar/section/add-quiz/add-quiz.html b/client/templates/course/sidebar/section/add-quiz/add-quiz.html new file mode 100644 index 00000000..4b692479 --- /dev/null +++ b/client/templates/course/sidebar/section/add-quiz/add-quiz.html @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/client/templates/course/sidebar/section/add-quiz/add-quiz.js b/client/templates/course/sidebar/section/add-quiz/add-quiz.js new file mode 100644 index 00000000..5c2ca517 --- /dev/null +++ b/client/templates/course/sidebar/section/add-quiz/add-quiz.js @@ -0,0 +1,40 @@ +Template.addQuiz.created = function(){ + this.showInput = new ReactiveVar(false) +}; + +Template.addQuiz.helpers({ + showInput: function (){ + return Template.instance().showInput.get(); + } + +}); + +Template.addQuiz.events({ + 'submit .add-new-quiz-form': function (event, instance) { + event.preventDefault(); // prevent page from refreshing + + var quizTitle, // Title of new quiz, from template + newQuizId; // ID of created quiz, returned from db insert + + // Get the title of new section + quizTitle = instance.find('#quiz-title').value; + + var quizObj = new Quiz(); + quizObj.title = quizTitle; + quizObj.lessonID = Template.currentData(); //current data is the lesson ID + quizObj.questions = []; + // Insert new quiz into database + newQuizId = Quizzes.insert(quizObj); + + // Reset the value of section title field + $("#quiz-title").val(""); + instance.showInput.set(false); + }, + + 'click .sidebar-add-quiz-link': function(event, template){ + event.preventDefault(); + var currentShowInput = template.showInput.get(); + var newShowInput = !currentShowInput + template.showInput.set(newShowInput); + } +}); \ No newline at end of file diff --git a/client/templates/course/sidebar/section/add-quiz/add-quiz.less b/client/templates/course/sidebar/section/add-quiz/add-quiz.less new file mode 100644 index 00000000..8e84055f --- /dev/null +++ b/client/templates/course/sidebar/section/add-quiz/add-quiz.less @@ -0,0 +1,3 @@ +.add-new-quiz-form{ + padding-top: 10px; +} \ No newline at end of file diff --git a/client/templates/course/sidebar/section/add-quiz/quiz.js b/client/templates/course/sidebar/section/add-quiz/quiz.js new file mode 100644 index 00000000..57fdbc5c --- /dev/null +++ b/client/templates/course/sidebar/section/add-quiz/quiz.js @@ -0,0 +1,36 @@ +/*function QuizOptions(){} + + + +QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER = "Multiple Choice - single answer"; +QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS = "Multiple Choice - multiple answers"; +QuizOptions.TRUE_OR_FALSE = "True or False"; +QuizOptions.QUESTION_TYPES = + [ QuizOptions.MULTIPLE_CHOICE_SINGLE_ANSWER, + QuizOptions.MULTIPLE_CHOICE_MULTIPLE_ANSWERS, + QuizOptions.TRUE_OR_FALSE + ]; + + + +function Quiz(){ + Object.defineProperty(this, "quiztype", { + get: function quiztype() { + return this.quiztype; + }, + set: function quiztype(val) { + this.quiztype = val; + } + }); + + //array of lesson IDs - the quiz can be used in more than one lesson + Object.defineProperty(this, "lessonIDs", { + get: function lessonIDs(){ + return this.lessonIDs + }, + set: function lessonIDs(val){ + this.lessonIDs = val; + } + }) +};*/ + diff --git a/client/templates/course/sidebar/section/lesson/lesson.html b/client/templates/course/sidebar/section/lesson/lesson.html index fe40dcce..5835ae77 100644 --- a/client/templates/course/sidebar/section/lesson/lesson.html +++ b/client/templates/course/sidebar/section/lesson/lesson.html @@ -16,6 +16,16 @@ {{/ with }} {{/ if }} + + + {{/ if }} diff --git a/client/templates/course/sidebar/section/lesson/lesson.js b/client/templates/course/sidebar/section/lesson/lesson.js index 9e18b7f2..0d4a074a 100644 --- a/client/templates/course/sidebar/section/lesson/lesson.js +++ b/client/templates/course/sidebar/section/lesson/lesson.js @@ -2,8 +2,11 @@ Template.sectionLesson.created = function () { // Save lesson ID as instance variable this.lessonID = this.data; + AutoForm.debug(); // Subscribe to single section lesson this.subscribe('singleLesson', this.lessonID); + this.subscribe('quizzes'); + this.subscribe('questions'); }; Template.sectionLesson.helpers({ @@ -34,6 +37,17 @@ Template.sectionLesson.helpers({ var lessonObject = Lessons.findOne(lessonID); return lessonObject; + }, + + 'quizzes': function() { + var instance = Template.instance(); + + var lessonID = instance.lessonID; + + //quizzes for the lesson: + var quizzes = Quizzes.find({'lessonID': lessonID}).fetch(); + + return quizzes; } }); @@ -47,5 +61,8 @@ Template.sectionLesson.events({ // set active lesson ID reactive variable // to the value of clicked lesson activeLessonID.set(lessonID); + + //TODO[EM] temporary code + activeQuizID.set(undefined); } }); diff --git a/client/templates/course/sidebar/section/quiz/quiz.html b/client/templates/course/sidebar/section/quiz/quiz.html new file mode 100644 index 00000000..710b8e89 --- /dev/null +++ b/client/templates/course/sidebar/section/quiz/quiz.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/client/templates/course/sidebar/section/quiz/quiz.js b/client/templates/course/sidebar/section/quiz/quiz.js new file mode 100644 index 00000000..154e4266 --- /dev/null +++ b/client/templates/course/sidebar/section/quiz/quiz.js @@ -0,0 +1,18 @@ +Template.lessonQuizTemplate.events({ + 'click .sidebar-quiz-link': function(event){ + event.preventDefault(); + + var data = Template.currentData(); + var quizId = String(data._id); + activeQuizID.set(quizId); + + //TODO [EM] temporary code to toggle lesson and quiz + activeLessonID.set(undefined); + } +}); + +Template.lessonQuizTemplate.helpers({ + quiz: function(){ + return Template.currentData(); + } +}) \ No newline at end of file diff --git a/client/templates/course/sidebar/sidebar.less b/client/templates/course/sidebar/sidebar.less index 05402648..c7009284 100644 --- a/client/templates/course/sidebar/sidebar.less +++ b/client/templates/course/sidebar/sidebar.less @@ -14,3 +14,8 @@ .course-navigation { margin-top: 1.4em; } + +.course-navigation ul > li .sidebar-quiz-link{ + padding: 2px 30px; + +} diff --git a/server/methods/questions.js b/server/methods/questions.js new file mode 100644 index 00000000..4c25be91 --- /dev/null +++ b/server/methods/questions.js @@ -0,0 +1,9 @@ +Meteor.methods({ + 'addQuestion': function(question, params){ + console.log(" add question : "); + Questions.insert(question) + }, + 'removeQuestion': function(question){ + Questions.remove({"id": question.id}) + } +}) \ No newline at end of file diff --git a/server/publications/quizzes.js b/server/publications/quizzes.js new file mode 100644 index 00000000..397ec1a4 --- /dev/null +++ b/server/publications/quizzes.js @@ -0,0 +1,12 @@ +Meteor.publish('quizzes', function () { + return Quizzes.find(); +}); + +Meteor.publish('singleQuiz', function (quizID) { + return Quizzes.find({"_id": quizID}); +}); + +Meteor.publish('questions', function(){ + return Questions.find(); +}); +