/**
* @file
* Handles site search functions.
*/
/* global jQuery */
'use strict'
/**
* @public {object} UCASDesignFramework - Global object.
*/
var UCASDesignFramework = UCASDesignFramework || {};
/**
* @public {object} UCASUtilities - Global object.
*/
var UCASUtilities = UCASUtilities || {};
/**
* @param {object} global - UCASDesignFramework object.
* @param {object} $ - jQuery.
*/
(function (global, _u, $) {
/**
* Initialise the plugin.
* @param context
*/
function init (context) {
var instances = $('[data-global-search]', context)
// Perform setup.
if (instances.length > 0) {
$.each(instances, function (index, instance) {
globalSearchPlugin(instance)
})
}
}
var globalSearchPlugin = function (instance) {
// @private
var ucasSearch = {}
// @private
var settings = {}
// @private
var dropdownTypes = ['firstDropdown', 'secondDropdown', 'thirdDropdown']
// @private
var activePlugin
// @private
var origin
// @private
var firstRun = true
// @private
var defaultPlugin = 'courses'
// @private
var defaultPluginOverride
/**
* Perform setup functions.
*/
function setup (instance) {
ucasSearch.form = $(instance)
// Set up location.origin for IE10.
if (!window.location.origin) {
window.location.origin = window.location.hostname + (window.location.port ? ':' + window.location.port : '')
}
origin = ucasSearch.form.data('globalSearchDomain') || window.location.origin
origin = origin.replace(/^https?:\/\//, '')
ucasSearch.input = ucasSearch.form.find('input[type="search"]').not('[readonly]')
ucasSearch.button = ucasSearch.form.find('input[type="submit"]')
ucasSearch.searchActive = false
defaultPluginOverride = ucasSearch.form.data('globalSearchPluginDefault') || defaultPlugin
var options = ucasSearch.form.data('globalSearchSettings')
// Extend the options passed via the initialisation call with some sensible defaults.
settings = $.extend({}, settings, options)
ucasSearch.plugins = getPlugins(settings)
// Create stub option tiers currently fixed to three. This could be improved to be more dynamic in future.
$.each(dropdownTypes, function (i, t) {
ucasSearch[t] = $('<select>', { class: 'global-search__' + t })
})
// Associate bindings.
bind()
// Trigger a change on first load.
ucasSearch[dropdownTypes[0]].trigger('change', { resetKeyword: false })
}
/**
* Bind events.
*/
function bind () {
var debug = false
// Bind change events to each drop down option.
$.each(dropdownTypes, function (index, type) {
ucasSearch[type].on('change', function (e, data) {
var resetKeyword = true
// Empty out types after the current.
$.each(dropdownTypes, function (i, t) {
if (i > index) { ucasSearch[t].detach().empty() }
})
// Act on arguments passed to the event.
if (data !== undefined) {
// Don't trigger a rebuild of the chain as this is part of a default or stored config change.
if (data.hasOwnProperty('rebuild')) { return }
// Don't wipe the keyword from the markup. This allows server side control of keywords for current searches.
resetKeyword = data.hasOwnProperty('resetKeyword') ? data.resetKeyword : true
}
changePlugin(resetKeyword)
})
})
// Create a close icon to collapse search options on mobile.
// As this is triggered on keyword focus we need ensure it is only done once.
ucasSearch.input.on('focus', function () {
if (!ucasSearch.searchActive) {
ucasSearch.searchActive = true
// Add a container class to control styling. This is hidden across the board on desktop but still gets created.
ucasSearch.form.addClass('global-search--active')
var closeIcon = $('<span>', { class: 'global-search__close' })
ucasSearch.form.before(closeIcon)
// Bind click to newly created icon.
closeIcon.on('click', function () {
ucasSearch.searchActive = false
ucasSearch.form.removeClass('global-search--active')
closeIcon.remove()
})
}
})
// Final submit action for the form.
// Construct a full URI and redirect to it.
ucasSearch.form.submit(function (e) {
e.preventDefault()
var keywords = ucasSearch.input.val()
// Record the search with Google Tag Manager.
$(UCASDesignFramework).trigger('searchSubmitted', {
searchScheme: getConfigKey(),
searchKeyword: keywords.toString().toLowerCase()
})
// Allow a callback to modify the query.
if (activePlugin.hasOwnProperty('modifyCallback')) {
// If this is a longer object path we just need to get the last part for the callback.
var callback = activePlugin.modifyCallback.split('.')
.reduce(function (object, property) {
return (object[property]) ? object[property] : false
}, window)
// Check the function exists then execute it.
if ($.isFunction(callback)) {
activePlugin = callback(activePlugin)
} else {
UCASUtilities.log.log(callback + ' is not a function. It is defined as type: ' + typeof callback)
}
}
// Enable debugging output paths.
if (activePlugin.hasOwnProperty('debug') && (activePlugin.debug === 'true' || activePlugin.debug === true)) {
debug = true
}
var query = buildQuery(keywords)
// Determine if we are updating an existing path or directing to a new one.
var redirectPath = query.path
if (activePlugin.retainQuery) {
var currentPath = window.location.pathname
// If we don't have an optional path set a default that will always be true, so if we do have one the below test can
// be skipped.
var optionalPath = activePlugin.hasOwnProperty('optionalPath') ? activePlugin.optionalPath : '/'
// We are already on the search page so simply replace the keywords query argument to avoid wiping other query arguments.
if (currentPath.indexOf(activePlugin.path) === 0 && currentPath.indexOf(optionalPath) !== -1) {
redirectPath = updateQueryStringParameter(window.location.href, query.keywordParameter, sanitizeUrl(encodeURIComponent(keywords)))
if (activePlugin.hasOwnProperty('args')) {
for (var p in activePlugin.args) {
if (activePlugin.args.hasOwnProperty(p)) {
// Replace other args token.
if (p !== query.keywordParameter) {
redirectPath = updateQueryStringParameter(redirectPath, encodeURIComponent(p), sanitizeUrl(encodeURIComponent(activePlugin.args[p])))
}
}
}
}
}
}
// Perform final redirect.
if (debug) {
UCASUtilities.log.log(redirectPath)
} else {
window.location = redirectPath
}
})
}
/**
* Build a query from the globally active plugin.
* @param {string} keywords - The user defined keyword string.
* @returns {object} - Full URI to the search query plus the current keyword query parameter.
*/
function buildQuery (keywords) {
var keywordParameter = 'none'
// If we are on a development site and redirecting to the same origin use a relative path.
if (activePlugin.domain.replace(/^https?:\/\//, '') === origin) {
activePlugin.domain = ''
}
var outputPath = activePlugin.domain + activePlugin.path
if (activePlugin.hasOwnProperty('optionalPath')) {
outputPath += activePlugin.optionalPath
}
// Failsafe if we don't have arguments.
if (!activePlugin.hasOwnProperty('args')) {
return { path: outputPath, keywordParameter: false }
}
// Loop over all the arguments and construct a querystring.
var queryString = []
for (var p in activePlugin.args) {
if (activePlugin.args.hasOwnProperty(p)) {
// Replace keywords token.
if (activePlugin.args[p] === '{keywords}') {
keywordParameter = p
activePlugin.args[p] = keywords
}
queryString.push(encodeURIComponent(p) + '=' + sanitizeUrl(encodeURIComponent(activePlugin.args[p])))
}
}
return { path: outputPath + '?' + queryString.join('&'), keywordParameter: keywordParameter }
}
/**
* Get a "parent chain" by starting at the current item and working backwards.
*
* @param {string} key - The key of the item whose parents should be found.
* @param {object} plugin - A copy of the object whose parents should be found.
* @returns {string} - A pipe delimited chain of parent keys, starting with the root element and ending with the
* element that was passed in. Note that this might ONLY contain the element key for the element
* passed in, if it had no parents.
*/
function findParentChain (key, plugin) {
var output = key
if (plugin.hasOwnProperty('parent') && ucasSearch.plugins.hasOwnProperty(plugin.parent)) {
output = findParentChain(plugin.parent, ucasSearch.plugins[plugin.parent]) + '|' + output
}
return output
}
/**
* Find a plugin by domain and path.
*
* @param {string} domain - Domain name to match on, can be partial. Protocol must be removed.
* @param {object} windowlocation - Object with information about the current location of the document
* @returns {object} - An object with the matching plugin object inside it, including its key. An empty object if
* there was no match.
*/
function findPlugin (domain, windowlocation) {
var path = windowlocation.pathname
var output = {}
// Start a search for the most specific plugin we can find including optionalPath if we have one.
$.each(ucasSearch.plugins, function (key, plugin) {
// We require an exact match on the domain, including http:// and the port, and we require the plugin's path
// setting to be at the START of the current path from the current URL. We also match on the optional path anywhere.
if (plugin.hasOwnProperty('domain') && plugin.domain.replace(/^https?:\/\//, '') === domain &&
plugin.hasOwnProperty('path') && path.indexOf(plugin.path) === 0 &&
plugin.hasOwnProperty('optionalPath') && path.indexOf(plugin.optionalPath) !== -1) {
output[key] = plugin
return false
}
})
// If we found something return it here.
if (!$.isEmptyObject(output)) {
return output
}
$.each(ucasSearch.plugins, function (key, plugin) {
// We require an exact match on the domain, including http:// and the port, and we require the plugin's path
// setting to be at the START of the current path from the current URL. We also require the destination filter
// to match e.g. Destination_Undergraduate / Destination_Postgraduage / Scheme_UCAS+Conservatoires.
if (plugin.hasOwnProperty('domain') && plugin.domain.replace(/^https?:\/\//, '') === domain &&
plugin.hasOwnProperty('path') && path.indexOf(plugin.path) === 0 &&
plugin.hasOwnProperty('args') &&
plugin.args.hasOwnProperty('filters') &&
windowlocation.search.indexOf(plugin.args.filters) !== -1) {
output[key] = plugin
return false
}
})
// If we found something return it here.
if (!$.isEmptyObject(output)) {
return output
}
// Start a more generalised search on just the domain and base path.
$.each(ucasSearch.plugins, function (key, plugin) {
// We require an exact match on the domain, including http:// and the port, and we require the plugin's path
// setting to be at the START of the current path from the current URL.
if (plugin.hasOwnProperty('domain') && plugin.domain.replace(/^https?:\/\//, '') === domain &&
plugin.hasOwnProperty('path') && path.indexOf(plugin.path) === 0) {
output[key] = plugin
return false
}
})
// If we found something return it here.
if (!$.isEmptyObject(output)) {
return output
}
// Check if we have a localStorage item, in which case return nothing here and getConfigKey will pick it up.
if (UCASDesignFramework.storage('ucasSearch-current') !== null) {
return {}
}
// It's possible the node has not been found and we have no more options, so we're returning false here.
return output
}
/**
* Generate a global key suitable for localStorage which defines the currently selected search.
* @return {string} - Pipe delimited string of plugin keys.
*/
function getConfigKey () {
var output = ''
// Build an array of selected options. We don't care if they are empty here.
$.each(dropdownTypes, function (i, t) {
var value = ucasSearch[t].find('option:selected').attr('value')
if (value !== undefined) {
output = output + value + '|'
}
})
// If we don't have any values we can assume this is a 'fresh' page load. Lets see if we can determine an
// intelligent starting point for the user. We will always force the user to see the "relevant" options on a
// specific page, such as on events pages, or undergraduate search.
if (output === '') {
var currentPlugin = findPlugin(origin, window.location)
if (Object.keys(currentPlugin).length === 0) {
// We did not locate a current plugin based on the domain and path. Try local storage, or fall back to the
// default key.
output = validifyPluginName(UCASDesignFramework.storage('ucasSearch-current'))
} else {
// We have our current plugin, so determine the correct chain to use to select the right dropdowns. currentPlugin
// could contain multiple plugins, so we just choose the first from the list in order of weight.
output = findParentChain(Object.keys(currentPlugin)[0], currentPlugin[Object.keys(currentPlugin)[0]])
}
// If this is a search that doesn't have keywords wipe the output, this is to stop users getting stuck with no way
// to open the dropdown filters.
if (firstRun === true && currentPlugin.hasOwnProperty('keywords') && currentPlugin.keywords === false) {
output = defaultPluginOverride
firstRun = false
}
} else {
// Remove the last pipe off the end for cleanliness.
if (output.slice(-1) === '|') {
output = output.slice(0, -1)
}
}
UCASDesignFramework.storage('ucasSearch-current', output)
return output
}
/**
* Make sure that whatever we pull out of local storage is a valid plugin name.
* If it isn't, we use the default.
*
* @param {string} value
* @return {string}
*/
function validifyPluginName (value) {
if (!value) {
return defaultPluginOverride
} else {
var plugins = getPlugins(settings)
var validNames = Object.keys(plugins)
// UCASDesignFramework.storage returns any type but we want a string.
var parts = value.toString().split('|')
var firstValue = parts[0]
// Check all the parts are valid.
_u.forEach(parts, function (i, el) {
if (validNames.indexOf(el) === -1) {
value = defaultPluginOverride
}
})
// Only use the value if the first part is tier 0.
return plugins[firstValue] && plugins[firstValue]['tier'] === 0 ? value : defaultPluginOverride
}
}
function getChildren (parentKey) {
var children = {}
$.each(ucasSearch.plugins, function (key, data) {
// Handle root level items which have no parent key.
if (parentKey === '') {
if (!data.hasOwnProperty('parent')) {
children[key] = data
return true
} else {
return true
}
} else {
// If the parent of 'data' is not found, we cannot continue.
if (!ucasSearch.plugins.hasOwnProperty(data.parent)) {
return true
}
if (data.hasOwnProperty('parent') && data.parent === parentKey) {
children[key] = data
return true
}
}
})
return children
}
/**
* Change the active search plugin.
*
* @param {boolean} reset - If to reset the default value or not.
*/
function changePlugin (reset) {
var plugin
var name = getConfigKey()
var config = name.split('|')
// Get root level elements to begin with.
var types = getChildren('')
$.each(dropdownTypes, function (position, key) {
// Select the current item from config, otherwise just get the first key from the list.
var defaultKey = config.hasOwnProperty(position) ? config[position] : Object.keys(types)[0]
// Populate the active dropdown and set the active term.
populateDropdown(types, position, defaultKey)
var children = getChildren(defaultKey)
if (Object.keys(children).length > 0) {
types = children
} else {
// Check the plugin exists then mark it as active, otherwise select the first from the list.
plugin = ucasSearch.plugins.hasOwnProperty(defaultKey) ? ucasSearch.plugins[defaultKey] : Object.keys(types)[0]
// Belt and braces, if for some reason the plugin has been removed, just select the default.
if (!$.isPlainObject(plugin)) {
plugin = ucasSearch.plugins[defaultPlugin]
}
// Break out of the loop early as we found what we were looking for.
return false
}
})
// Clear the current keywords.
if (reset) {
ucasSearch.input.val('')
if (!plugin.hasOwnProperty('keywords')) {
ucasSearch.input.focus()
} else {
ucasSearch.button.focus()
}
}
// Update placeholder text on keyword input
if (plugin.hasOwnProperty('placeholder')) {
ucasSearch.input.attr('placeholder', plugin.placeholder)
}
// Switch keyword field functionality.
if (plugin.hasOwnProperty('keywords') && plugin.keywords === false) {
ucasSearch.form.addClass('global-search--no-keyword')
ucasSearch.input.val('').prop('disabled', true)
ucasSearch.searchActive = true
// Add a container class to control styling. This is hidden across the board on desktop but still gets created.
ucasSearch.form.addClass('global-search--active')
ucasSearch.form.parent().find('.global-search__close').remove()
} else {
if (!firstRun) {
ucasSearch.form.removeClass('global-search--no-keyword')
ucasSearch.input.prop('disabled', false)
ucasSearch.searchActive = true
ucasSearch.form.parent().find('.global-search__close').remove()
// Add a container class to control styling. This is hidden across the board on desktop but still gets created.
ucasSearch.form.addClass('global-search--active')
var closeIcon = $('<span>', {class: 'global-search__close'})
ucasSearch.form.before(closeIcon)
// Bind click to newly created icon.
closeIcon.on('click', function () {
ucasSearch.searchActive = false
ucasSearch.form.removeClass('global-search--active')
closeIcon.remove()
})
}
}
firstRun = false
// Trigger the active plugin event.
$(ucasSearch.form).trigger('changePlugin', { plugin: plugin, key: name })
// Assign the global.
activePlugin = plugin
}
/**
* Create a drop down list at a given position with a specific active option.
*
* @param {object} types
* @param {int} position - Index position in dropdownTypes array
* @param {string} activeKey - Active value in the drop down
*/
function populateDropdown (types, position, activeKey) {
var activeDropdown = ucasSearch[dropdownTypes[(position)]]
activeDropdown.removeAttr('disabled')
activeDropdown.empty()
$.each(types, function (name, plugin) {
var option = $('<option>').attr('value', name).text(plugin.label)
activeDropdown.append(option)
})
if (Object.keys(types).length <= 1) {
activeDropdown.attr('disabled', 'disabled')
}
activeDropdown.find('[value="' + activeKey + '"]').prop('selected', true)
// Determine position. The first element needs a fixed location before the keyword container.
if (position === 0) {
ucasSearch['input'].parents('.form-item__search_field').before(activeDropdown)
} else {
// Insert subsequent dropdowns after the previous one.
ucasSearch[dropdownTypes[position - 1]].after(activeDropdown)
}
// Ensure any new default drop downs get built after this, but mark the current element for no rebuild.
activeDropdown.trigger('change', { rebuild: false, resetKeyword: true })
}
/**
* Get the active plugins.
*
* @param {object} settings - Global settings object.
* @return {object} List of search plugin definitions.
*/
function getPlugins (settings) {
var defaults = pluginDefaults
var custom = {}
if (typeof settings.plugins !== 'undefined') {
custom = settings.plugins
}
var plugins = $.extend(true, defaults, custom)
// Remove disabled plugins.
var disabled = ucasSearch.form.data('globalSearchDisabled')
if (disabled) {
disabled = disabled.split(', ')
for (var count = 0; count < disabled.length; count++) {
if (plugins.hasOwnProperty(disabled[count])) {
delete plugins[disabled[count]]
}
}
}
// Sort the plugins in order of weight.
var keysSorted = Object.keys(plugins).sort(function (a, b) { return plugins[a]['weight'] - plugins[b]['weight'] })
var sorted = {}
for (var i = 0; i < keysSorted.length; i++) {
sorted[keysSorted[i]] = plugins[keysSorted[i]]
}
return sorted
}
/**
* Helper function for replacing a single query argument and returning the url.
*
* @param uri
* The URL to work with.
* @param key
* The query argument key.
* @param value
* The value to set on the key.
*
* @return {string}
* A the final parsed URL.
*/
function updateQueryStringParameter (uri, key, value) {
var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i')
var separator = uri.indexOf('?') !== -1 ? '&' : '?'
if (uri.match(re)) {
return uri.replace(re, '$1' + key + '=' + value + '$2')
} else {
return uri + separator + key + '=' + value
}
}
/**
* A set of default searches configurations. This will be replaced with something more dynamic at some point.
* @type {object}
*/
// @private
var pluginDefaults = {
information: {
domain: 'https://www.ucas.com',
path: '/search/site',
optionalPath: '/type/article/type/structure_content/type/landing_page',
retainQuery: true,
args: {
keywords: '{keywords}'
},
label: 'Information and advice',
placeholder: 'Search articles, information and advice by keyword',
weight: 2,
tier: 0
},
courses: {
label: 'Courses',
weight: 1,
tier: 0
},
undergraduate: {
label: 'Undergraduate',
placeholder: 'Search for undergraduate courses by keyword',
domain: 'https://digital.ucas.com',
path: '/search/results',
retainQuery: false,
modifyCallback: 'ucas.searchbarcallback.initPlugins',
args: {
SearchText: '{keywords}',
filters: 'Destination_Undergraduate'
},
parent: 'courses',
weight: 1,
tier: 1
},
postgraduate: {
label: 'Postgraduate',
placeholder: 'Search for postgraduate courses by keyword',
domain: 'https://digital.ucas.com',
path: '/search/results',
retainQuery: false,
modifyCallback: 'ucas.searchbarcallback.initPlugins',
args: {
SearchText: '{keywords}',
filters: 'Destination_Postgraduate'
},
weight: 2,
parent: 'courses',
tier: 1
},
conservatoires: {
label: 'Conservatoires',
placeholder: 'Search for conservatoire courses by keyword',
domain: 'https://digital.ucas.com',
path: '/search/results',
retainQuery: false,
modifyCallback: 'ucas.searchbarcallback.initPlugins',
args: {
SearchText: '{keywords}',
filters: 'Scheme_UCAS+Conservatoires'
},
parent: 'courses',
weight: 3,
tier: 1
},
utt: {
label: 'Teacher Training',
placeholder: 'Search for postgraduate teacher training courses',
domain: 'http://search.gttr.ac.uk',
path: '/cgi-bin/hsrun.hse/General/2018_gttr_search/gttr_search.hjx;start=gttr_search.HsForm.run',
retainQuery: false,
keywords: false,
weight: 4,
parent: 'courses',
tier: 1
},
progress: {
label: '16-18 Choices',
placeholder: 'Search for courses for 16-18 year olds',
domain: 'https://www.ucasprogress.com',
path: '/search',
retainQuery: false,
keywords: false,
weight: 5,
parent: 'courses',
tier: 1
},
apprenticeships: {
label: 'Apprenticeships',
placeholder: 'Search for higher and degree apprenticeships in England',
domain: 'https://careerfinder.ucas.com',
path: '/jobs/apprenticeship/',
retainQuery: false,
keywords: false,
weight: 6,
parent: 'courses',
tier: 1
},
events: {
label: 'Events and key dates',
weight: 3,
tier: 0
},
openday: {
label: 'Open days',
placeholder: 'Find open days',
domain: 'https://www.ucas.com',
path: '/ucas/events/find',
optionalPath: '/type/open-day',
retainQuery: true,
args: {
keywords: '{keywords}'
},
parent: 'events',
tier: 1
},
exhibition: {
label: 'Exhibitions',
placeholder: 'Find exhibitions',
domain: 'https://www.ucas.com',
path: '/ucas/events/find',
optionalPath: '/type/exhibition',
retainQuery: true,
args: {
keywords: '{keywords}'
},
parent: 'events',
tier: 1
},
conference: {
label: 'Conferences',
placeholder: 'Find conferences',
domain: 'https://www.ucas.com',
path: '/ucas/events/find',
optionalPath: '/type/conference',
retainQuery: true,
args: {
keywords: '{keywords}'
},
parent: 'events',
tier: 1
},
keydate: {
label: 'Key dates',
placeholder: 'Find key dates',
domain: 'https://www.ucas.com',
path: '/ucas/events/find',
optionalPath: '/type/key-date',
retainQuery: true,
args: {
keywords: '{keywords}'
},
parent: 'events',
tier: 1
},
documents: {
label: 'Documents and data',
placeholder: 'Find documents, data files, and downloads',
weight: 4,
domain: 'https://www.ucas.com',
path: '/data/documents/search',
retainQuery: true,
args: {
keywords: '{keywords}'
},
tier: 0
},
videos: {
label: 'Videos',
placeholder: 'Find videos on the UCAS video wall',
weight: 5,
domain: 'https://www.ucas.com',
path: '/connect/videos',
retainQuery: true,
args: {
combine: '{keywords}'
},
tier: 0
}
}
/**
* Sanitizes a string for use in a query argument.
*
* @param string
* @returns {string|*}
*/
function sanitizeUrl (string) {
string = replaceAll(string, '%2B', '+')
string = replaceAll(string, ' ', '+')
string = replaceAll(string, '%20', '+')
return string.replace(/['()]/g, escape)
}
/**
* Replaces all occurances of a term in a string.
*
* @param string
* @param search
* @param replacement
* @returns {string}
*/
function replaceAll (string, search, replacement) {
return string.split(search).join(replacement)
}
setup(instance)
}
/**
* Assign public methods.
*/
global.globalSearch = {
init: init
}
})(UCASDesignFramework, UCASUtilities, jQuery);
/**
* Simple localStorage with Cookie Fallback
*
* This is designed to handle cases like iOS incognito. Which reports localStorage as being available but fails when you
* actually try to store something.
*
* @param {string} key
* The key to identify the stored item.
* @param {string} value
* Optional. The value to set.
*
* @return {*}
* Mixed, could be nothing or could be the requested value.
*
* Usage:
* Set New/modify:
* storage('key', 'value');
* Retrieve:
* storage('key');
* Delete/remove:
* storage('key', null);
*/
UCASDesignFramework.storage = function storage (key, value) {
'use strict'
var lsSupport = false
// Check for native support
function storageAvailable (type) {
try {
var storage = window[type]
var x = '__storage_test__'
storage.setItem(x, x)
storage.removeItem(x)
return true
} catch (e) {
return false
}
}
if (storageAvailable('localStorage')) {
// We can use localStorage awesomeness
lsSupport = true
}
// If value is detected, set new or modify store
if (typeof value !== 'undefined' && value !== null) {
// Convert object values to JSON
if (typeof value === 'object') {
value = JSON.stringify(value)
}
// Set the store
if (lsSupport) { // Native support
localStorage.setItem(key, value)
} else {
// Use Cookie
createCookie(key, value, 30)
}
}
// No value supplied, return value
if (typeof value === 'undefined') {
var data
// Get value
if (lsSupport) {
// Native support
data = localStorage.getItem(key)
} else {
// Use cookie
data = readCookie(key)
}
// Try to parse JSON...
try {
data = JSON.parse(data)
} catch (e) {
// Do nothing, data will be passed as-is.
}
return data
}
// Null specified, remove store
if (value === null) {
if (lsSupport) {
// Native support
localStorage.removeItem(key)
} else {
// Use cookie
createCookie(key, '', -1)
}
}
/**
* Creates new cookie or removes cookie with negative expiration.
*
* @param {string} key
* The key or identifier for the store
* @param {string} value
* Contents of the store
* @param {int} exp
* Expiration - creation defaults to 30 days
*/
function createCookie (key, value, exp) {
var date = new Date()
date.setTime(date.getTime() + (exp * 24 * 60 * 60 * 1000))
var expires = '; expires=' + date.toGMTString()
document.cookie = key + '=' + value + expires + '; path=/; SameSite=None; Secure;'
}
/**
* Returns contents of cookie.
*
* @param {string} key
* The key or identifier for the store
*
* @return {*}
* The value of the cookie, otherwise null.
*/
function readCookie (key) {
var nameEQ = key + '='
var ca = document.cookie.split(';')
for (var i = 0, max = ca.length; i < max; i++) {
var c = ca[i]
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length)
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length)
}
}
return null
}
}