From f494f5b6c3bab1867cf8a66e9845be881a83125d Mon Sep 17 00:00:00 2001 From: KiaraEdTech Date: Thu, 6 May 2021 13:16:43 +0200 Subject: [PATCH 1/8] adding number poll --- resources/helpers/constants.js | 5 +- resources/helpers/scoreHelpers.js | 6 + resources/helpers/statsCalculators.js | 57 +++++ resources/helpers/utils.js | 1 + resources/schema.json | 60 +++++ routes/answer-service/number-poll.js | 311 ++++++++++++++++++++++++++ routes/rendering-info/html-js.js | 1 - routes/routes.js | 1 + script_src/AnswerStore.js | 1 + script_src/NumberPollHandler.js | 222 ++++++++++++++++++ script_src/QuestionHandler.js | 2 + script_src/helpers.js | 1 + test/mock/answers.js | 8 + test/mock/views.js | 5 + views/NumberPoll.html | 58 +++++ views/Question.html | 8 +- 16 files changed, 743 insertions(+), 4 deletions(-) create mode 100644 routes/answer-service/number-poll.js create mode 100644 script_src/NumberPollHandler.js create mode 100644 views/NumberPoll.html diff --git a/resources/helpers/constants.js b/resources/helpers/constants.js index 436c73fc..1d1aad8a 100644 --- a/resources/helpers/constants.js +++ b/resources/helpers/constants.js @@ -1,8 +1,9 @@ module.exports = { - questionTypes: ["multipleChoice", "numberGuess", "mapPointGuess"], + questionTypes: ["multipleChoice", "numberGuess", "mapPointGuess", "numberPoll"], multiplicator: { multipleChoice: 5, numberGuess: 10, - mapPointGuess: 10 + mapPointGuess: 10, + numberPoll: 10 } }; diff --git a/resources/helpers/scoreHelpers.js b/resources/helpers/scoreHelpers.js index 84885b6d..66b86053 100644 --- a/resources/helpers/scoreHelpers.js +++ b/resources/helpers/scoreHelpers.js @@ -8,6 +8,9 @@ function calculateWorstAnswerDifference(question) { question.max - question.answer ); } + if (question.type === "numberPoll") { + return 1; + } if (question.type === "mapPointGuess") { const bbox = question.answer.bbox; const correctAnswerCoord = question.answer.geometry.coordinates; @@ -84,6 +87,9 @@ function getAnswerQuality(question) { Math.abs(question.userAnswer - question.answer) / worstAnswerDifference ); } + if (question.type === "numberPoll") { + return 1; + } if ( question.type === "mapPointGuess" && worstAnswerDifference !== undefined diff --git a/resources/helpers/statsCalculators.js b/resources/helpers/statsCalculators.js index c0a8e140..11c7bb27 100644 --- a/resources/helpers/statsCalculators.js +++ b/resources/helpers/statsCalculators.js @@ -54,6 +54,62 @@ class NumberGuessStatsCalculator { } } +class NumberPollStatsCalculator { + constructor(answersStats, correctAnswer, userAnswer) { + this.answersStats = answersStats; + this.correctAnswer = correctAnswer; + this.userAnswer = userAnswer; + } + + getStats() { + let betterThanCount; + let numberOfSameAnswers; + let diffPercentage; + + let totalAnswers = this.answersStats.reduce((prev, current) => { + return prev + current.count; + }, 0); + + if (this.userAnswer) { + betterThanCount = this.answersStats.reduce((prev, current) => { + if ( + Math.abs(this.correctAnswer - current.value) > + Math.abs(this.correctAnswer - this.userAnswer.value) + ) { + return prev + current.count; + } + return prev; + }, 0); + + numberOfSameAnswers = this.answersStats.reduce((prev, current) => { + if (this.userAnswer.value === current.value) { + return prev + current.count - 1; + } + return prev; + }, 0); + + diffPercentage = Math.abs( + Math.round( + (Math.abs(this.correctAnswer - this.userAnswer.value) / + this.correctAnswer) * + 100 + ) + ); + } + + return { + betterThanPercentage: + betterThanCount !== undefined + ? Math.round((betterThanCount / totalAnswers) * 100) + : undefined, + betterThanCount: betterThanCount, + diffPercentage: diffPercentage, + numberOfSameAnswers: numberOfSameAnswers, + totalAnswers: totalAnswers + }; + } +} + class MultipleChoiceStatsCalculator { constructor(answersStats, correctAnswer, userAnswer) { this.answersStats = answersStats; @@ -120,6 +176,7 @@ class MapPointGuessStatsCalculator { module.exports = { numberGuess: NumberGuessStatsCalculator, + numberPoll: NumberPollStatsCalculator, multipleChoice: MultipleChoiceStatsCalculator, mapPointGuess: MapPointGuessStatsCalculator }; diff --git a/resources/helpers/utils.js b/resources/helpers/utils.js index 64628c65..f1694713 100644 --- a/resources/helpers/utils.js +++ b/resources/helpers/utils.js @@ -7,6 +7,7 @@ function getAnswers(type, questionId, options) { } if ( type === "numberGuess" || + type === "numberPoll" || type === "mapPointGuess" || type === "multipleChoice" ) { diff --git a/resources/schema.json b/resources/schema.json index 3410893b..3727adda 100644 --- a/resources/schema.json +++ b/resources/schema.json @@ -28,6 +28,7 @@ { "$ref": "#/definitions/cover" }, { "$ref": "#/definitions/multipleChoice" }, { "$ref": "#/definitions/numberGuess" }, + { "$ref": "#/definitions/numberPoll" }, { "$ref": "#/definitions/mapPointGuess" }, { "$ref": "#/definitions/lastCard" } ] @@ -178,6 +179,65 @@ }, "required": ["question", "answer", "min", "max", "step"] }, + "numberPoll": { + "type": "object", + "title": "Zahl Umfrage", + "properties": { + "id": { "$ref": "#/definitions/elementId" }, + "type": { + "type": "string", + "default": "numberPoll", + "enum": ["numberPoll"], + "Q:options": { + "hideInEditor": true + } + }, + "introduction": { "$ref": "#/definitions/introduction" }, + "image": { "$ref": "#/definitions/image" }, + "question": { + "type": "string", + "title": "Frage" + }, + "unit": { + "type": "string", + "title": "Einheit Plural", + "Q:options": { + "placeholder": "Was für eine Zahl ist zu schätzen? Meter? Personen? Franken? etc." + } + }, + "unit_singular": { + "type": "string", + "title": "Einheit Singular", + "Q:options": { + "placeholder": "Nur nötig, falls abweichend von Einheit Plural." + } + }, + "min": { + "type": "number", + "title": "Minimalwert auf der Schätzskala" + }, + "max": { + "type": "number", + "title": "Maximalwert auf der Schätzskala" + }, + "step": { + "type": "number", + "minimum": 0, + "title": "Grösse der Zwischenschritte beim Verschieben des Sliders", + "Q:options": { + "placeholder": "Sollte so gewählt sein, dass sich zwischen Minimal- und Maximalwert etwa 100-500 Zwischenschritte ergeben." + } + }, + "notes": { + "type": "string", + "title": "Anmerkungen" + }, + "articleRecommendations": { + "$ref": "#/definitions/articleRecommendations" + } + }, + "required": ["question", "min", "max", "step"] + }, "mapPointGuess": { "type": "object", "title": "Ort schätzen", diff --git a/routes/answer-service/number-poll.js b/routes/answer-service/number-poll.js new file mode 100644 index 00000000..69a5ee9f --- /dev/null +++ b/routes/answer-service/number-poll.js @@ -0,0 +1,311 @@ +"use strict"; + +const Boom = require("@hapi/boom"); +const Joi = require("@hapi/joi"); +const getAnswers = require("../../resources/helpers/utils.js").getAnswers; +const getItem = require("../../resources/helpers/utils.js").getItem; +const getPrecision = require("../../resources/helpers/utils.js").getPrecision; + +const jsdom = require("jsdom"); +const { JSDOM } = jsdom; + +let d3 = { + scaleLinear: require("d3-scale").scaleLinear, + scaleBand: require("d3-scale").scaleBand, + max: require("d3-array").max, + select: require("d3-selection").select +}; + +function getStripplotSvg(data, stats, plotWidth) { + return new Promise((resolve, reject) => { + // in order to work with jsdom like that let jsdom version at 9.5 + // should be rebuilt and upgraded in the future + const dom = new JSDOM(""); + if (!stats) { + reject("stats is undefined"); + return; + } + + let margin = { top: 0, right: 0, bottom: 30, left: 0 }, + width = plotWidth - margin.left - margin.right, + height = 90 - margin.top - margin.bottom; + + let xScale = d3 + .scaleLinear() + .domain([data.min, data.max]) + .range([0, width]); + + let yScale = d3 + .scaleLinear() + .domain([0, d3.max(stats, d => parseInt(d.count))]) + .range([height, 0]); + + let element = dom.window.document.createElement("div"); + + let svg = d3 + .select(element) + .append("svg") + .datum(stats) + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + svg + .append("rect") + .attr("x", 0) + .attr("y", margin.top) + .attr("width", width) + .attr("height", 60) + .attr("fill", "transparent"); + + svg + .append("text") + .text(data.min) + .attr("class", "s-font-note-s s-font-note-s--light s-font-note--tabularnums") + .attr("text-anchor", "start") + .attr("x", 3) + .attr("y", margin.top + 60) + .attr("dy", "1em"); + svg + .append("line") + .attr("class", "s-color-gray-6 q-quiz-answer-chart-min-line") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", margin.top + height + 2) + .attr("y2", margin.top + height + 8); + + svg + .append("text") + .text(data.max) + .attr("class", "s-font-note-s s-font-note-s--light s-font-note--tabularnums") + .attr("text-anchor", "end") + .attr("x", width - 3) + .attr("y", margin.top + 60) + .attr("dy", "1em"); + svg + .append("line") + .attr("class", "s-color-gray-6 q-quiz-answer-chart-max-line") + .attr("x1", width) + .attr("x2", width) + .attr("y1", margin.top + height + 2) + .attr("y2", margin.top + height + 8); + + let lines = svg + .selectAll("line.q-quiz-answer-chart-line") + .data(stats) + .enter() + .append("line") + .attr("class", "q-quiz-answer-chart-line s-color-gray-8") + .attr("style", d => { + return `opacity: ${0.1 * d.count > 0.9 ? 0.9 : 0.1 * d.count}`; + }) + .attr("x1", d => xScale(d.value)) + .attr("x2", d => xScale(d.value)) + .attr("y1", margin.top) + .attr("y2", margin.top + 60); + + resolve(element.innerHTML); + }); +} + +function getBarchartSvg(data, stats, chartWidth) { + return new Promise((resolve, reject) => { + const dom = new JSDOM(""); + if (!stats) { + reject("stats is undefined"); + return; + } + + let margin = { top: 0, right: 0, bottom: 30, left: 0 }, + width = chartWidth - margin.left - margin.right, + height = 90 - margin.top - margin.bottom; + + let precision = getPrecision(data.step); + + let xDomain = []; + for ( + let i = data.min; + i <= data.max; + i = parseFloat((i + data.step).toFixed(precision)) + ) { + xDomain.push(i); + } + + let xScale = d3 + .scaleBand() + .domain(xDomain) + // we probably want to have this padding, its removed to have it easier on the client to calculate the position for the overlaying correct and users answer + // .paddingInner(0.1) + .range([0, width]); + + let yScale = d3 + .scaleLinear() + .domain([ + 0, + Math.max( + 200 / stats.length, + d3.max(stats, d => parseInt(d.count)) + ) + ]) + .range([height, 0]); + + let element = dom.window.document.createElement("div"); + + let svg = d3 + .select(element) + .append("svg") + .datum(stats) + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + svg + .append("rect") + .attr("x", 0) + .attr("y", margin.top) + .attr("width", width) + .attr("height", 60) + .attr("fill", "transparent"); + + svg + .append("text") + .text(data.min) + .attr("class", "s-font-note-s s-font-note-s--light s-font-note--tabularnums") + .attr("text-anchor", "start") + .attr("x", 3) + .attr("y", margin.top + 60) + .attr("dy", "1em"); + svg + .append("line") + .attr("class", "s-color-gray-6 q-quiz-answer-chart-min-line") + .attr("x1", 0) + .attr("x2", 0) + .attr("y1", margin.top + height + 2) + .attr("y2", margin.top + height + 8); + + svg + .append("text") + .text(data.max) + .attr("class", "s-font-note-s s-font-note-s--light s-font-note--tabularnums") + .attr("text-anchor", "end") + .attr("x", width - 3) + .attr("y", margin.top + 60) + .attr("dy", "1em"); + svg + .append("line") + .attr("class", "s-color-gray-6 q-quiz-answer-chart-max-line") + .attr("x1", width) + .attr("x2", width) + .attr("y1", margin.top + height + 2) + .attr("y2", margin.top + height + 8); + + let bars = svg + .selectAll(".bar") + .data(stats) + .enter() + .append("rect") + .attr("class", "s-color-gray-4") + .attr("fill", "currentColor") + .attr("x", function(d) { + return xScale(d.value); + }) + .attr("width", xScale.bandwidth()) + .attr("y", function(d) { + return yScale(d.count); + }) + .attr("height", function(d) { + return Math.max(1, height - yScale(d.count)); + }); + + resolve(element.innerHTML); + }); +} + +module.exports = [ + { + method: "GET", + path: "/number-poll/{itemId}/{questionId}/plot/{width}", + options: { + tags: ["api"], + validate: { + params: { + itemId: Joi.string().required(), + questionId: Joi.string().required(), + width: Joi.number().required() + } + }, + cors: true + }, + handler: async function(request, h) { + return await Promise.all([ + getItem(request.params.itemId), + getAnswers("numberPoll", request.params.questionId) + ]) + .then(data => { + let item = data[0]; + let answers = data[1]; + let stats; + + if (answers.rows && answers.rows.length > 0) { + stats = answers.rows.map(row => { + return { + value: row.key[1], + count: row.value + }; + }); + } + let question = item.elements.filter(element => { + return element.id === request.params.questionId; + })[0]; + + return { + stats: stats, + question: question + }; + }) + .then(async data => { + let stats = data.stats; + let question = data.question; + + question.min = parseFloat(question.min); + question.max = parseFloat(question.max); + question.step = parseFloat(question.step); + + let numberOfPossibleAnswers = 0; + for (let i = question.min; i <= question.max; i = i + question.step) { + numberOfPossibleAnswers++; + } + + if (numberOfPossibleAnswers <= 100) { + try { + return await getBarchartSvg( + question, + stats, + request.params.width + ); + } catch (errMessage) { + console.log(errMessage); + return Boom.badRequest(errMessage); + } + } else { + try { + return await getStripplotSvg( + question, + stats, + request.params.width + ); + } catch (errMessage) { + console.log(errMessage); + return Boom.badRequest(errMessage); + } + } + }) + .catch(couchError => { + console.log(couchError); + return Boom.badRequest(couchError.message); + }); + } + } +]; diff --git a/routes/rendering-info/html-js.js b/routes/rendering-info/html-js.js index ddf5f13b..2fca7349 100644 --- a/routes/rendering-info/html-js.js +++ b/routes/rendering-info/html-js.js @@ -77,7 +77,6 @@ module.exports = { // after that we don't need item.elements anymore let item = transform(request.payload.item); delete item.elements; - // get id of quiz item out of query string let id = request.query._id; if (id === undefined && item.elements && item.elements.length > 0) { diff --git a/routes/routes.js b/routes/routes.js index 47e67b79..e5ea12b0 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -11,5 +11,6 @@ module.exports = [ require("./answer-service/stats.js"), require("./answer-service/map-point-guess.js"), require("./answer-service/number-guess.js"), + require("./answer-service/number-poll.js"), require("./answer-service/score.js") ); diff --git a/script_src/AnswerStore.js b/script_src/AnswerStore.js index 144e7330..46d248a0 100644 --- a/script_src/AnswerStore.js +++ b/script_src/AnswerStore.js @@ -24,6 +24,7 @@ export default class AnswerStore { getStats(itemId, questionData, answerId = undefined) { if ( questionData.type === "numberGuess" || + questionData.type === "numberPoll" || questionData.type === "mapPointGuess" || questionData.type === "multipleChoice" ) { diff --git a/script_src/NumberPollHandler.js b/script_src/NumberPollHandler.js new file mode 100644 index 00000000..60a5e491 --- /dev/null +++ b/script_src/NumberPollHandler.js @@ -0,0 +1,222 @@ +import Scale from "./Scale.js"; +import { getAnswerTextElement } from "./answerHelpers.js"; + +function getUnit(value, data) { + let unit = data.unit; + if ( + parseFloat(value) === 1 && + data.unitSingular && + data.unitSingular !== "" + ) { + unit = data.unitSingular; + } + return unit; +} + +export default class NumberPollHandler { + constructor(questionElement, data, quizId, toolBaseUrl) { + this.questionElement = questionElement; + this.inputElement = this.questionElement.querySelector( + '.q-quiz-input input[type="range"]' + ); + this.min = parseFloat(this.inputElement.getAttribute("min")); + this.max = parseFloat(this.inputElement.getAttribute("max")); + this.step = parseFloat(this.inputElement.getAttribute("step")); + this.defaultInputValue = + (parseFloat(this.max) - parseFloat(this.min)) / 2 + parseFloat(this.min); + this.data = data; + this.quizId = quizId; + this.toolBaseUrl = toolBaseUrl; + this.correctAnswer = parseFloat(data.correctAnswer); + } + + renderInput() { + const labelContainer = this.inputElement.parentNode.firstElementChild; + let label = labelContainer.querySelector( + ".q-quiz-input-range-position-label" + ); + this.inputElement.setAttribute("value", this.defaultInputValue); + this.defaultInputValue = this.inputElement.value; + + this.inputElement.addEventListener("input", () => { + label.textContent = this.inputElement.value; + label.setAttribute( + "style", + `left: calc(${((this.inputElement.value - this.min) / + (this.max - this.min)) * + 100}% - 1px);` + ); + }); + this.inputElement.addEventListener("change", () => { + label.textContent = this.inputElement.value; + label.setAttribute( + "style", + `left: calc(${((this.inputElement.value - this.min) / + (this.max - this.min)) * + 100}% - 1px);` + ); + }); + + label.innerHTML = this.defaultInputValue; + label.setAttribute( + "style", + `left: calc(${((this.inputElement.value - this.min) / + (this.max - this.min)) * + 100}% - 1px);` + ); + } + + getValue(event) { + return parseFloat(this.inputElement.value); + } + + isAnswerValid() { + let element = this.inputElement.parentNode.nextElementSibling; + return ( + element.classList.contains("q-quiz-invalid-input-message") || + parseFloat(this.defaultInputValue) !== parseFloat(this.inputElement.value) + ); + } + + handleInvalidAnswer() { + let answerButton = this.inputElement.parentNode.nextSibling; + let defaultInputValueMessageElement = document.createElement("div"); + defaultInputValueMessageElement.classList.add( + "q-quiz-invalid-input-message" + ); + defaultInputValueMessageElement.classList.add("s-font-note"); + defaultInputValueMessageElement.classList.add("s-font-note--strong"); + defaultInputValueMessageElement.innerHTML = + "Sie haben den Schieberegeler nicht bewegt, trotzdem die Position speichern?"; + answerButton.parentNode.insertBefore( + defaultInputValueMessageElement, + answerButton + ); + } + + renderResult(answer) { + this.resultElement = this.questionElement.querySelector( + ".q-quiz-result__number-guess-visual" + ); + const unitData = { + unit: this.resultElement.getAttribute("unit"), + unitSingular: this.resultElement.getAttribute("unit-singular") + }; + + let steppedValues = []; + for (let i = this.min; i <= this.max; i = i + this.step) { + steppedValues.push((100 / (this.max - this.min + 1)) * i); + } + let range = []; + for (let i = 0; i < steppedValues.length; i++) { + range.push(steppedValues[i] - 100 / steppedValues.length / 2); + } + let xScale = new Scale([this.min, this.max], range); + + let stepWidth = + this.resultElement.getBoundingClientRect().width / steppedValues.length; + + let additionalMarkerClass = ""; + let additionalMarkerAttributes = ""; + if (steppedValues.length <= 100) { + additionalMarkerClass = + "q-quiz-result__number-guess-visual__text__marker--few-answers"; + additionalMarkerAttributes = `style="width: ${stepWidth}px;"`; + } + + // show the users answer + let answerHtml = ` +
+ Ihre Schätzung +
${answer} ${getUnit( + answer, + unitData + )}
+
+
+ `; + + let answerElement = document.createElement("div"); + answerElement.classList.add("q-quiz-result__number-guess-visual__text"); + answerElement.classList.add( + "q-quiz-result__number-guess-visual__text--top" + ); + answerElement.classList.add("s-color-primary-7"); + answerElement.innerHTML = answerHtml; + + answerElement.style.position = "absolute"; + + if (answer - this.min > (this.max - this.min) / 2) { + let rightPos = ( + (xScale.range.length - xScale.getIndexOnScale(answer) - 1) * stepWidth + + stepWidth / 2 + + 1 + ).toFixed(1); + answerElement.style.right = `${rightPos}px`; + answerElement.classList.add( + "q-quiz-result__number-guess-visual__text--right" + ); + } else { + let leftPos = ( + (xScale.getIndexOnScale(answer) + 1) * stepWidth - + stepWidth / 2 + + 1 + ).toFixed(1); + answerElement.style.left = `${leftPos}px`; + } + this.resultElement.appendChild(answerElement); + } + + renderResultStats(answer, answersStats) { + let isCorrectAnswer = answer === this.correctAnswer; + let resultVisualElement = this.questionElement.querySelector( + ".q-quiz-result .q-quiz-result__number-guess-visual" + ); + let resultTextElement = this.questionElement.querySelector( + ".q-quiz-result .q-quiz-result-answer-text" + ); + + if ( + answersStats !== undefined && + answersStats.diffPercentage !== undefined && + answersStats.diffPercentage !== null + ) { + let textElement = getAnswerTextElement( + answersStats, + isCorrectAnswer, + () => { + return `Ihre Schätzung liegt um ${ + answersStats.diffPercentage + } Prozent zu ${answer > this.correctAnswer ? "hoch" : "tief"}.`; + } + ); + resultTextElement.appendChild(textElement); + } + + this.renderStatsVisual(resultVisualElement); + } + + getStatsPlot(width) { + return fetch( + `${this.toolBaseUrl}/number-poll/${this.quizId}/${ + this.data.id + }/plot/${width}` + ).then(response => { + if (response.ok) { + return response.text(); + } + return ""; + }); + } + + renderStatsVisual(element) { + this.getStatsPlot(element.getBoundingClientRect().width).then(svgString => { + let statsVisualContainerElement = document.createElement("div"); + statsVisualContainerElement.classList.add( + "q-quiz-result__number-guess-visual__stats-graphic-container" + ); + statsVisualContainerElement.innerHTML = svgString; + element.appendChild(statsVisualContainerElement); + }); + } +} diff --git a/script_src/QuestionHandler.js b/script_src/QuestionHandler.js index db12df00..b720e25b 100644 --- a/script_src/QuestionHandler.js +++ b/script_src/QuestionHandler.js @@ -1,4 +1,5 @@ import NumberGuess from "./NumberGuessHandler.js"; +import NumberPoll from "./NumberPollHandler.js"; import MultipleChoice from "./MultipleChoiceHandler.js"; import MapPointGuess from "./MapPointGuessHandler.js"; import * as answerHelpers from "./answerHelpers.js"; @@ -7,6 +8,7 @@ import AnswerStore from "./AnswerStore.js"; const questionTypes = { numberGuess: NumberGuess, + numberPoll: NumberPoll, multipleChoice: MultipleChoice, mapPointGuess: MapPointGuess }; diff --git a/script_src/helpers.js b/script_src/helpers.js index 9f88d172..fb69e88d 100644 --- a/script_src/helpers.js +++ b/script_src/helpers.js @@ -35,6 +35,7 @@ export function isQuestionElement(element) { export function isQuestionType(type) { return ( type === "numberGuess" || + type === "numberPoll" || type === "multipleChoice" || type === "mapPointGuess" ); diff --git a/test/mock/answers.js b/test/mock/answers.js index 596acc85..3d62e752 100644 --- a/test/mock/answers.js +++ b/test/mock/answers.js @@ -55,6 +55,14 @@ module.exports = [ value: 30 } }, + { + data: { + itemId: "quiz-0", + questionId: "quiz-0-1503673375520-572013999", + type: "numberPoll", + value: 30 + } + }, { data: { itemId: "quiz-0", diff --git a/test/mock/views.js b/test/mock/views.js index cce3dd1a..bc86a93d 100644 --- a/test/mock/views.js +++ b/test/mock/views.js @@ -7,6 +7,11 @@ module.exports = [ "function (doc) {\n if (doc.data && doc.data.type === 'numberGuess' && typeof doc.data.value !== 'undefined') {\n var questionId;\n if (doc.data.questionId) {\n questionId = doc.data.questionId;\n } else if (doc.data.itemId) {\n questionId = doc.data.itemId;\n }\n if (questionId) {\n emit([questionId, doc.data.value], 1);\n }\n }\n}", reduce: "_count" }, + "answers-numberPoll": { + map: + "function (doc) {\n if (doc.data && doc.data.type === 'numberPoll' && typeof doc.data.value !== 'undefined') {\n var questionId;\n if (doc.data.questionId) {\n questionId = doc.data.questionId;\n } else if (doc.data.itemId) {\n questionId = doc.data.itemId;\n }\n if (questionId) {\n emit([questionId, doc.data.value], 1);\n }\n }\n}", + reduce: "_count" + }, "answers-mapPointGuess": { map: "function (doc) {\n if (doc.data && doc.data.type === 'mapPointGuess' && typeof doc.data.value !== 'undefined') {\n var questionId;\n if (doc.data.questionId) {\n questionId = doc.data.questionId;\n } else if (doc.data.itemId) {\n questionId = doc.data.itemId;\n }\n if (questionId) {\n emit([questionId, doc.data.value.distance], 1);\n }\n }\n}", diff --git a/views/NumberPoll.html b/views/NumberPoll.html new file mode 100644 index 00000000..1dbbc7ad --- /dev/null +++ b/views/NumberPoll.html @@ -0,0 +1,58 @@ +
+
+
+
+ {initialValue} +
+
+ +
+ {labelMin} +
+
+ {labelMax} +
+
+ +
+
+
+

+
+ + diff --git a/views/Question.html b/views/Question.html index 961636d4..31fdfe0a 100644 --- a/views/Question.html +++ b/views/Question.html @@ -13,7 +13,8 @@
{#if isMultipleChoice} {/if} {#if isNumberGuess} - {/if} {#if isMapPointGuess} + {/if} {#if isNumberPoll} + {/if} {#if isMapPointGuess} {/if}