Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F86374521
questions.coffee
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, Oct 6, 03:15
Size
21 KB
Mime Type
text/x-c++
Expires
Tue, Oct 8, 03:15 (2 d)
Engine
blob
Format
Raw Data
Handle
21410287
Attached To
R7177 epiph
questions.coffee
View Options
class
@Question
constructor:
(doc) ->
_
.
extend
this
,
doc
getModifiedPlainObject:
(modifier) ->
TempCollection =
new
Mongo
.
Collection
(
"temp"
,
connection:
null
)
TempCollection
.
insert
(
@
)
selector =
{
_id:
@_id
}
TempCollection
.
update
(
selector
,
modifier
)
simulation =
TempCollection
.
findOne
(
selector
)
TempCollection
.
remove
(
selector
)
simulation.updatedAt =
Date
.
now
()
JSON
.
parse
(
JSON
.
stringify
(
simulation
))
copy:
->
copy =
_
.
clone
@
#http://stackoverflow.com/questions/597588/how-do-you-clone-an-array-of-objects-in-javascript
if
@choices
?
copy.choices =
JSON
.
parse
(
JSON
.
stringify
(
@choices
))
if
@subquestions
?
copy.subquestions =
JSON
.
parse
(
JSON
.
stringify
(
@subquestions
))
copy
translateTo:
(lang) ->
return
if
!
lang
try
translation =
@translations
[
lang
]
try
@label =
translation
.
label
if
translation
.
label
?
try
@choices
.
forEach
(c) ->
translatedChoice =
translation
.
choices
.
find
(tc) ->
tc
.
value
is
c
.
value
if
translatedChoice
?
c.label =
translatedChoice
.
label
try
@subquestions
.
forEach
(s) ->
tSubquestion =
translation
.
subquestions
.
find
(ts) ->
ts
.
code
is
s
.
code
if
tSubquestion
?
s.label =
tSubquestion
.
label
if
tSubquestion
.
label
?
s.minLabel =
tSubquestion
.
minLabel
if
tSubquestion
.
minLabel
?
s.maxLabel =
tSubquestion
.
maxLabel
if
tSubquestion
.
maxLabel
?
return
@
getSchemaDict:
(lang) ->
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
]
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
return
s
getMetaSchemaDict:
(finalValidation)->
schema =
{}
noWhitespaceRegex =
/^\S*$/
#don't match if contains whitespace
if
@type
is
"table"
or
@type
is
"table_polar"
_
.
extend
schema
,
label:
label:
"Title"
type:
String
optional:
true
autoform:
type:
"textarea"
else
if
@type
isnt
"description"
_
.
extend
schema
,
code:
label:
"Code"
type:
String
regEx:
noWhitespaceRegex
defaultValue:
new
Mongo
.
ObjectID
().
_str
label:
label:
"Question"
type:
String
defaultValue:
"Insert label here"
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
defaultValue:
false
if
@type
isnt
"description"
_
.
extend
schema
,
optional:
label:
"Optional"
type:
Boolean
defaultValue:
true
if
@type
is
"multipleChoice"
or
@type
is
"table"
or
@type
is
"table_polar"
_
.
extend
schema
,
selectionMode:
label:
"Mode"
type:
String
defaultValue:
"single"
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
defaultValue:
"vertical"
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
defaultValue:
"Insert text here"
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
defaultValue:
false
if
@type
is
"multipleChoice"
or
@type
is
"table"
or
@type
is
"table_polar"
_
.
extend
schema
,
choices:
type:
[
Object
]
label:
"Choices"
minCount:
1
defaultValue:
[{
label:
"Insert choice here"
,
value:
"1"
}]
'choices.$.label'
:
type:
String
optional:
true
'choices.$.value'
:
type:
String
regEx:
noWhitespaceRegex
if
@selectionMode
is
"multi"
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
defaultValue:
[{
label:
"Insert question here"
,
code:
new
Mongo
.
ObjectID
().
_str
}]
'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
defaultValue:
[{
code:
new
Mongo
.
ObjectID
().
_str
}]
'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
_
.
extend
schema
,
translations:
type:
Object
blackbox:
true
optional:
true
if
finalValidation
_
.
extend
schema
,
_id:
type:
String
optional:
true
questionnaireId:
type:
String
index:
type:
Number
createdAt:
type:
Number
optional:
true
updatedAt:
type:
Number
optional:
true
return
schema
getTranslationSchemaDict:
->
schema =
{}
noWhitespaceRegex =
/^\S*$/
#don't match if contains whitespace
if
@type
is
"table"
or
@type
is
"table_polar"
_
.
extend
schema
,
label:
label:
"Title"
type:
String
optional:
true
autoform:
type:
"textarea"
else
if
@type
isnt
"description"
_
.
extend
schema
,
label:
label:
"Question"
type:
String
autoform:
type:
"textarea"
if
@type
is
"description"
_
.
extend
schema
,
label:
label:
"Text (markdown)"
type:
String
autoform:
type:
"textarea"
rows:
10
if
@type
is
"multipleChoice"
or
@type
is
"table"
or
@type
is
"table_polar"
_
.
extend
schema
,
choices:
type:
[
Object
]
label:
"Choices"
minCount:
@choices
.
length
or
1
maxCount:
@choices
.
length
or
1
'choices.$.label'
:
type:
String
optional:
true
'choices.$.value'
:
type:
String
autoform:
readonly:
true
if
@type
is
"table"
_
.
extend
schema
,
subquestions:
type:
[
Object
]
label:
"Subquestions"
minCount:
@subquestions
.
length
or
1
maxCount:
@subquestions
.
length
or
1
'subquestions.$.label'
:
type:
String
autoform:
type:
"textarea"
'subquestions.$.code'
:
type:
String
autoform:
readonly:
true
if
@type
is
"table_polar"
_
.
extend
schema
,
subquestions:
type:
[
Object
]
label:
"Subquestions"
minCount:
0
maxCount:
@subquestions
.
length
'subquestions.$.minLabel'
:
label:
"min label"
type:
String
optional:
true
'subquestions.$.maxLabel'
:
label:
"max label"
type:
String
optional:
true
'subquestions.$.code'
:
type:
String
autoform:
readonly:
true
return
schema
@Questions =
new
Meteor
.
Collection
(
"questions"
,
transform:
(doc) ->
new
Question
(
doc
)
)
Questions
.
before
.
insert
BeforeInsertTimestampHook
Questions
.
before
.
update
BeforeUpdateTimestampHook
Meteor
.
methods
insertQuestion:
(question) ->
checkIfAdmin
()
check
(
question
.
questionnaireId
,
String
)
questionnaire =
Questionnaires
.
findOne
question
.
questionnaireId
throw
new
Meteor
.
Error
(
400
,
"questionnaire #{question.questionnaireId}) not found."
)
unless
questionnaire
?
check
(
question
.
type
,
String
)
if
question
.
type
isnt
"text"
and
question
.
type
isnt
"description"
throw
new
Meteor
.
Error
(
400
,
"only text and description questions can be inserted"
)
delete
question
.
_id
delete
question
.
code
numQuestions =
Questions
.
find
(
questionnaireId:
questionnaire
.
_id
).
count
()
nextIndex =
numQuestions
+
1
if
(
question
.
index
?
and
question
.
index
>
nextIndex
)
or
!
question
.
index
?
question.index =
nextIndex
q =
new
Question
(
question
)
ss =
new
SimpleSchema
(
q
.
getMetaSchemaDict
(
true
))
ss
.
clean
(
question
)
check
(
question
,
ss
)
Questions
.
insert
question
copyQuestion:
(questionId) ->
checkIfAdmin
()
check
questionId
,
String
question =
Questions
.
findOne
questionId
throw
new
Meteor
.
Error
(
403
,
"question (#{questionId}) not found."
)
unless
question
?
questionnaire =
Questionnaires
.
findOne
question
.
questionnaireId
throw
new
Meteor
.
Error
(
400
,
"questionnaire #{question.questionnaireId}) not found."
)
unless
questionnaire
?
delete
question
.
_id
if
question
.
code
?
question
.
code
+=
":#{new Mongo.ObjectID()._str}"
if
question
.
subquestions
?
question
.
subquestions
.
forEach
(q) ->
q
.
code
+=
":#{new Mongo.ObjectID()._str}"
question.index =
Questions
.
find
(
questionnaireId:
questionnaire
.
_id
).
count
()
+
1
q =
new
Question
(
question
)
ss =
new
SimpleSchema
(
q
.
getMetaSchemaDict
(
true
))
ss
.
clean
(
question
)
check
(
question
,
ss
)
Questions
.
insert
question
updateQuestion:
(modifier, docId) ->
checkIfAdmin
()
check
(
modifier
,
Object
)
check
(
docId
,
String
)
question =
Questions
.
findOne
docId
throw
new
Meteor
.
Error
(
403
,
"question (#{docId}) not found."
)
unless
question
?
typeChange =
false
#check if question.code is unique
if
(
code =
modifier
[
'$set'
].
code
)
and
code
isnt
question
.
code
count =
Questions
.
find
(
_id: $ne:
question
.
_id
questionnaireId:
question
.
questionnaireId
$or:
[
{
code:
code
},
{
'subquestions.code'
:
code
}
]
).
count
()
if
count
>
0
details =
EJSON
.
stringify
[
{
name:
"code"
,
type:
"notUnique"
,
value:
code
}
]
throw
new
Meteor
.
Error
(
400
,
"validationError"
,
details
)
#check for dangerous changes not allowed for already answered questions
#don't check if subquestions.$.code and choices.$.value are unique, we do that in the schema
dangerousChange =
false
if
(
type
=
modifier
[
'$set'
].
type
)
?
and
Object
.
keys
(
modifier
[
'$set'
]).
length
is
1
typeChange =
true
dangerousChange =
true
if
(
choices =
modifier
[
'$set'
].
choices
)
?
if
choices
.
length
<
question
.
choices
.
length
dangerousChange =
true
else
i =
0
values =
{}
while
i
<
choices
.
length
c =
choices
[
i
]
co =
question
.
choices
[
i
]
if
!
c
?
#c was removed
dangerousChange =
true
continue
if
co
?
and
c
.
value
isnt
co
.
value
#co can be null if c is being added
dangerousChange =
true
#check if s.value is unique within this questions choices
if
values
[
c
.
value
]
?
details =
EJSON
.
stringify
[
{
name:
"choices.#{i}.value"
,
type:
"notUnique"
,
value:
c
.
value
}
]
throw
new
Meteor
.
Error
(
400
,
"validationError"
,
details
)
values
[
c
.
value
]
=
i
i
+=
1
if
(
subquestions =
modifier
[
'$set'
].
subquestions
)
if
subquestions
.
length
<
question
.
subquestions
.
length
dangerousChange =
true
else
i =
0
codes =
{}
while
i
<
subquestions
.
length
s =
subquestions
[
i
]
so =
question
.
subquestions
[
i
]
if
!
s
?
#s was removed
dangerousChange =
true
continue
else
if
so
?
and
s
.
code
isnt
so
.
code
#so can be null if s is being added
dangerousChange =
true
#check if s.code is unique
#check within this questions subquestions
if
codes
[
s
.
code
]
?
details =
EJSON
.
stringify
[
{
name:
"subquestions.#{i}.code"
,
type:
"notUnique"
,
value:
s
.
code
}
]
throw
new
Meteor
.
Error
(
400
,
"validationError"
,
details
)
codes
[
s
.
code
]
=
i
#check within other questions of this questionnaire
count =
Questions
.
find
(
_id: $ne:
question
.
_id
questionnaireId:
question
.
questionnaireId
$or:
[
{
code:
s
.
code
},
{
'subquestions.code'
:
s
.
code
}
]
).
count
()
if
count
>
0
details =
EJSON
.
stringify
[
{
name:
"subquestions.#{i}.code"
,
type:
"notUnique"
,
value:
s
.
code
}
]
throw
new
Meteor
.
Error
(
400
,
"validationError"
,
details
)
i
+=
1
if
(
selectionMode
=
modifier
[
'$set'
].
selectionMode
)
?
and
selectionMode
isnt
question
.
selectionMode
dangerousChange =
true
if
dangerousChange
count =
Answers
.
find
(
questionId:
docId
).
count
()
if
count
>
0
throw
new
Meteor
.
Error
(
400
,
"validationErrorQuestionInUse"
)
q =
new
Question
(
question
).
getModifiedPlainObject
(
modifier
)
schema =
new
Question
(
q
).
getMetaSchemaDict
(
true
)
ss =
new
SimpleSchema
(
schema
)
if
typeChange
#only the type changed: we need to clean the new object
#to ornament it with default values
#apply modifier
ss
.
clean
(
q
)
check
(
q
,
ss
)
# delete translations entirely on typeChange
if
question
.
translations
?
delete
q
.
translations
#replace question entirely
#use direct to prevent $set.updatedAt being added
Questions
.
direct
.
update
docId
,
q
else
check
(
q
,
ss
)
Questions
.
update
docId
,
modifier
# after save tasks
# update translated choices and subquestions
question =
Questions
.
findOne
question
.
_id
checkTranslations =
false
if
dangerousChange
and
question
.
translations
?
Object
.
keys
(
question
.
translations
).
forEach
(lang) ->
translation =
question
.
translations
[
lang
]
if
question
.
choices
?
checkTranslations =
true
translatedChoices =
[]
question
.
choices
.
forEach
(c) ->
translatedChoice =
translation
.
choices
.
find
(tc) ->
tc
.
value
is
c
.
value
if
!
translatedChoice
?
translatedChoice =
c
translatedChoices
.
push
translatedChoice
Questions
.
update
question
.
_id
,
$set:
"translations.#{lang}.choices"
:
translatedChoices
if
question
.
subquestions
?
checkTranslations =
true
translatedSubquestions =
[]
question
.
subquestions
.
forEach
(s) ->
tSubquestion =
translation
.
subquestions
.
find
(ts) ->
ts
.
code
is
s
.
code
if
!
tSubquestion
?
tSubquestion =
s
translatedSubquestions
.
push
tSubquestion
Questions
.
update
question
.
_id
,
$set:
"translations.#{lang}.subquestions"
:
translatedSubquestions
if
checkTranslations
throw
new
Meteor
.
Error
(
400
,
"validationWarningCheckTranslations"
)
return
# changed at: 2016/08/03: didn't work in production. not able to reproduce, reason unknown.
# moveQuestion: (questionnaireId, oldIndex, newIndex) ->
# checkIfAdmin()
# 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}
# return
moveQuestion:
(questionId, up) ->
checkIfAdmin
()
check
(
questionId
,
String
)
check
(
up
,
Boolean
)
question =
Questions
.
findOne
questionId
throw
new
Meteor
.
Error
(
403
,
"question not found."
)
unless
question
?
questionnaire =
Questionnaires
.
findOne
_id:
question
.
questionnaireId
throw
new
Meteor
.
Error
(
403
,
"questionnaire not found."
)
unless
questionnaire
?
numQuestions =
Questions
.
find
(
questionnaireId:
questionnaire
.
_id
).
count
()
if
question
.
index
is
1
and
up
return
if
question
.
index
>=
numQuestions
and
!
up
return
addend =
-
1
addend =
1
if
!
up
Questions
.
update
questionnaireId:
question
.
questionnaireId
index:
question
.
index
+
addend
,
$inc:
{
index:
-
addend
}
Questions
.
update
_id:
questionId
,
$inc:
{
index:
addend
}
return
removeQuestion:
(id) ->
checkIfAdmin
()
check
(
id
,
String
)
question =
Questions
.
findOne
id
throw
new
Meteor
.
Error
(
403
,
"question (#{id}) not found."
)
unless
question
?
questionnaire =
Questionnaires
.
findOne
_id:
question
.
questionnaireId
throw
new
Meteor
.
Error
(
403
,
"questionnaire (#{question.questionnaireId}) not found."
)
unless
questionnaire
?
#check if question is used
count =
Answers
.
find
(
questionId:
id
).
count
()
if
count
>
0
throw
new
Meteor
.
Error
(
400
,
"validationErrorQuestionInUse"
)
Questions
.
remove
id
#update index of remaining questions
Questions
.
update
questionnaireId:
questionnaire
.
_id
index:
{
$gt:
question
.
index
}
,
$inc:
{
index:
-
1
}
,
multi:
true
return
translateQuestion:
(questionId, translation, lang) ->
checkIfAdmin
()
check
(
questionId
,
String
)
check
(
translation
,
Object
)
check
(
lang
,
String
)
question =
Questions
.
findOne
questionId
throw
new
Meteor
.
Error
(
403
,
"question (#{questionId}) not found."
)
unless
question
?
ss =
new
SimpleSchema
(
question
.
getTranslationSchemaDict
())
ss
.
clean
(
translation
)
check
(
translation
,
ss
)
Questions
.
update
_id:
question
.
_id
,
$set:
"translations.#{lang}"
:
translation
Questionnaires
.
update
_id:
question
.
questionnaireId
,
$addToSet:
translationLanguages:
lang
return
Event Timeline
Log In to Comment