NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Prefill MS Forms
// @namespace http://tampermonkey.net/
// @version 0.8.2
// @description Pre-populate fields in Microsoft Forms via URL parameter
// @author KyleMit
// @copyright 2018, KyleMit
// @license MIT
// @match https://forms.office.com/Pages/ResponsePage.aspx*
// @grant none
// @homepage https://github.com/KyleMit/CustomizeTheWeb#readme
// @supportURL https://github.com/KyleMit/CustomizeTheWeb/issues
// ==/UserScript==
// ==OpenUserJS==
// @author KyleMit
// ==/OpenUserJS==
/* global URLSearchParams */
(function() {
'use strict';
// prior art
// [Pre-populate fields in Microsoft Forms via URL parameter?](https://techcommunity.microsoft.com/t5/microsoft-forms/p/m-p/113803)
// [Allow pre-populated data via URL parameter](https://microsoftforms.uservoice.com/forums/386451/suggestions/18741331)
// sample form
// https://forms.office.com/Pages/ResponsePage.aspx?id=O5O0IK26PEOcAnDtzHVZxoUFQnFGPs9NnXgmxPQfHEFUQVBRUTQzRjc0OVNXM1VYSklHTlk3MkhMNi4u
// 1. handle page load
// parse query param
// apply data to forms
// 2. handle form change
// extract data
// update URL
// let formTypes = [...document.querySelectorAll('[id$="-questiontype"]')].map(el => ({id: el.id, text: el.textContent}))
let questionTypes = [{
type: "Single line text",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Single line text" },
getValue: (el) => { return el.querySelector("input.office-form-question-textbox").value },
setValue: (el, val) => { el.querySelector("input.office-form-question-textbox").value = val }
},
{
type: "Multi line text",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Multi Line Text" },
getValue: (el) => { return el.querySelector("textarea.office-form-question-textbox").value },
setValue: (el, val) => { el.querySelector("textarea.office-form-question-textbox").value = val }
},
{
type: "Date",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Date" },
getValue: (el) => { return el.querySelector("input.office-form-question-textbox").value },
setValue: (el, val) => { el.querySelector("input.office-form-question-textbox").value = val }
},
{
type: "Single choice (select)",
matches: (el) => { return el.querySelector(".select-placeholder") },
getValue: (el) => { return el.querySelector(".select-placeholder-text").textContent },
setValue: (el, val) => {
// get fake select
let sel = el.querySelector("select-placeholder")
// open select
sel.click();
// give it a second
setTimeout(function() {
// get fake dropdown options
let options = [...el.querySelectorAll(".select-option-menu-container li")]
// get new option
let newOption = options.find(opt => opt.querySelector(".select-option-content").textContent == val)
// select new option
newOption.click()
}, 30)
}
},
{
type: "Single choice (radio)",
matches: (el) => { return el.querySelector(".office-form-question-choice .radio") },
getValue: (el) => {
let option = [...el.querySelectorAll("input[type='radio']")].find(o => o.checked)
return option ? option.value : null
},
setValue: (el, val) => {
let options = [...el.querySelectorAll("input[type='radio']")]
// get new option
let newOption = options.find(opt => opt.value == val)
// set new option
newOption.checked = true
}
},
{
type: "Multiple choice",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Multiple choice" },
getValue: (el) => { return [...el.querySelectorAll("input[type='checkbox']")].filter(o => o.checked).map(o => o.value) },
setValue: (el, val) => {
let options = [...el.querySelectorAll("input[type='checkbox']")]
// get new option
let selOptions = options.filter(opt => val.includes(opt.value))
// set new option
selOptions.forEach(o => { o.checked = true })
}
},
{
type: "Rating",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Rating" },
getValue: (el) => {
return null
},
setValue: (el, val) => {
// todo
// // menu must be visible for events to fire https://stackoverflow.com/a/33971082/1366033
// let range = el.querySelector(".rateit-range")
// // only listens for mousedown event (not click) https://stackoverflow.com/a/20567978/1366033
// range.dispatchEvent(new Event('mouseup'));
// // menu must be visible for events to fire https://stackoverflow.com/a/33971082/1366033
// let menu = el.querySelector(".rateit-hover")
// menu.style.display = "flex"
// let opts = [...el.querySelectorAll(".rateit-slected span")]
// let opt = opts[val - 1]
// // only listens for mousedown event (not click) https://stackoverflow.com/a/20567978/1366033
// opt.dispatchEvent(new Event('mouseup'));
}
},
{
type: "Ranking",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Ranking" },
getValue: (el) => {
return null
},
setValue: (el, val) => {
// todo
}
},
{
type: "Likert",
matches: (el) => { return el.querySelector('[id$="-questiontype"]').textContent == "Likert" },
getValue: (el) => {
return null
},
setValue: (el, val) => {
// todo
}
},
]
let getQuestionNum = (el) => el.querySelector(".ordinal-number").textContent.replace(".", "")
let getQuestionType = (el) => questionTypes.find(q => q.matches(el))
let origUrl = window.location.pathname + window.location.search
let updateUrlWithData = () => {
// extract values
let questions = [...document.querySelectorAll('.office-form-question')]
let formDataEntries = questions.map(el => {
let num = getQuestionNum(el)
// get question type (determines how to extract value)
let type = getQuestionType(el)
let key = 'q' + num
let value = type.getValue(el)
return [key, value]
})
let formData = Object.fromEntries(formDataEntries.filter(ent => ent[1]))
let queryParams = new URLSearchParams(formData).toString()
let newUrl = `${origUrl}&${queryParams}`
history.pushState({}, '', newUrl)
}
let updateDataWithUrl = () => {
// get values from URL
let urlData = Object.fromEntries(new URLSearchParams(location.search));
// apply values for each question
let questions = [...document.querySelectorAll('.office-form-question')]
questions.forEach(el => {
let num = getQuestionNum(el)
// get question type (determines how to extract value)
let type = getQuestionType(el)
// get question value
let key = 'q' + num
let value = urlData[key]
if (value) {
type.setValue(el, value)
}
})
}
// handle page load event and parse data from URL into form
// https://stackoverflow.com/a/36096571/1366033
document.addEventListener("DOMContentLoaded", function() {})
window.addEventListener("load", updateDataWithUrl);
// handle form change events and push data to URL
document.addEventListener('change', updateUrlWithData)
})();