diff --git a/app/client/views/studies/edit_study_designs.coffee b/app/client/views/studies/edit_study_designs.coffee index 0f5ed8b..5328eb5 100644 --- a/app/client/views/studies/edit_study_designs.coffee +++ b/app/client/views/studies/edit_study_designs.coffee @@ -1,272 +1,279 @@ listQuestionnaireIds = new ReactiveVar([]) listRecordPhysicalData = new ReactiveVar(false) remainingQuestionnaires = (design) -> qIds = design.questionnaireIds or [] qIds = _.union(qIds, (listQuestionnaireIds.get() or []) ) Questionnaires.find _id: {$nin: qIds} _bloodhound = null Template.editStudyDesignsTags.created = -> questionnaires = Questionnaires.find().fetch() questionnaires.push _id: "recordPhysicalData" title: "record physical data" id: "record physical data" _bloodhound = new Bloodhound( datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title', 'id') queryTokenizer: Bloodhound.tokenizers.whitespace local: questionnaires ) _bloodhound.initialize() Template.editStudyDesignsTags.rendered = -> elt = @$('.tags') elt.tagsinput itemValue: '_id' itemText: 'title' typeaheadjs: name: 'tags' displayKey: 'title' source: _bloodhound.ttAdapter() allowDuplicates: true visit = @data.visit elt.tagsinput('removeAll') if visit.questionnaireIds? and visit.questionnaireIds.length > 0 Questionnaires.find _id: {$in: visit.questionnaireIds} .forEach (q) -> elt.tagsinput 'add', q if visit.recordPhysicalData? and visit.recordPhysicalData elt.tagsinput 'add', _id: "recordPhysicalData" title: "record physical data" _ignoreAddEvents = true Template.editStudyDesignsTags.events = "itemAdded input, itemRemoved input": (evt) -> return if _ignoreAddEvents questionnaireIds = _.pluck $(evt.target).tagsinput('items'), '_id' recordPhysicalData = false questionnaireIds = _.filter questionnaireIds, (id) -> recordPhysicalData = true if id.valueOf() is "recordPhysicalData" id.valueOf() isnt "recordPhysicalData" Meteor.call "scheduleQuestionnairesAtVisit", @design._id, @visit._id, questionnaireIds, (error) -> throwError error if error? if @visit.recordPhysicalData isnt recordPhysicalData Meteor.call "scheduleRecordPhysicalDataAtVisit", @design._id, @visit._id, recordPhysicalData, (error) -> throwError error if error? return Template.editStudyDesigns.destroyed = -> _ignoreAddEvents = true Template.editStudyDesigns.rendered = -> _ignoreAddEvents = false Meteor.setTimeout -> $("button.accordion-toggle").first().click() , 400 Meteor.setTimeout -> _ignoreAddEvents = false , 1000 Template.editStudyDesigns.helpers allQuestionnaires: -> Questionnaires.find({}, sort: title: 1) designs: -> StudyDesigns.find studyId: @_id, sort: {createdAt: 1} #this design=design titleEO: -> design = @design value: design.title emptytext: "no title" success: (response, newVal) -> Meteor.call "updateStudyDesignTitle", design._id, newVal, (error) -> throwError error if error? return #this design=design hasRemainingQuestionnaires: -> remainingQuestionnaires(@design).count() #this design=design remainingQuestionnaires: -> remainingQuestionnaires(@design) #this design=design questionnaires: -> qIds = @design.questionnaireIds or [] qIds = _.union(qIds, (listQuestionnaireIds.get() or []) ) Questionnaires.find _id: {$in: qIds} listRecordPhysicalData: -> @design.recordPhysicalData || listRecordPhysicalData.get() #this design=design visits: -> @design.visits.sort (a, b)-> a.index - b.index prevDay = 0 #augment visits #http://stackoverflow.com/questions/13789622/accessing-parent-context-in-meteor-templates-and-template-helpers @design.visits.map (v)-> if v.day? daysBetween = v.day-prevDay _.extend v, daysBetween: daysBetween prevDay = v.day if daysBetween is 0 delete v.daysBetween v #this visit design visitQuestionnaires: -> #Questionnaires.find # _id: {$in: @visit.questionnaireIds} @visit.questionnaireIds #this visit design visitTitleEO: -> visit = @visit design = @design value: visit.title emptytext: "no title" success: (response, newVal) -> dv = design.visits.find (v) -> v.title is newVal if dv? return "a visit with this title already exists." Meteor.call "changeStudyDesignVisitTitle", design._id, visit._id, newVal, (error) -> throwError error if error? return #this visit design visitDayEO: -> visit = @visit design = @design value: visit.day emptytext: "no day set" success: (response, newVal) -> Meteor.call "changeStudyDesignVisitDay", design._id, visit._id, newVal, (error) -> throwError error if error? return #this design:StudyDesign visit:StudyDesign.visit questionnaire:Questionnaire questionnaireIconClass: -> questionnaire = @questionnaire found = false if @visit.questionnaireIds _.some @visit.questionnaireIds, (qId)-> found = qId is questionnaire._id found if found return "fa-check-square-o brand-primary" else return "fa-square-o hoverOpaqueExtreme" #this design:StudyDesign visit:StudyDesign.visit physicalIconClass: -> if @visit.recordPhysicalData? and @visit.recordPhysicalData return "fa-check-square-o brand-primary" else return "fa-square-o hoverOpaqueExtreme" Template.editStudyDesigns.events "click .editable-click": (evt) -> evt.preventDefault() evt.stopPropagation() "click #createStudyDesign": (evt) -> Meteor.call "createStudyDesign", @_id, (error, studyDesignId) -> throwError error if error? + "click .copyDesign": (evt) -> + evt.preventDefault() + Meteor.call "copyStudyDesign", @design._id, (error, studyDesignId) -> + if error? + throwError error + return false + "click .removeDesign": (evt) -> evt.preventDefault() designId = @design._id swal { title: 'Are you sure?' text: 'Do you really want to delete this design?' type: 'warning' showCancelButton: true confirmButtonText: 'Yes' closeOnConfirm: false }, -> Meteor.call "removeStudyDesign", designId, (error) -> if error? throwError error else swal.close() return return false "click #addVisit": (evt) -> evt.preventDefault() Meteor.call "addStudyDesignVisit", @design._id, (error) -> throwError error if error? "click .listQuestionnaire": (evt) -> evt.preventDefault() questionnaireId = $(evt.target).data("id") qIds = listQuestionnaireIds.get() or [] qIds.push questionnaireId listQuestionnaireIds.set qIds "click .listRecordPhysicalData": (evt) -> evt.preventDefault() listRecordPhysicalData.set !listRecordPhysicalData.get() #"click .toggleQuestionnaireAtVisit": (evt) -> # evt.preventDefault() # doSchedule = not $(evt.target).hasClass('fa-check-square-o') #isChecked # questionnaireIds = @visit.questionnaireIds || [] # questionnaire = @questionnaire # if doSchedule # questionnaireIds.push questionnaire._id # $("input.tags[data-visit-id=#{@visit._id}]").tagsinput('add', questionnaire) # else # questionnaireIds = _.filter questionnaireIds, (qId)-> # qId isnt questionnaire._id # $("input.tags[data-visit-id=#{@visit._id}]").tagsinput('remove', questionnaire) # Meteor.call "scheduleQuestionnairesAtVisit", @design._id, @visit._id, questionnaireIds, (error) -> # throwError error if error? #"click .toggleRecordPhysicalDataAtVisit": (evt) -> # evt.preventDefault() # Meteor.call "scheduleRecordPhysicalDataAtVisit", @design._id, @visit._id, !@visit.recordPhysicalData, (error) -> # throwError error if error? "click .moveUp": (evt) -> Meteor.call "moveStudyDesignVisit", @design._id, @visit._id, true, (error) -> throwError error if error? "click .moveDown": (evt) -> Meteor.call "moveStudyDesignVisit", @design._id, @visit._id, false, (error) -> throwError error if error? "click .removeVisit": (evt) -> evt.preventDefault() designId = @design._id visitId = @visit._id swal { title: 'Are you sure?' text: 'Do you really want to delete this visit?' type: 'warning' showCancelButton: true confirmButtonText: 'Yes' closeOnConfirm: false }, -> Meteor.call "removeStudyDesignVisit", designId, visitId, (error) -> if error? throwError error else swal.close() return return false diff --git a/app/client/views/studies/edit_study_designs.html b/app/client/views/studies/edit_study_designs.html index 36565eb..dde1075 100644 --- a/app/client/views/studies/edit_study_designs.html +++ b/app/client/views/studies/edit_study_designs.html @@ -1,137 +1,140 @@ diff --git a/app/lib/collections/studie_designs.coffee b/app/lib/collections/studie_designs.coffee index 1c05bc8..b82f483 100644 --- a/app/lib/collections/studie_designs.coffee +++ b/app/lib/collections/studie_designs.coffee @@ -1,370 +1,385 @@ @StudyDesigns = new Meteor.Collection("study_designs") StudyDesigns.before.insert BeforeInsertTimestampHook StudyDesigns.before.update BeforeUpdateTimestampHook schema = 'title': type: String 'studyId': type: String 'creatorId': type: String 'visits': type: [Object] optional: true 'visits.$._id': type: String 'visits.$.title': type: String 'visits.$.index': type: Number 'visits.$.day': type: Number optional: true 'visits.$.questionnaireIds': type: [String] optional: true 'visits.$.recordPhysicalData': type: Boolean optional: true 'questionnaireIds': type: [String] optional: true 'recordPhysicalData': type: Boolean optional: true 'updatedAt': type: Number optional: true 'createdAt': type: Number optional: true StudyDesigns.attachSchema new SimpleSchema(schema) Meteor.methods "createStudyDesign": (studyId) -> checkIfAdmin() count = StudyDesigns.find( studyId: studyId ).count() _id = StudyDesigns.insert title: "design #{count+1}" studyId: studyId creatorId: Meteor.userId() visits: [ _id: new Meteor.Collection.ObjectID()._str day: 0 index: 0 title: "visit 1" ] _id "updateStudyDesignTitle": (studyDesignId, title) -> checkIfAdmin() check title, String studyDesign = StudyDesigns.findOne studyDesignId throw new Meteor.Error(403, "studyDesign not found.") unless studyDesign? StudyDesigns.update studyDesignId, $set: title: title return + "copyStudyDesign": (studyDesignId) -> + checkIfAdmin() + check studyDesignId, String + + design = StudyDesigns.findOne studyDesignId + throw new Meteor.Error(400, "studyDesign (#{studyDesignId}) not found") unless design? + study = Studies.findOne design.studyId + throw new Meteor.Error(400, "study (#{design.studyDesignId}) not found") unless study? + + delete design._id + design.title += " copy" + design.creatorId = Meteor.userId() + + StudyDesigns.insert design + "removeStudyDesign": (studyDesignId) -> checkIfAdmin() check studyDesignId, String design = StudyDesigns.findOne studyDesignId throw new Meteor.Error(400, "removeStudyDesign: studyDesign (#{studyDesignId}) not found") unless design? #check patients patientIds = Patients.find studyDesignId: design._id .map (patient) -> patient.id if patientIds.length > 0 throw new Meteor.Error(500, "Can't remove study design because these patients are mapped to it: #{patientIds.join(", ")}") error = null _.some design.visits, (visit) -> next = true try Meteor.call "removeStudyDesignVisit", design._id, visit._id catch e error = e next = false next if error? throw error StudyDesigns.remove design._id return "addStudyDesignVisit": (studyDesignId) -> checkIfAdmin() check studyDesignId, String design = StudyDesigns.findOne studyDesignId throw new Meteor.Error(500, "StudyDesign #{studyDesignId} not found!") unless design? index = design.visits.length title = "visit #{index+1}" visit = _id: new Meteor.Collection.ObjectID()._str title: title index: index StudyDesigns.update studyDesignId, $push: visits: visit "changeStudyDesignVisitTitle": (studyDesignId, visitId, title) -> checkIfAdmin() check studyDesignId, String check visitId, String check title, String n = StudyDesigns.update _id: studyDesignId 'visits._id': visitId , $set: 'visits.$.title': title throw new Meteor.Error(500, "changeStudyVisitTitle: no StudyDesign.visit to update found") unless n > 0 #update existing visits Visits.update designVisitId: visitId , $set: title: title , multi: true return "changeStudyDesignVisitDay": (studyDesignId, visitId, day) -> checkIfAdmin() check studyDesignId, String check visitId, String day = parseInt(day) check day, Number day = null if isNaN(day) n = StudyDesigns.update _id: studyDesignId 'visits._id': visitId , $set: 'visits.$.day': day throw new Meteor.Error(500, "changeStudyVisitTitle: no StudyDesign.visit to update found") unless n > 0 #update existing visits Visits.update designVisitId: visitId , $set: day: day , multi: true return "scheduleQuestionnairesAtVisit": (studyDesignId, visitId, questionnaireIds) -> checkIfAdmin() check studyDesignId, String check visitId, String check questionnaireIds, [String] n = StudyDesigns.update _id: studyDesignId 'visits._id': visitId , $set: 'visits.$.questionnaireIds': questionnaireIds throw new Meteor.Error(500, "scheduleQuestionnaireAtVisit: no StudyDesign with that visit found") unless n > 0 #update existing visits Visits.find designVisitId: visitId .forEach (visit) -> addedQuestionnaireIds = _.difference questionnaireIds, visit.questionnaireIds if addedQuestionnaireIds.length > 0 #console.log "pushing:" #console.log addedQuestionnaireIds Visits.update visit._id, $pushAll: questionnaireIds: addedQuestionnaireIds removedQuestionnaireIds = _.difference visit.questionnaireIds, questionnaireIds removeQuestionnaireIds = removedQuestionnaireIds.filter (rQuestId) -> rQuestionIds = Questions.find( questionnaireId: rQuestId ).map( (q) -> q._id ) c = Answers.find( visitId: visit._id questionId: {$in: rQuestionIds} ).count() if c > 0 return false return true if removeQuestionnaireIds.length > 0 #console.log "pulling:" #console.log removeQuestionnaireIds Visits.update visit._id, $pullAll: questionnaireIds: removeQuestionnaireIds updateQuestionnaireIdsOfStudyDesign(studyDesignId) return "scheduleRecordPhysicalDataAtVisit": (studyDesignId, visitId, doSchedule) -> checkIfAdmin() check visitId, String check studyDesignId, String n = StudyDesigns.update _id: studyDesignId 'visits._id': visitId , $set: 'visits.$.recordPhysicalData': doSchedule throw new Meteor.Error(500, "scheduleRecordPhysicalDataAtVisit: no StudyDesign with that visit found") unless n > 0 updateRecordPhysicalDataOfStudyDesign(studyDesignId) #update existing visits Visits.find designVisitId: visitId .forEach (visit) -> if doSchedule Visits.update visit._id, $set: recordPhysicalData: true else Visits.update visit._id, $set: recordPhysicalData: false return "moveStudyDesignVisit": (studyDesignId, visitId, up) -> checkIfAdmin() check visitId, String check studyDesignId, String design = StudyDesigns.findOne studyDesignId throw new Meteor.Error(500, "removeStudyDesignVisit: studyDesign not found") unless design? visit = _.find design.visits, (v) -> v._id is visitId throw new Meteor.Error(500, "removeStudyDesignVisit: visit not found") unless visit? move = -1 move = 1 if !up return if visit.index is 0 and move is -1 return if visit.index+1 >= design.visits.length and move is 1 StudyDesigns.update _id: studyDesignId 'visits.index': visit.index+move , $inc: 'visits.$.index': -move StudyDesigns.update _id: studyDesignId 'visits._id': visitId , $inc: 'visits.$.index': move #update existing visits designVisitIds = design.visits.map (designVisit) -> designVisit._id Visits.update designVisitId: { $in: designVisitIds } index: visit.index+move , $inc: index: -move , multi: true Visits.update designVisitId: visitId , $inc: index: move , multi: true return "removeStudyDesignVisit": (studyDesignId, visitId) -> checkIfAdmin() check visitId, String check studyDesignId, String design = StudyDesigns.findOne _id: studyDesignId throw new Meteor.Error(500, "removeStudyDesignVisit: studyDesign not found") unless design? visit = _.find design.visits, (v) -> v._id is visitId throw new Meteor.Error(500, "removeStudyDesignVisit: visit not found") unless visit? #check existing visits visits = Visits.find designVisitId: visitId .fetch() foundData = _.some visits, (visit) -> questionIds = Questions.find questionnaireId: $in: visit.questionnaireIds .map( (q) -> q._id ) c1 = Answers.find visitId: visit._id questionId: {$in: questionIds} .count() if c1 > 0 console.log "the following visit of the template has data attached:" console.log visit return true return false - throw new Meteor.Error(500, "The visit is used by at least one patient and has data attached to it. Please consult your system operator for further information.") if foundData + throw new Meteor.Error(500, "The visit is used by at least one patient, has data attached to it and can therefore not be deleted. Please consider using a copy of this design and assign it to new patients.") if foundData #remove existing visits Visits.remove designVisitId: visitId StudyDesigns.update studyDesignId, $pull: {visits: {_id: visitId}} #TODO normalize visits into it's own collection #to avoid stuff like this index = visit.index+1 loop n = StudyDesigns.update _id: studyDesignId 'visits.index': index , $inc: {'visits.$.index': -1} index += 1 break if n is 0 updateQuestionnaireIdsOfStudyDesign(studyDesignId) updateRecordPhysicalDataOfStudyDesign(studyDesignId) return updateQuestionnaireIdsOfStudyDesign = (studyDesignId) -> design = StudyDesigns.findOne studyDesignId throw new Meteor.Error(500, "updateQuestionnaireIdsOfStudyDesign: studyDesign not found") unless design? questionnaireIds = [] design.visits.forEach (visit) -> if visit.questionnaireIds? and visit.questionnaireIds.length > 0 questionnaireIds = _.union questionnaireIds, visit.questionnaireIds #questionnaireIds from design.visits and real visits may differt in case a questionnaire, for which #data was collected already, got removed from the design.visit #here we search for other questionnaireIds Visits.find( designVisitId: visit._id ).forEach (v) -> questionnaireIds = _.union questionnaireIds, v.questionnaireIds StudyDesigns.update studyDesignId, $set: questionnaireIds: questionnaireIds return updateRecordPhysicalDataOfStudyDesign = (studyDesignId) -> design = StudyDesigns.findOne studyDesignId throw new Meteor.Error(500, "updateRecordPhysicalDataOfStudyDesign: studyDesign not found") unless design? recordPhysicalData = false _.some design.visits, (visit) -> if visit.recordPhysicalData? recordPhysicalData = visit.recordPhysicalData recordPhysicalData StudyDesigns.update studyDesignId, $set: recordPhysicalData: recordPhysicalData return