'use strict'
var umCurScriptElement = document.currentScript;
/**
* Global user menu component.
* @namespace userMenu
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
*/
(function (global) {
var userInfo = {
UID: false,
firstName: 'Account',
userType: 'student',
avatar: '',
digitalUrl: 'https://digital.ucas.com',
accountsUrl: 'https://accounts.ucas.com',
ucasComUrl: 'https://www.ucas.com'
}
/**
* Initialise the menu.
*
* This will attempt to derive the current user state from markup. This can
* then later be refreshed if needed.
*
* @function init
* @memberof! UCASDesignFramework.userMenu
* @example
* UCASDesignFramework.userMenu.init()
*/
function init () {
// Inject our CSS. This is included because this script is designed to be
// self contained.
var link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.onload = function () {
var userMenuContainer = document.querySelector('[data-uid]')
if (userMenuContainer) {
setUser({
UID: userMenuContainer.dataset.uid ? userMenuContainer.dataset.uid : false,
firstName: userMenuContainer.dataset.firstName ? userMenuContainer.dataset.firstName : userInfo.firstName,
userType: userMenuContainer.dataset.userPreference ? userMenuContainer.dataset.userPreference.toLowerCase() : userInfo.userType.toLowerCase(),
avatar: userMenuContainer.dataset.avatar ? userMenuContainer.dataset.avatar : userInfo.avatar,
digitalUrl: userMenuContainer.dataset.digitalDomain ? userMenuContainer.dataset.digitalDomain : userInfo.digitalUrl,
accountsUrl: userMenuContainer.dataset.accountsDomain ? userMenuContainer.dataset.accountsDomain : userInfo.accountsUrl,
ucasComUrl: userMenuContainer.dataset.ucascomDomain ? userMenuContainer.dataset.ucascomDomain : userInfo.ucasComUrl
})
updateUserMenuUI()
}
}
// The CSS file needs to be the same version as the javascript. By stripping
// the current known path of this file from the src of this file we can
// determine its origin.
if (umCurScriptElement && umCurScriptElement.src) {
var basePath = umCurScriptElement.src.split(/[?#]/)[0].replace('scripts/evergreen/user-menu.js', '')
link.href = basePath + 'stylesheets/optional/usermenu.css'
} else {
// currentScript is not supported by some older browsers (IE) but is
// polyfilled. In case of unexpected errors we can hard code the
// most likely appropriate path as belt and braces.
link.href = 'https://prod.df.ucascdn.com/evergreen/stylesheets/optional/usermenu.css'
}
var headScript = document.querySelector('script')
headScript.parentNode.insertBefore(link, headScript)
}
/**
* Update or set the global userInfo object.
*
* Technically the only required property is UID, the rest will be
* backfilled with defaults.
*
* @function setUser
* @param {object} newUserInfo
* See updateUserMenu
*/
function setUser (newUserInfo) {
userInfo.UID = newUserInfo.UID ? newUserInfo.UID : false
userInfo.firstName = newUserInfo.hasOwnProperty('firstName') ? newUserInfo.firstName : userInfo.firstName
userInfo.userType = newUserInfo.hasOwnProperty('userType') ? newUserInfo.userType.toLowerCase() : userInfo.userType.toLowerCase()
userInfo.avatar = newUserInfo.hasOwnProperty('avatar') ? newUserInfo.avatar : userInfo.avatar
userInfo.ucasComUrl = newUserInfo.hasOwnProperty('ucasComUrl') ? newUserInfo.ucasComUrl : userInfo.ucasComUrl
userInfo.digitalUrl = newUserInfo.hasOwnProperty('digitalUrl') ? newUserInfo.digitalUrl : userInfo.digitalUrl
userInfo.accountsUrl = newUserInfo.hasOwnProperty('accountsUrl') ? newUserInfo.accountsUrl : userInfo.accountsUrl
}
/**
* Inserts a personalised user menu.
*
* This method will be called from the Gigya handler file and will be passed
* a validated user object.
*
* @function updateUserMenuUI
*/
function updateUserMenuUI () {
var userMenuToggle = document.getElementById('user-menu-toggle')
var userMenuWrapper = document.getElementById('user-menu')
var subMenu
userMenuToggle.classList.remove('meganav__user-menu-toggle--loading')
var userMenuText = userMenuToggle.querySelector('.meganav__user-menu-text')
userMenuText.textContent = getText()
var avatar = userMenuToggle.querySelector('.meganav__user-menu-icon')
avatar.style.backgroundImage = 'url(' + getAvatar() + ')'
// Ensure we have some user info to display.
if (userInfo.UID) {
switch (userInfo.userType) {
case 'ucas':
case 'student':
subMenu = userMenuStudentSignedIn()
break
case 'provider':
subMenu = userMenuProviderSignedIn()
break
case 'agent':
case 'adviser':
subMenu = userMenuAdviserSignedIn()
break
default:
subMenu = userMenuStudentSignedIn()
}
} else {
subMenu = userMenuSignedOut()
}
userMenuWrapper.querySelector('.meganav__user-menu-wrapper').innerHTML = subMenu
}
/**
* Perform an update on the user menu.
* Updates the global user object and refreshes the menu itself accordingly.
*
* @function updateUserMenu
* @param {object} userInfo
* User object containing the following properties. They are all strings.
* - UID: The users user identifier if they are logged in, otherwise
* false, this is the main way of determining login status.
* - firstName: The users specified first name name if they are logged in,
* otherwise null.
* - userType: One of, student, provider, adviser. Used for personalising
* the menu.
* - avatar: A remote URL to the users avatar image.
* - ucasComUrl: Target ucascom system domain, used for rewriting link
* destinations.
* - digitalUrl: Target digitalUrl system domain, used for rewriting link
* destinations.
* - accountsUrl: Target accountsUrl system domain, used for rewriting link
* destinations.
* @memberof! UCASDesignFramework.userMenu
* @example
* UCASDesignFramework.userMenu.updateUserMenu()
*/
function updateUserMenu (userInfo) {
setUser(userInfo)
updateUserMenuUI()
}
/**
* Signed out user menu markup to inject.
*
* @return {string}
* Rendered signed out menu markup.
*/
function userMenuSignedOut () {
return '<ul class="user-menu">' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/students" class="user-menu__menu-link user-menu__menu-link--students">Students</a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.accountsUrl + '/account/login" class="user-menu__menu-link user-menu__menu-link--advisers">Advisers / Agents</a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.accountsUrl + '/account/login" class="user-menu__menu-link user-menu__menu-link--providers">Providers</a></li>' +
' </ul>'
}
/**
* Signed in user menu markup to inject.
*
* @return {string}
* Rendered signed in Student menu markup.
*/
function userMenuStudentSignedIn () {
return '<ul class="user-menu">' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/dashboard#/" class="user-menu__menu-link">Your Hub</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.digitalUrl + '/search/dashboard" class="user-menu__menu-link">Dashboard</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/explore/favourites" class="user-menu__menu-link">Favourites</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/account/profile" class="user-menu__menu-link">Profile</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/dashboard#/notifications" class="user-menu__menu-link">Notifications</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/dashboard#/preferences" class="user-menu__menu-link">Preferences</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/changepassword" class="user-menu__menu-link">Change password</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/changeemail" class="user-menu__menu-link">Change email</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/logout" class="user-menu__menu-link">Sign out</a>' +
' </li>' +
' </ul>' +
' <ul class="user-menu">' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/students" class="user-menu__menu-link">Students<small>Sign in to other student services</small></a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/advisers" class="user-menu__menu-link">Advisers / Agents</a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/providers" class="user-menu__menu-link">Providers</a></li>' +
' </ul>'
}
/**
* Signed in user menu markup to inject.
*
* @return {string}
* Rendered signed in Provider menu markup.
*/
function userMenuProviderSignedIn () {
return '<ul class="user-menu">' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.ucasComUrl + '/providers" class="user-menu__menu-link">Provider homepage</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.digitalUrl + '/search/dashboard" class="user-menu__menu-link">Dashboard</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/changepassword" class="user-menu__menu-link">Change password</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/logout" class="user-menu__menu-link">Sign out</a>' +
' </li>' +
' </ul>' +
' <ul class="user-menu">' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/students" class="user-menu__menu-link">Students<small>Sign in to other student services</small></a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/advisers" class="user-menu__menu-link">Advisers / Agents</a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/providers" class="user-menu__menu-link">Providers</a></li>' +
' </ul>'
}
/**
* Signed in user menu markup to inject.
*
* @return {string}
* Rendered signed in Adviser menu markup.
*/
function userMenuAdviserSignedIn () {
return '<ul class="user-menu">' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.digitalUrl + '/search/dashboard" class="user-menu__menu-link">Dashboard</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/changepassword" class="user-menu__menu-link">Change password</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/preferencesettings" class="user-menu__menu-link">Language preference</a>' +
' </li>' +
' <li class="user-menu__menu-item">' +
' <a href="' + userInfo.accountsUrl + '/account/logout" class="user-menu__menu-link">Sign out</a>' +
' </li>' +
' </ul>' +
' <ul class="user-menu">' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/students" class="user-menu__menu-link">Students<small>Sign in to other student services</small></a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/advisers" class="user-menu__menu-link">Advisers / Agents</a></li>' +
' <li class="user-menu__menu-item"><a href="' + userInfo.ucasComUrl + '/providers" class="user-menu__menu-link">Providers</a></li>' +
' </ul>'
}
/**
* Fetch the most appropriate avatar.
*
* @return {string}
* Either inline SVG for logged out avatar, or the URL to a user provided
* avatar ready to be injected into a src attribute.
*/
function getAvatar () {
var defaultLoggedOutAvatar = 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2033%2033%22%20width%3D%2235px%22%20height%3D%2235px%22%3E%3Cg%20transform%3D%22translate%28-1667%20-7%29%22%3E%3Ccircle%20cx%3D%2216.5%22%20cy%3D%2216.5%22%20r%3D%2216.5%22%20fill%3D%22%23ededed%22%20transform%3D%22translate%281667%207%29%22%2F%3E%3Cpath%20d%3D%22M1671.6%2035c1.6-6.5%208.2-10.6%2014.7-9%204.4%201.1%207.9%204.5%209%209-3.1%203.2-7.4%205-11.9%205-4.4%200-8.6-1.8-11.8-5z%22%20fill%3D%22%239d9d9d%22%2F%3E%3Cellipse%20cx%3D%221683.5%22%20cy%3D%2220.6%22%20rx%3D%227.9%22%20ry%3D%227.9%22%20fill%3D%22%23adadad%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E'
if (!userInfo.UID) {
return defaultLoggedOutAvatar
}
if (!userInfo.avatar) {
return getDefaultAvatar(userInfo.firstName)
}
return userInfo.avatar
}
/**
* Creates a user avatar
* @function getAvatar
* @param {string} userName - The name of the user
* @return {string} - The svg image for use as a background
*/
function getDefaultAvatar (userName) {
userName = userName || false
if (userName) {
var letter = userName.charAt(0).toUpperCase()
var letterCode = letter.charCodeAt(0)
// Limit to a palette of colours to ensure they fit with the theme and are readable
var colors = ['#e00023', '#759500', '#117882', '#1077d0', '#223992', '#752482']
var mappedNumber = ((letterCode >= 65) && (letterCode <= 90)) ? mapRange(letterCode, [65, 90], [0, 5], true) : 0
var avatarColour = encodeURIComponent(colors[mappedNumber])
letter = encodeURIComponent(sanitize(letter))
return 'data:image/svg+xml;charset=utf-8,%3Csvg%20height%3D%2238%22%20width%3D%2238%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2219%22%20cy%3D%2219%22%20fill%3D%22' + avatarColour + '%22%20r%3D%2219%22%3E%3C%2Fcircle%3E%3Ctext%20dy%3D%220.35em%22%20fill%3D%22white%22%20style%3D%22%20font-size%3A%2022px%3B%20font-family%3A%20%27Arial%27%2C%20sans-serif%3B%20text-anchor%3A%20middle%3B%20white-space%3A%20pre%3B%22%20x%3D%2219%22%20y%3D%2219%22%3E' + letter + '%3C%2Ftext%3E%3C%2Fsvg%3E'
}
return 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2033%2033%22%20width%3D%2235px%22%20height%3D%2235px%22%3E%3Cg%20transform%3D%22translate%28-1667%20-7%29%22%3E%3Ccircle%20cx%3D%2216.5%22%20cy%3D%2216.5%22%20r%3D%2216.5%22%20fill%3D%22%23414141%22%20transform%3D%22translate%281667%207%29%22%2F%3E%3Cpath%20d%3D%22M1671.6%2035c1.6-6.5%208.2-10.6%2014.7-9%204.4%201.1%207.9%204.5%209%209-3.1%203.2-7.4%205-11.9%205-4.4%200-8.6-1.8-11.8-5z%22%20fill%3D%22%23e3e3e3%22%2F%3E%3Cellipse%20cx%3D%221683.5%22%20cy%3D%2220.6%22%20rx%3D%227.9%22%20ry%3D%227.9%22%20fill%3D%22%23efefef%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E'
}
/**
* Map a value to a specific range.
* @function mapRange
* @param {number} number - The number being mapped
* @param {array} from - The range being mapped from
* @param {array} to - The range being mapped to
* @param {boolean} round - Whether to round to the nearest whole number
* @return {number} - The mapped number
*/
function mapRange (number, from, to, round) {
round = (round === undefined) ? true : round
var result = to[0] + (number - from[0]) * (to[1] - to[0]) / (from[1] - from[0])
return round ? Math.round(result) : result
}
/**
* Sanitizes a string.
* @function sanitize
* @param {string} string - The string to be sanitised
* @returns {string} - The sanitized string
*/
function sanitize (string) {
var map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`'
}
var reg = /[&<>"'/`]/ig
return string.replace(reg, function (match) {
return map[match]
})
}
/**
* Fetch the most appropriate user text tag.
*
* @return {string}
* The most appropriate text label to identify the current user state.
*/
function getText () {
if (!userInfo.UID) {
return 'Sign in'
}
return userInfo.firstName !== '' ? userInfo.firstName : 'Account'
}
// Expose public methods and properties.
global.userMenu = {
init: init,
initOnLoad: true,
updateUserMenu: updateUserMenu
}
})(UCASDesignFramework)