diff --git a/app/client/views/questionnaires/questionnaire_wizzard.coffee b/app/client/views/questionnaires/questionnaire_wizzard.coffee index 5269b8a..f84bdf7 100644 --- a/app/client/views/questionnaires/questionnaire_wizzard.coffee +++ b/app/client/views/questionnaires/questionnaire_wizzard.coffee @@ -1,364 +1,364 @@ _numQuestions = new ReactiveVar(0) _numPages = new ReactiveVar(0) _questionIdsForPage = new ReactiveVar({}) _pageIndex = new ReactiveVar(0) _numFormsToSubmit = 0 _readonly = new ReactiveVar(false) _questionnaire = new ReactiveVar(null) _nextQuestionnaire = null _preview = new ReactiveVar(false) isAFormDirty = -> if _readonly.get() or _preview.get() return false isDirty = false $("form").each () -> return if isDirty e = $(@)[0] dirty = formIsDirty(e) isDirty = dirty if dirty isDirty _goto = null submitAllForms = (goto) -> if _readonly.get() or _preview.get() throw new Error("Can't submitAllForms because _readonly == true") _goto = goto numFormsToSubmit = 0 $("form").each () -> e = $(@) classes = e.attr('class') if classes? and classes.indexOf('question') > -1 numFormsToSubmit += 1 _numFormsToSubmit = numFormsToSubmit $("form").each () -> e = $(@) classes = e.attr('class') if classes? and classes.indexOf('question') > -1 e.submit() formSubmitted = -> if (_numFormsToSubmit -= 1) <= 0 if _goto is 'nextPage' nextPage() else if _goto is 'previousPage' previousPage() else if _goto is 'close' Modal.hide('questionnaireWizzard') else if _goto.pageIndex? _pageIndex.set _goto.pageIndex close = -> if isAFormDirty() swal { title: 'Unsaved Changes' text: "Do you want to save the changes on this page?" type: 'warning' showCancelButton: true confirmButtonText: 'Save and exit' cancelButtonText: "Exit without saving" }, (save) -> if save submitAllForms('close') else Modal.hide('questionnaireWizzard') else Modal.hide('questionnaireWizzard') nextPage = -> if _pageIndex.get() is _numPages.get()-1 if _nextQuestionnaire? _pageIndex.set 0 _questionnaire.set _nextQuestionnaire else Modal.hide('questionnaireWizzard') else _pageIndex.set _pageIndex.get()+1 previousPage = -> index = _pageIndex.get() index -= 1 if index > 0 _pageIndex.set index autoformHooks = onSubmit: (insertDoc, updateDoc, currentDoc) -> return if _preview.get() insertDoc.visitId = currentDoc.visitId insertDoc.questionId = currentDoc.questionId insertDoc._id = currentDoc._id if currentDoc._id? #console.log "submit questionAutoform" #console.log insertDoc if insertDoc.value? and (!currentDoc.value? or (currentDoc.value? and currentDoc.value isnt insertDoc.value)) Meteor.call "upsertAnswer", insertDoc, (error) -> throwError error if error? formSubmitted() @done() false Template.questionnaireWizzard.created = -> _questionnaire.set @data.questionnaire delete @data.questionnaire if @data.readonly _readonly.set true else _readonly.set false if @data.preview _preview.set true else _preview.set false #close on escape key press $(document).on('keyup.wizzard', (e)-> e.stopPropagation() if e.keyCode is 27 close() return ) self = @ @autorun -> self.subscribe("questionsForQuestionnaire", _questionnaire.get()._id) #get manage nextQuestionnaire @autorun -> return if _preview.get() data = Template.currentData() validatedQuestionnaires = data.visit.validatedQuestionnaires i = 0 index = null while i < validatedQuestionnaires.length-1 && index is null q = validatedQuestionnaires[i] if q._id is _questionnaire.get()._id index = i i += 1 if index? and index < validatedQuestionnaires.length-1 _nextQuestionnaire = validatedQuestionnaires[index+1] else _nextQuestionnaire = null #collect autoformIds, count pages @autorun -> count = 0 page = 0 questionIdsForPage = {} didBreakPage = false autoformIds = [] Questions.find questionnaireId: _questionnaire.get()._id , sort: {index: 1} .forEach (q) -> if q.type isnt "description" and q._id isnt "table" and q._id isnt "table_polar" autoformIds.push q._id count += 1 if questionIdsForPage[page]? questionIdsForPage[page].push q._id else questionIdsForPage[page] = [q._id] didBreakPage = false if q.break page += 1 didBreakPage = true page -= 1 if didBreakPage _questionIdsForPage.set questionIdsForPage _numQuestions.set count _numPages.set page+1 _pageIndex.set 0 AutoForm.addHooks(autoformIds, autoformHooks) Template.questionnaireWizzard.destroyed = -> $(document).unbind('keyup.wizzard') Template.questionnaireWizzard.helpers templateGestures: 'swipeleft div': (evt, templateInstance) -> nextQuestion() 'swiperight div': (evt, templateInstance) -> previousQuestion() title: -> _questionnaire.get().title questionsForPage: -> questionIdsForPage = _questionIdsForPage.get()[_pageIndex.get()] Questions.find questionnaireId: _questionnaire.get()._id _id: {$in: questionIdsForPage} , sort: {index: 1} questionnaire: -> _questionnaire.get() answerForQuestion: (visitId, questionId) -> return if _preview.get() Answers.findOne visitId: visitId questionId: questionId readonly: -> _readonly.get() formType: -> if _readonly.get() "disabled" else "normal" answerFormSchema: -> schema = _id: type: String optional: true visitId: type: String optional: true questionId: type: String optional: true value: @question.getSchemaDict() new SimpleSchema(schema) doc: -> return if _preview.get() @answer or visitId: @visit._id questionId: @question._id pages: -> answers = {} questionIds = Questions.find questionnaireId: _questionnaire.get()._id .map (question) -> question._id if !_preview.get() Answers.find visitId: @visit._id questionId: {$in: questionIds} .forEach (answer) -> answers[answer.questionId] = answer activeIndex = _pageIndex.get() questionIdsForPage = _questionIdsForPage.get() pages = [] for i in [0.._numPages.get()-1] css = "" allQuestionsAnsweredInPage = true someQuestionsAnsweredInPage = false Questions.find questionnaireId: _questionnaire.get()._id _id: {$in: questionIdsForPage[i]} .forEach (question) -> return if question.type is "description" answer = answers[question._id] if question.type is "table" or question.type is "table_polar" if !answer? or answer.value.length < question.subquestions.length allQuestionsAnsweredInPage = false if answer? and answer.value.length > 0 someQuestionsAnsweredInPage = true else if !answer? allQuestionsAnsweredInPage = false if allQuestionsAnsweredInPage css = "answered" else if someQuestionsAnsweredInPage css = "answeredPartly" if i is activeIndex css += " active" pages[i] = index: i+1 css: css pages isOnFirstPage: -> _pageIndex.get() is 0 isOnLastPageOfLastQuestionnaire: -> _pageIndex.get() is _numPages.get()-1 and (_preview.get() or _nextQuestionnaire is null) Template.questionnaireWizzard.events "click #next": (evt, tmpl) -> if _readonly.get() or _preview.get() nextPage() else submitAllForms('nextPage') false "click #back": (evt, tmpl) -> if _readonly.get() or _preview.get() previousPage() else submitAllForms('previousPage') false "click .jumpToPage": (evt) -> pageIndex = @index-1 if isAFormDirty() swal { title: 'Unsaved Changes' text: "Do you want to save the changes on this page?" type: 'warning' showCancelButton: true confirmButtonText: 'Save' cancelButtonText: "Don't save" }, (save) -> if save submitAllForms(pageIndex: pageIndex) else _pageIndex.set pageIndex else _pageIndex.set pageIndex false "click #close": (evt) -> close() false "submit .questionForm": (evt) -> #table and table_polar if _readonly.get() or _preview.get() return evt.preventDefault() evt.stopPropagation() if @question.type is "description" formSubmitted() return answer = visitId: @visit._id questionId: @question._id value: [] _id: @answer._id if @answer? for subquestion in @question.subquestions inputs = $(evt.target).find("input[data-subquestion_code=#{subquestion.code}]:checked") if @question.selectionMode is "multi" - variables = [] + values = [] inputs.each -> input = $(@) - variables.push input.data('choice_variable').toString() - if variables.length > 0 + values.push input.data('choice_value').toString() + if values.length > 0 answer.value.push code: subquestion.code - value: variables + value: values else #if @question.selectionMode is "single" if inputs.length > 1 throw new Meteor.Error('error when processing the values: single selection got multiple values.') else if inputs.length is 1 value = inputs.first().data('choice_value').toString() answer.value.push code: subquestion.code value: value if answer.value.length > 0 Meteor.call "upsertAnswer", answer, (error) -> throwError error if error? formSubmitted() else formSubmitted() false diff --git a/app/client/views/questions/question_table_input.coffee b/app/client/views/questions/question_table_input.coffee index 4af69d6..59aab8b 100644 --- a/app/client/views/questions/question_table_input.coffee +++ b/app/client/views/questions/question_table_input.coffee @@ -1,35 +1,32 @@ Template.questionTableInput.rendered = -> #we calculate checked here because we need to set it to defaultChecked of the element and as attribute (a helper would come to late) selectionMode = @data.question.selectionMode code = @data.subquestion.code - if selectionMode is 'multi' - choiceVariable = @data.choice.variable - else #if selectionMode is 'single' - choiceValue = @data.choice.value + choiceValue = @data.choice.value @data.answer? and @data.answer.value? and checked = (_.find @data.answer.value, (subanswer) -> subanswer.code is code and ((selectionMode is "multi" and - subanswer.value.indexOf(choiceVariable) > -1 ) or + subanswer.value.indexOf(choiceValue) > -1 ) or (selectionMode is "single" and subanswer.value is choiceValue) ) )? if checked e = @$('input') e.prop('checked', checked) e[0].defaultChecked = checked return Template.questionTableInput.helpers type: -> if @question.selectionMode is "multi" "checkbox" else #if @question.selectionMode is "single" "radio" disabled: -> if @readonly "disabled" else "" diff --git a/app/client/views/questions/question_table_input.html b/app/client/views/questions/question_table_input.html index 2e2f2d6..abf6b7f 100644 --- a/app/client/views/questions/question_table_input.html +++ b/app/client/views/questions/question_table_input.html @@ -1,3 +1,3 @@ diff --git a/app/lib/collection_helpers/export.coffee b/app/lib/collection_helpers/export.coffee index 75900a2..61940af 100644 --- a/app/lib/collection_helpers/export.coffee +++ b/app/lib/collection_helpers/export.coffee @@ -1,163 +1,163 @@ class @Export @columnHeaders: (selection) -> cols = [] if selection.system? Object.keys(selection.system).forEach (entity) -> variables = selection.system[entity] variables.forEach (variable) -> cols.push title: "#{entity}.#{variable}" if selection.questionnaires? selection.questionnaires.forEach (questionnaire) -> questionnaire = Questionnaires.findOne questionnaire._id Questions.find( questionnaireId: questionnaire._id , sort: {index: 1} ).forEach (question) -> #console.log question if !question.code? question.code = "question code missing (index: #{question.index} in questionnaire: #{questionnaire.title})" if question.type is 'table' or question.type is 'table_polar' if question.subquestions? question.subquestions.forEach (subquestion) -> if question.selectionMode is "multi" question.choices.forEach (choice) -> cols.push - title: "#{subquestion.code}-#{choice.variable}" + title: "#{subquestion.code}-#{choice.value}" else #if question.selectionMode is "single" cols.push title: "#{subquestion.code}" else cols.push title: "#{question.code} missing subquestions (index: #{question.index} in questionnaire: #{questionnaire.title})" else if question.type is 'multipleChoice' if question.choices? if question.selectionMode is "multi" question.choices.forEach (choice) -> cols.push - title: "#{question.code}-#{choice.variable}" + title: "#{question.code}-#{choice.value}" else# if question.selectionMode is "single" cols.push title: "#{question.code}" else cols.push title: "#{question.code} missing subquestions (index: #{question.index} in questionnaire: #{questionnaire.title})" else cols.push title: "#{question.code}" cols @rows: (selection) -> rows = [] selection.designs.forEach (design) -> studyDesign = StudyDesigns.findOne _id: design._id visitTemplates = studyDesign.visits.filter (visit) -> design.visitIds.indexOf(visit._id) > -1 patients = Patients.find( _id: {$in: design.patientIds} ).forEach (patient) -> studyDesign = StudyDesigns.findOne patient.studyDesignId study = Studies.findOne studyDesign.studyId visitTemplates.forEach (visitTemplate) -> visit = Visits.findOne patientId: patient._id designVisitId: visitTemplate._id rows.push study: study studyDesign: studyDesign patient: patient visitTemplate: visitTemplate visit: visit rows @columns: (selection, row) -> cols = [] empty = 'NA' if Meteor.isClient empty = 'only in CSV' error = 'ERROR' if selection.system? Object.keys(selection.system).forEach (entity) -> variables = selection.system[entity] variables.forEach (variable) -> if row[entity]? if typeof row[entity][variable] is 'function' val = row[entity][variable]() else val = row[entity][variable] else val = empty#"#{entity}.#{variable}" cols.push "#{val}" if selection.questionnaires? selection.questionnaires.forEach (questionnaire) -> questionnaire = Questionnaires.findOne questionnaire._id Questions.find( questionnaireId: questionnaire._id , sort: {index: 1} ).forEach (question) -> answer = Answers.findOne questionId: question._id visitId: row.visit._id if row.visit? if question.type is 'table' or question.type is 'table_polar' if question.subquestions? and question.subquestions.length > 0 question.subquestions.forEach (subquestion) -> if answer? and answer.value.length > 0 subanswer = answer.value.find (v) -> v.code is subquestion.code if question.selectionMode is "multi" question.choices.forEach (choice) -> if subanswer? - if subanswer.value.indexOf(choice.variable) > -1 + if subanswer.value.indexOf(choice.value) > -1 cols.push 1 else cols.push 0 else #no subanswer for this question cols.push empty else #if question.selectionMode is "single" if subanswer? and subanswer.value? cols.push subanswer.value else #no subanswer for this subquestion cols.push empty else #no answer for this question if question.selectionMode is "multi" question.choices.forEach (choice) -> cols.push empty else #if question.selectionMode is "single" cols.push empty else #missing subquestions if Meteor.isServer cols.push error else cols.push "#{question.code} missing subquestions (index: #{question.index} in questionnaire: #{questionnaire.title})" else if question.type is 'multipleChoice' if question.choices? if question.selectionMode is "multi" question.choices.forEach (choice) -> if answer? - if answer.value.indexOf(choice.variable) > -1 + if answer.value.indexOf(choice.value) > -1 cols.push 1 else cols.push 0 else cols.push empty else #if question.selectionMode is "single" if answer? cols.push answer.value else cols.push empty else if Meteor.isServer cols.push error else cols.push "#{question.code} missing subquestions (index: #{question.index} in questionnaire: #{questionnaire.title})" else #all other question types if answer? cols.push answer.value else cols.push empty cols diff --git a/app/lib/collections/questions.coffee b/app/lib/collections/questions.coffee index 960749b..36b02cf 100644 --- a/app/lib/collections/questions.coffee +++ b/app/lib/collections/questions.coffee @@ -1,329 +1,318 @@ class @Question constructor: (doc) -> _.extend this, doc getSchemaDict: -> s = _.pickDeep @, 'type', 'label', 'optional', 'min', 'max', 'decimal', 'options', 'options.label', 'options.value' switch @type when "text" s.type = String s.autoform = type: "textarea" when "number" s.type = Number when "boolean" s.type = Boolean s.autoform = type: "boolean-radios" when "date" s.type = Date s.autoform = type: "bootstrap-datepicker" when "dateTime" s.type = Date s.autoform = type: "bootstrap-datetimepicker" when "multipleChoice" s.autoform = options: @choices if @selectionMode is "multi" s.type = [String] - #sanitize for autoform: variable to value - s.autoform.options.forEach (option) -> - if option.variable? - option.value = option.variable - delete option.variable if @orientation is 'inline' s.autoform.type = "select-checkbox-inline" else #if @orientation is 'vertical' s.autoform.type = "select-checkbox" else #if @selectionMode is "single" s.type = String if @orientation is 'inline' s.autoform.type = "select-radio-inline" else #if @orientation is 'vertical' s.autoform.type = "select-radio" when "description" s.type = String s.label = ' ' s.autoform = type: "description" delete s.options s getMetaSchemaDict: -> schema = {} noWhitespaceRegex = /^\S*$/ #don't match if contains whitespace if @type isnt "description" _.extend schema, code: label: "Code" type: String optional: false regEx: noWhitespaceRegex optional: label: "Optional" type: Boolean label: label: if (@type is "table" or @type is 'table_polar') then "Title" else "Question" type: String optional: (@type is "table" or @type is 'table_polar') autoform: type: "textarea" _.extend schema, type: label: "Type" type: String autoform: type: "select" options: -> [ {label: "Text", value: "text"}, {label: "Number", value: "number"}, {label: "Boolean", value: "boolean"}, {label: "Date", value: "date"}, {label: "Date & Time", value: "dateTime"}, {label: "Multiple Choice", value: "multipleChoice"}, {label: "Table Multiple Choice", value: "table"}, {label: "Table Polar", value: "table_polar"}, {label: "Description (no question)", value: "description"}, ] break: label: "insert pagebreak after this item" type: Boolean if @type is "multipleChoice" or @type is "table" or @type is "table_polar" _.extend schema, selectionMode: label: "Mode" type: String autoform: type: "select-radio-inline" options: [ label: "single selection (radios)" value: "single" , label: "multiple selections (checkboxes)" value: "multi" ] if @type is "multipleChoice" _.extend schema, orientation: label: "Orientation" type: String autoform: type: "select-radio-inline" options: [ label: "inline" value: "inline" , label: "vertical" value: "vertical" ] if @type is "description" _.extend schema, label: label: "Text (markdown)" type: String autoform: type: "textarea" rows: 10 if @type is "number" _.extend schema, min: type: Number optional: true decimal: true max: type: Number optional: true decimal: true decimal: type: Boolean if @type is "multipleChoice" or @type is "table" or @type is "table_polar" _.extend schema, choices: type: [Object] label: "Choices" minCount: 1 'choices.$.label': type: String optional: true + 'choices.$.value': + type: String + regEx: noWhitespaceRegex if @selectionMode is "multi" - _.extend schema, - 'choices.$.variable': - type: String - regEx: noWhitespaceRegex - custom: -> - #console.log "> #{@value} #{@key} <" - #console.log "----" - digitRegex = /(\d+)/g - matches = digitRegex.exec(@key) - if matches.length > 0 - index = parseInt(matches[0])-1 - while index >= 0 - v = @field("choices.#{index}.variable").value - #console.log v - if v? and v.valueOf() is @value.valueOf() - return "notUnique" - index -= 1 - return - else #if @selectionMode is "single" - _.extend schema, - 'choices.$.value': - type: String - regEx: noWhitespaceRegex + schema['choices.$.value'].custom = -> + #console.log "> #{@value} #{@key} <" + #console.log "----" + digitRegex = /(\d+)/g + matches = digitRegex.exec(@key) + if matches.length > 0 + index = parseInt(matches[0])-1 + while index >= 0 + v = @field("choices.#{index}.value").value + #console.log v + if v? and v.valueOf() is @value.valueOf() + return "notUnique" + index -= 1 + return if @type is "table" _.extend schema, subquestions: type: [Object] label: "Subquestions" minCount: 1 'subquestions.$.label': type: String autoform: type: "textarea" 'subquestions.$.code': type: String regEx: noWhitespaceRegex custom: -> digitRegex = /(\d+)/g matches = digitRegex.exec(@key) if matches.length > 0 index = parseInt(matches[0])-1 while index >= 0 v = @field("subquestions.#{index}.code").value if v? and v.valueOf() is @value.valueOf() return "notUnique" index -= 1 return if @type is "table_polar" _.extend schema, subquestions: type: [Object] label: "Subquestions" minCount: 1 'subquestions.$.minLabel': label: "min label" type: String optional: true 'subquestions.$.maxLabel': label: "max label" type: String optional: true 'subquestions.$.code': type: String regEx: noWhitespaceRegex custom: -> digitRegex = /(\d+)/g matches = digitRegex.exec(@key) if matches.length > 0 index = parseInt(matches[0])-1 while index >= 0 v = @field("subquestions.#{index}.code").value if v? and v.valueOf() is @value.valueOf() return "notUnique" index -= 1 return schema @Questions = new Meteor.Collection("questions", transform: (doc) -> new Question(doc) ) Questions.before.insert BeforeInsertTimestampHook Questions.before.update BeforeUpdateTimestampHook #TODO check if allowed #TODO check consistency Questions.allow insert: (userId, doc) -> true update: (userId, doc, fieldNames, modifier) -> true remove: (userId, doc) -> true Meteor.methods insertQuestion: (question) -> check(question.questionnaireId, String) questionnaire = Questionnaires.findOne _id: question.questionnaireId check(question.type, String) if question.type isnt 'table' and question.type isnt 'table_polar' check(question.label, String) numQuestions = Questions.find questionnaireId: questionnaire._id .count() nextIndex = numQuestions+1 if (question.index? and question.index > nextIndex) or !question.index? question.index = nextIndex #TODO filter question atters _id = Questions.insert question _id removeQuestion: (_id) -> check(_id, String) question = Questions.findOne _id questionnaire = Questionnaires.findOne _id: question.questionnaireId Questions.remove _id Questions.update questionnaireId: questionnaire._id index: { $gt: question.index } , $inc: { index: -1 } , multi: true moveQuestion: (questionnaireId, oldIndex, newIndex) -> check(questionnaireId, String) check(oldIndex, Match.Integer) check(newIndex, Match.Integer) questionnaire = Questionnaires.findOne _id: questionnaireId question = Questions.findOne questionnaireId: questionnaireId index: oldIndex throw new Meteor.Error(403, "question with index #{oldIndex} not found.") unless question? Questions.update questionnaireId: questionnaireId index: { $gt: oldIndex } , $inc: { index: -1 } , multi: true Questions.update questionnaireId: questionnaireId index: { $gte: newIndex } , $inc: { index: 1 } , multi: true Questions.update _id: question._id , $set: { index: newIndex} null diff --git a/app/server/mongo_migrations/migrations.coffee b/app/server/mongo_migrations/migrations.coffee index 29fe005..e94c646 100644 --- a/app/server/mongo_migrations/migrations.coffee +++ b/app/server/mongo_migrations/migrations.coffee @@ -1,239 +1,257 @@ util = Npm.require('util') Migrations.add version: 1 up: -> console.log "delete everything except questionnaires & questions" Answers.remove({}) Patients.remove({}) Studies.remove({}) StudyDesigns.remove({}) Visits.remove({}) Migrations.add version: 2 up: -> console.log "sanitize: choices variables & null values; multiplechoice modes" Questions.find().forEach (question) -> if question.choices? question.choices = question.choices.filter (choice) -> choice? question.choices.forEach (choice) -> choice.variable = choice.value #console.log question.choices Questions.update question._id, $set: choices: question.choices if question.type is 'multipleChoice' if !question.mode? question.mode = 'radio' #console.log question Questions.update question._id, $set: mode: question.mode Migrations.add version: 3 up: -> console.log "sanitize whitespaces in question.code, question.choices.variable and question.subquestions.code" Questionnaires.find().forEach (questionnaire) -> console.log questionnaire.title Questions.find( questionnaireId: questionnaire._id ).forEach (question) -> code = question.code if !code code = questionnaire.title+'_'+question.index+1 code = code.toString() code = code.replace(/\s/g, '_') if code isnt question.code console.log "updating code #{question.code} -> #{code}" Questions.update question._id, $set: code: code if question.choices? updateChoices = false question.choices.forEach (choice) -> variable = choice.variable.toString() variable = variable.replace(/\s/g, '_') if variable.valueOf() isnt choice.variable.valueOf() console.log "updating variable #{choice.variable} -> #{variable}" choice.variable = variable updateChoices = true if updateChoices Questions.update question._id, $set: choices: question.choices if question.subquestions? updateSubquestions = false question.subquestions.forEach (subq) -> code = subq.code code = code.replace(/\s/g, '_') if code.valueOf() isnt subq.code.valueOf() console.log "updating code #{subq.code} -> #{code}" subq.code = code updateSubquestions = true if updateSubquestions Questions.update question._id, $set: subquestions: question.subquestions Migrations.add version: 4 up: -> console.log "fix visit titles" StudyDesigns.find({}).forEach (design) -> design.visits.forEach (v) -> Visits.find( designVisitId: v._id ).forEach (visit) -> if visit.title isnt v.title Visits.update visit._id, $set: title: v.title return Migrations.add version: 5 up: -> console.log "fix empty choices and subquestions" Questions.find({}).forEach (question) -> if question.choices? choices = question.choices.filter (choice) -> choice? if choices.length isnt question.choices.length Questions.update question._id, $set: choices: choices if question.subquestions? subquestions = question.subquestions.filter (subq) -> subq? if subquestions.length isnt question.subquestions.length console.log "fix subquestions" Questions.update question._id, $set: subquestions: subquestions return Migrations.add version: 6 up: -> console.log "fix question indices" Questionnaires.find().forEach (questionnaire) -> i = 1 Questions.find( questionnaireId: questionnaire._id , sort: index: 1 ).forEach (question) -> if i isnt question.index console.log "fix question index" console.log question Questions.update question._id, $set: index: i i += 1 return Migrations.add version: 7 up: -> console.log "migrate question.choices.$.value from Number to String" Questions.find().forEach (question) -> if question.choices? question.choices.forEach (choice) -> choice.variable = choice.variable.toString() choice.value = choice.value.toString() #console.log question.choices Questions.update question._id, $set: choices: question.choices Answers.find().forEach (answer) -> if typeof answer.value is 'object' updated = false answer.value.forEach (v) -> if v.checkedChoices? v.checkedChoices.forEach (cc) -> if typeof(cc.value) isnt 'string' or typeof(cc.variable) isnt 'string' updated = true cc.value = cc.value.toString() cc.variable = cc.variable.toString() if updated console.log(util.inspect(answer, {showHidden: false, depth: null})) Answers.update answer._id, $set: value: answer.value Migrations.add version: 8 up: -> console.log "init hasData for patients" Patients.find().forEach (patient) -> hasData = false Visits.find( patientId: patient._id ).forEach (v) -> c = Answers.find( visitId: v._id ).count() if c > 0 hasData = true if hasData console.log patient._id+" has data!" Patients.update patient._id, $set: hasData: true return Migrations.add version: 9 up: -> console.log "remove all answers" Answers.remove({}) return Migrations.add version: 10 up: -> console.log "remove choices.$.variable from single-selection and choices.$.value multi-selection questions" counter = 0 Questions.find().forEach (q) -> if q.type is "multipleChoice" or q.type is "table" or q.type is "table_polar" #console.log q if q.mode is 'checkbox' q.choices.forEach (c) -> delete c.value else #if q.mode is 'radio' q.choices.forEach (c) -> delete c.variable #console.log q #console.log "\n\n\n" counter += Questions.update q._id, $set: choices: q.choices console.log counter+" questions updated" return Migrations.add version: 11 up: -> console.log "rename question.mode to question.selectionMode and it's values from radio to single and checkbox to multi" c = 0 Questions.find().forEach (q) -> if q.type is "multipleChoice" or q.type is "table" or q.type is "table_polar" console.log q if q.mode is 'checkbox' q.mode = 'multi' else #if q.mode is 'radio' q.mode = 'single' q.selectionMode = q.mode delete q.mode console.log q console.log "\n\n\n" c += Questions.update q._id, $set: selectionMode: q.selectionMode Questions.update q._id, $unset: mode: 1 console.log c+" questions updated" return +Migrations.add + version: 12 + up: -> + console.log "unify choices.$.variable into choice.$.value" + counter = 0 + Questions.find().forEach (q) -> + if q.type is "multipleChoice" or q.type is "table" or q.type is "table_polar" + if q.selectionMode is 'multi' + console.log q + q.choices.forEach (c) -> + c.value = c.variable + delete c.variable + console.log q + console.log "\n\n\n" + counter += Questions.update q._id, + $set: choices: q.choices + console.log counter+" questions updated" + return Meteor.startup -> - #Migrations.migrateTo('11,rerun') + #Migrations.migrateTo('12,rerun') Migrations.migrateTo('latest')