Skip to content

Commit

Permalink
subscription checkbox ++
Browse files Browse the repository at this point in the history
- adds checkbox to subscription form
- restricts tabbing to modal when modal is open
- sends POST for subscription form with fetch
- more placeholder css mobile tweaks
  • Loading branch information
lesleyjanenorton committed Aug 14, 2018
1 parent 72fda27 commit 71e5cfc
Show file tree
Hide file tree
Showing 12 changed files with 368 additions and 263 deletions.
281 changes: 161 additions & 120 deletions public/css/app.css

Large diffs are not rendered by default.

276 changes: 168 additions & 108 deletions public/js/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function getLocation() {
}
}

function handleEvents(string) {
function sendGAPing(string) {
if(typeof(ga) !== "undefined") {
const eventLocation = getLocation();
const event = events[string];
Expand All @@ -57,13 +57,6 @@ function isValidEmail(val) {
return re.test(String(val).toLowerCase());
}

function addClass(selector, className) {
const el = document.querySelector(selector);
if (el) {
el.classList.add(className);
}
}

function removeClass(selector, className) {
const el = document.querySelector(selector);
if (el) {
Expand All @@ -72,7 +65,6 @@ function removeClass(selector, className) {
}

function removeInvalidMessage(e) {
enableBtnIfEmailValid(e);
const thisForm = e.target.form;
thisForm.classList.remove("invalid");
}
Expand All @@ -81,79 +73,88 @@ function doOauth() {
window.open("/oauth/init");
}

function handleSignUpModal() {
handleEvents("SignUp");
addClass("body","show-subscribe-modal");
window.addEventListener("keydown", function subscribeModalKeyListener(e) {
if (e.code !== "Escape") {
return;
}
removeClass("body", "show-subscribe-modal");
window.removeEventListener("keydown", subscribeModalKeyListener);
});
}
// restricts tabbing to modal elements when modal is open.
// disables tabbing on modal elements when modal is closed.
function setModalTabbing(){
const tabbableElements = Array.from(document.querySelectorAll("a, button, input"));

function addSubscriptionListeners() {
const subscribeModal = document.getElementById("subscribe-modal");
subscribeModal.addEventListener("click", function subscribeModalClickListener(e) {
if (e.target === subscribeModal) {
removeClass("body", "show-subscribe-modal");
}
});
document.querySelector("#subscribe-fxa-btn").addEventListener("click", function fxaSubmit() {
doOauth();
removeClass("body", "show-subscribe-modal");
});
// get tabbable elements in sign up form window
let modalTabContent = Array.from(document.getElementById("subscribe-to-ffxm").querySelectorAll("a, input, button"));

}
// if "confirm your email" message is showing, tab those elements instead
if (!document.getElementById("subscribe-to-ffxm").classList.contains("show")) {
modalTabContent = Array.from(document.getElementById("confirm-your-account").querySelectorAll("a, input, button"));
}

function enableBtnIfEmailValid(e) {
const thisForm = e.target.form;
thisForm.classList.remove("invalid");
const emailButton = thisForm.querySelector(".button");
if (isValidEmail(e.target.value)) {
emailButton.disabled = false;
thisForm.classList.remove("invalid");
} else {
emailButton.disabled = true;
if(e.code === "Enter") {
thisForm.classList.add("invalid");
// if modal is displayed, set tabindex to 1 on only those elements
// and disable tabbing on everything else
if (document.body.classList.contains("show-subscribe-modal")) {
for (const eachElement of tabbableElements) {
if (modalTabContent.indexOf(eachElement) === -1) {
eachElement.setAttribute("tabindex", "-1");
}
else {
eachElement.setAttribute("tabindex", "1");
}
}
return;
}
}

function findParent(element, tag) {
while (element.parentNode) {
element = element.parentNode;
if (element.tagName === tag)
return element;
// disable tabbing if modal window is closed and re-enable all other tabbing
for (const eachElement of tabbableElements) {
if (modalTabContent.indexOf(eachElement) === -1) {
eachElement.setAttribute("tabindex", "1");
} else {
eachElement.setAttribute("tabindex", "-1");
}
}
return null;
}

// function findChild(element, tag) {
// while (element.)
// }
function closeModalWindow() {
document.body.classList.remove("show-subscribe-modal");
removeClass("#subscribe-to-ffm", "show");
removeClass("#confirm-your-account", "show");
document.getElementById("subscribe-email-input").value = "";
document.getElementById("additional-emails-checkbox").checked = false;
setModalTabbing();
}

function showMessageIfInvalid(e) {
const thisElement = e.target;
const thisForm = findParent(thisElement, "FORM");
if (!thisForm.querySelector("input").value || thisForm.querySelector(".button").disabled) {
thisForm.classList.add("invalid");
return true;
}
function openModalWindow() {
sendGAPing("SignUp");
document.body.classList.add("show-subscribe-modal");
document.getElementById("subscribe-to-ffxm").classList.add("show");
setModalTabbing();
const subscribeModal = document.getElementById("subscribe-modal");
subscribeModal.addEventListener("click", (e) => {
if (e.target === subscribeModal) {
closeModalWindow();
}
});
}

function checkBoxStates(e) {
const checkboxGroup = e.target;
const checkBox = [].slice.call(checkboxGroup.querySelectorAll("input"))[0];
console.log(checkBox);
// handles checkbox states and expands the 'checkbox clickable space'
// by letting user click the checkbox's wrapping div to toggle states
function checkBoxStates(checkBoxEvent) {
checkBoxEvent.preventDefault();
let checkBox = null;
// user tabs (keyCode === 16) or tabs BACK (keyCode === 16) to checkbox element
if (checkBoxEvent.keyCode === 9 || checkBoxEvent.keyCode === 16) {
checkBox = checkBoxEvent.target;
checkBox.focused = true;
return;
}
// user hit space to select checkbox element
if (checkBoxEvent.keyCode === 32 ) {
checkBox = checkBoxEvent.target;
}
// user clicks checkbox group
if (checkBoxEvent.target.classList.contains("checkbox-group")) {
const thisCheckBoxGroup = checkBoxEvent.target;
checkBox = thisCheckBoxGroup.querySelector("input[type=checkbox]");
}
if (checkBox.checked) {
console.log("checkbox is checked")
checkBox.checked = false;
return;
} else {
console.log("checkbox isn't checked");
checkBox.checked = true;
}
}
Expand All @@ -168,13 +169,8 @@ async function sha1(message) {
}

async function hashEmailAndSend(emailFormSubmitEvent) {
emailFormSubmitEvent.preventDefault();
const emailForm = emailFormSubmitEvent.target;
if (!emailForm.querySelector("input").value) {
emailForm.classList.add("invalid");
return;
}
handleEvents("Scan");
sendGAPing("Scan");
emailForm.classList.add("loading-data");
for (const emailInput of emailForm.querySelectorAll("input[type=email]")) {
emailForm.querySelector("input[name=emailHash]").value = await sha1(emailInput.value);
Expand All @@ -183,66 +179,130 @@ async function hashEmailAndSend(emailFormSubmitEvent) {
emailForm.submit();
}

function addFormListeners() {
for (const input of document.querySelectorAll(".input-group-field")) {
input.addEventListener("keydown", (e) => enableBtnIfEmailValid(e));
input.addEventListener("focusin", (e) => removeInvalidMessage(e));
const addUser = (formEvent) => {
const formElement = formEvent.target;
const formObject = {};
formObject["email"] = formElement.querySelector("#subscribe-email-input").value;
if (formElement.querySelector("input[type=checkbox]").checked) {
formObject["additionalEmails"] = "Opt user in to additional emails";
}
postData(formElement.action, formObject)
.then(data => {
console.log(data);
document.getElementById("subscribe-to-ffxm").classList.remove("show");
document.getElementById("confirm-your-account").classList.add("show");
setModalTabbing();
})
.catch(error => console.error(error));
};

const postData = (url, data = {}) => {
return fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
headers: {
"Content-Type": "application/json; charset=utf-8",
},
// redirect: "follow", // manual, *follow, error
referrer: "no-referrer", // no-referrer, *client
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
.then(response => response.json()) // parses response to JSON
.catch(error => console.error(error));
};

function handleFormSubmits(formEvent) {
formEvent.preventDefault();
const thisForm = formEvent.target;
if (!thisForm.email.value || !isValidEmail(thisForm.email.value)) {
thisForm.classList.add("invalid");
return;
}
for (const buttonWrapper of document.querySelectorAll(".input-group-button")) {
buttonWrapper.addEventListener("click", (e) => showMessageIfInvalid(e));
if (thisForm.classList.contains("email-scan")) {
hashEmailAndSend(formEvent);
return;
}
if (formEvent.target.id === "subscribe-form") {
addUser(formEvent);
setModalTabbing();
return;
}
return;
}

function addFormListeners() {
for (const form of document.forms) {
if (form.querySelector("input[type=email]")) {
const emailInput = form.querySelector("input[type=email]");
emailInput.addEventListener("keydown", (e) => removeInvalidMessage(e));
emailInput.addEventListener("focusin", (e) => removeInvalidMessage(e));
}
if (form.querySelector("input[type=checkbox]")) {
const checkBoxWrapper = form.querySelector(".checkbox-group");
checkBoxWrapper.addEventListener("click", (e) => checkBoxStates(e));
checkBoxWrapper.addEventListener("keyup",(e) => checkBoxStates(e));
}
form.addEventListener("submit", (e) => handleFormSubmits(e));
}
for(const checkbox of document.querySelectorAll(".checkbox-group")) {
checkbox.addEventListener("click", (e) => checkBoxStates(e));
}

function showAdditionalBreaches(){
document.getElementById("show-additional-breaches").classList.toggle("hide");
const additionalBreaches = document.getElementById("additional-breaches");
additionalBreaches.classList.toggle("show-breaches");
//setting height this way enables transition easing... setting the new height to "auto"
if (additionalBreaches.classList.contains("show-breaches")) {
additionalBreaches.style.height = additionalBreaches.scrollHeight + "px";
}
}

function doButtonRouting(event) {
if (event.target.id === "show-additional-breaches") {
addClass("#show-additional-breaches", "hide");
addClass("#additional-breaches", "show-breaches");
const additionalBreaches = document.getElementById("additional-breaches");
if (additionalBreaches.classList.contains("show-breaches")) {
additionalBreaches.style.height = additionalBreaches.scrollHeight + "px";
}
showAdditionalBreaches();
return;
}
if (event.target.id === "sign-up") {
handleSignUpModal();
addSubscriptionListeners();
openModalWindow();
return;
}
if (event.target.id === "subscribe-fxa-btn") {
doOauth();
closeModalWindow();
return;
}
if (event.target.classList.contains("close-modal")) {
closeModalWindow();
return;
}
return;
}

//re-enables inputs and clears loader
//re-enables inputs and clears loader
function restoreInputs() {
for (const input of document.querySelectorAll(".input-group-field")) {
for (const input of document.querySelectorAll("input")) {
if (input.disabled) {
input.disabled = false;
}
}
removeClass(".form-group", "loading-data");
removeClass(".form-group", "invalid");
removeClass("form", "loading-data");
removeClass("form", "invalid");
}

//adds listeners to scan and subscription forms.
if (document.querySelector(".form-group")) {
addFormListeners();
if (document.querySelector(".email-scan")) {
document.querySelector(".email-scan").addEventListener("submit", hashEmailAndSend);
}
}

window.addEventListener("pageshow", function() {
if (document.getElementById("no-breaches") || document.getElementById("found-breaches")) {
handleEvents("Pageview");
sendGAPing("Pageview");
}
if (document.querySelector(".form-group")) {
if (document.forms) {
restoreInputs();
}
});

if (document.querySelectorAll(".button")) {
console.log("button");
const buttons = [].slice.call(document.querySelectorAll("button"));
buttons.forEach(button => {
button.addEventListener("click", (e) => doButtonRouting(e));
});
if(document.forms) {
addFormListeners();
}

if (document.querySelectorAll("button")) {
for (const eachButton of document.querySelectorAll("button")) {
eachButton.addEventListener("click", (e) => doButtonRouting(e));
}
}
10 changes: 4 additions & 6 deletions routes/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ const ResponseCodes = Object.freeze({

const router = express.Router();
const jsonParser = bodyParser.json();
const urlEncodedParser = bodyParser.urlencoded({ extended: false });


router.post("/add", urlEncodedParser, async (req, res) => {
router.post("/add", jsonParser, async (req, res) => {
const email = req.body.email;
const verificationToken = await DB.addSubscriberUnverifiedEmailHash(email);

Expand All @@ -34,10 +33,9 @@ router.post("/add", urlEncodedParser, async (req, res) => {
{ email, url}
);

res.render("add", {
title: "Verify email",
email: email,
});
res.send({
title: "Firefox Monitor : Confirm Email",
});
} catch (e) {
console.error(e);
res.status(500).json({
Expand Down
1 change: 0 additions & 1 deletion views/confirm.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{{!< default }}
<div class="subscribe-message-wrapper">
<div class="subscribe-message">

<h4>Your Email Is Confirmed!</h4>
<h5>You will receive Firefox Monitor notifications at {{ email }}.</h5>
<button class="button" data-open="share-modal">Share</button>
Expand Down
Loading

0 comments on commit 71e5cfc

Please sign in to comment.