access-nyc-patterns
Version:
User Interface Patterns for Benefits Access
316 lines (271 loc) • 9.82 kB
JavaScript
'use strict';
import valid from '../../utilities/valid/valid';
import joinValues from '../../utilities/join-values/join-values';
import formSerialize from 'form-serialize';
/**
* The Newsletter module
* @class
*/
class Newsletter {
/**
* [constructor description]
*/
/**
* The class constructor
* @param {Object} element The Newsletter DOM Object
* @return {Class} The instantiated Newsletter object
*/
constructor(element) {
this._el = element;
this.STRINGS = Newsletter.strings;
// Map toggled checkbox values to an input.
this._el.addEventListener('click', joinValues);
// This sets the script callback function to a global function that
// can be accessed by the the requested script.
window[Newsletter.callback] = (data) => {
this._callback(data);
};
this._el.querySelector('form').addEventListener('submit', (event) =>
(valid(event, this.STRINGS)) ?
this._submit(event).then(this._onload).catch(this._onerror) : false
);
return this;
}
/**
* The form submission method. Requests a script with a callback function
* to be executed on our page. The callback function will be passed the
* response as a JSON object (function parameter).
* @param {Event} event The form submission event
* @return {Promise} A promise containing the new script call
*/
_submit(event) {
event.preventDefault();
// Serialize the data
this._data = formSerialize(event.target, {hash: true});
// Switch the action to post-json. This creates an endpoint for mailchimp
// that acts as a script that can be loaded onto our page.
let action = event.target.action.replace(
`${Newsletter.endpoints.MAIN}?`, `${Newsletter.endpoints.MAIN_JSON}?`
);
// Add our params to the action
action = action + formSerialize(event.target, {serializer: (...params) => {
let prev = (typeof params[0] === 'string') ? params[0] : '';
return `${prev}&${params[1]}=${params[2]}`;
}});
// Append the callback reference. Mailchimp will wrap the JSON response in
// our callback method. Once we load the script the callback will execute.
action = `${action}&c=window.${Newsletter.callback}`;
// Create a promise that appends the script response of the post-json method
return new Promise((resolve, reject) => {
const script = document.createElement('script');
document.body.appendChild(script);
script.onload = resolve;
script.onerror = reject;
script.async = true;
script.src = encodeURI(action);
});
}
/**
* The script onload resolution
* @param {Event} event The script on load event
* @return {Class} The Newsletter class
*/
_onload(event) {
event.path[0].remove();
return this;
}
/**
* The script on error resolution
* @param {Object} error The script on error load event
* @return {Class} The Newsletter class
*/
_onerror(error) {
// eslint-disable-next-line no-console
if (process.env.NODE_ENV !== 'production') console.dir(error);
return this;
}
/**
* The callback function for the MailChimp Script call
* @param {Object} data The success/error message from MailChimp
* @return {Class} The Newsletter class
*/
_callback(data) {
if (this[`_${data[this._key('MC_RESULT')]}`])
this[`_${data[this._key('MC_RESULT')]}`](data.msg);
else
// eslint-disable-next-line no-console
if (process.env.NODE_ENV !== 'production') console.dir(data);
return this;
}
/**
* Submission error handler
* @param {string} msg The error message
* @return {Class} The Newsletter class
*/
_error(msg) {
this._elementsReset();
this._messaging('WARNING', msg);
return this;
}
/**
* Submission success handler
* @param {string} msg The success message
* @return {Class} The Newsletter class
*/
_success(msg) {
this._elementsReset();
this._messaging('SUCCESS', msg);
return this;
}
/**
* Present the response message to the user
* @param {String} type The message type
* @param {String} msg The message
* @return {Class} Newsletter
*/
_messaging(type, msg = 'no message') {
let strings = Object.keys(Newsletter.stringKeys);
let handled = false;
let alertBox = this._el.querySelector(
Newsletter.selectors[`${type}_BOX`]
);
let alertBoxMsg = alertBox.querySelector(
Newsletter.selectors.ALERT_BOX_TEXT
);
// Get the localized string, these should be written to the DOM already.
// The utility contains a global method for retrieving them.
for (let i = 0; i < strings.length; i++)
if (msg.indexOf(Newsletter.stringKeys[strings[i]]) > -1) {
msg = this.STRINGS[strings[i]];
handled = true;
}
// Replace string templates with values from either our form data or
// the Newsletter strings object.
for (let x = 0; x < Newsletter.templates.length; x++) {
let template = Newsletter.templates[x];
let key = template.replace('{{ ', '').replace(' }}', '');
let value = this._data[key] || this.STRINGS[key];
let reg = new RegExp(template, 'gi');
msg = msg.replace(reg, (value) ? value : '');
}
if (handled)
alertBoxMsg.innerHTML = msg;
else if (type === 'ERROR')
alertBoxMsg.innerHTML = this.STRINGS.ERR_PLEASE_TRY_LATER;
if (alertBox) this._elementShow(alertBox, alertBoxMsg);
return this;
}
/**
* The main toggling method
* @return {Class} Newsletter
*/
_elementsReset() {
let targets = this._el.querySelectorAll(Newsletter.selectors.ALERT_BOXES);
for (let i = 0; i < targets.length; i++)
if (!targets[i].classList.contains(Newsletter.classes.HIDDEN)) {
targets[i].classList.add(Newsletter.classes.HIDDEN);
Newsletter.classes.ANIMATE.split(' ').forEach((item) =>
targets[i].classList.remove(item)
);
// Screen Readers
targets[i].setAttribute('aria-hidden', 'true');
targets[i].querySelector(Newsletter.selectors.ALERT_BOX_TEXT)
.setAttribute('aria-live', 'off');
}
return this;
}
/**
* The main toggling method
* @param {object} target Message container
* @param {object} content Content that changes dynamically that should
* be announced to screen readers.
* @return {Class} Newsletter
*/
_elementShow(target, content) {
target.classList.toggle(Newsletter.classes.HIDDEN);
Newsletter.classes.ANIMATE.split(' ').forEach((item) =>
target.classList.toggle(item)
);
// Screen Readers
target.setAttribute('aria-hidden', 'true');
if (content) content.setAttribute('aria-live', 'polite');
return this;
}
/**
* A proxy function for retrieving the proper key
* @param {string} key The reference for the stored keys.
* @return {string} The desired key.
*/
_key(key) {
return Newsletter.keys[key];
}
/**
* Setter for the Autocomplete strings
* @param {object} localizedStrings Object containing strings.
* @return {object} The Newsletter Object.
*/
strings(localizedStrings) {
Object.assign(this.STRINGS, localizedStrings);
return this;
}
}
/** @type {Object} API data keys */
Newsletter.keys = {
MC_RESULT: 'result',
MC_MSG: 'msg'
};
/** @type {Object} API endpoints */
Newsletter.endpoints = {
MAIN: '/post',
MAIN_JSON: '/post-json'
};
/** @type {String} The Mailchimp callback reference. */
Newsletter.callback = 'AccessNycNewsletterCallback';
/** @type {Object} DOM selectors for the instance's concerns */
Newsletter.selectors = {
ELEMENT: '[data-js="newsletter"]',
ALERT_BOXES: '[data-js-newsletter*="alert-box-"]',
WARNING_BOX: '[data-js-newsletter="alert-box-warning"]',
SUCCESS_BOX: '[data-js-newsletter="alert-box-success"]',
ALERT_BOX_TEXT: '[data-js-newsletter="alert-box__text"]'
};
/** @type {String} The main DOM selector for the instance */
Newsletter.selector = Newsletter.selectors.ELEMENT;
/** @type {Object} String references for the instance */
Newsletter.stringKeys = {
SUCCESS_CONFIRM_EMAIL: 'Almost finished...',
ERR_PLEASE_ENTER_VALUE: 'Please enter a value',
ERR_TOO_MANY_RECENT: 'too many',
ERR_ALREADY_SUBSCRIBED: 'is already subscribed',
ERR_INVALID_EMAIL: 'looks fake or invalid'
};
/** @type {Object} Available strings */
Newsletter.strings = {
VALID_REQUIRED: 'This field is required.',
VALID_EMAIL_REQUIRED: 'Email is required.',
VALID_EMAIL_INVALID: 'Please enter a valid email.',
VALID_CHECKBOX_BOROUGH: 'Please select a borough.',
ERR_PLEASE_TRY_LATER: 'There was an error with your submission. ' +
'Please try again later.',
SUCCESS_CONFIRM_EMAIL: 'Almost finished... We need to confirm your email ' +
'address. To complete the subscription process, ' +
'please click the link in the email we just sent you.',
ERR_PLEASE_ENTER_VALUE: 'Please enter a value',
ERR_TOO_MANY_RECENT: 'Recipient "{{ EMAIL }}" has too' +
'many recent signup requests',
ERR_ALREADY_SUBSCRIBED: '{{ EMAIL }} is already subscribed' +
'to list {{ LIST_NAME }}.',
ERR_INVALID_EMAIL: 'This email address looks fake or invalid.' +
'Please enter a real email address.',
LIST_NAME: 'ACCESS NYC - Newsletter'
};
/** @type {Array} Placeholders that will be replaced in message strings */
Newsletter.templates = [
'{{ EMAIL }}',
'{{ LIST_NAME }}'
];
Newsletter.classes = {
ANIMATE: 'animated fadeInUp',
HIDDEN: 'hidden'
};
export default Newsletter;