Last active
March 11, 2021 13:01
-
-
Save iOnline247/f2dd4307fedc3661c8eae4705ac8717e to your computer and use it in GitHub Desktop.
Create a bulletproof configuration list for custom SharePoint apps.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Created by Matthew Bramer | |
* Released under the MIT license | |
* Date: 2016-08-05 | |
* Tested using SharePoint Online. | |
*/ | |
// Get $sp.min.js from here. | |
// https://gist.github.com/iOnline247/cc8d68cb611b056695434224e6c2aa19 | |
import '../vendor/$sp.min'; | |
(async () => { | |
const rest = window.$sp(); | |
const DEFAULT_EXPIRY_DAYS = 1; | |
const CONFIG_LIST_NAME = 'ConfigList'; | |
// #region Utils | |
function debug(...args) { | |
console.error(...args); | |
// eslint-disable-next-line no-debugger | |
debugger; | |
} | |
function dateInFuture(daysInFuture) { | |
const date = new Date(); | |
date.setDate(date.getDate() + daysInFuture); | |
return date; | |
} | |
function addItem(item) { | |
return rest.add({ | |
url: `/_api/web/lists/getByTitle('${CONFIG_LIST_NAME}')/items`, | |
data: { | |
Expires: dateInFuture(DEFAULT_EXPIRY_DAYS).toISOString(), | |
Title: item.Title, | |
JSON: item.JSON, | |
}, | |
}); | |
} | |
function updateItem(item) { | |
return rest.update({ | |
url: `/_api/web/lists/getByTitle('${CONFIG_LIST_NAME}')/items(${item.Id})`, | |
data: { | |
Expires: dateInFuture(DEFAULT_EXPIRY_DAYS).toISOString(), | |
JSON: item.JSON, | |
}, | |
}); | |
} | |
// #endregion | |
const dataConfigs = { | |
spaceX: async () => { | |
const output = { | |
// Gets the name of this function, so the key will be the same for the list. | |
// This is important later on in the logic. | |
Title: dataConfigs.spendingData.name, | |
}; | |
const data = await someApiCall(); | |
// Useful for debugging. | |
// output.JSON = JSON.stringify(data, null, '\t'); | |
output.JSON = JSON.stringify(data); | |
return output; | |
}, | |
test1: () => ({ | |
Title: dataConfigs.test1.name, | |
JSON: JSON.stringify({ test1: true }), | |
}), | |
test2: () => ({ | |
Title: dataConfigs.test2.name, | |
JSON: JSON.stringify({ test2: true }), | |
}), | |
}; | |
function createConfigs(configs) { | |
// At this point, we have raw data from any missing config keys. | |
// format will be this. | |
// { | |
// Title: 'somekey', | |
// JSON: '{}' | |
// } | |
// We also have the existing data, that hasn't expired. | |
// This is stored at the end of `args` | |
// Find the unexpired config, so we can slice it off. | |
const spData = configs[configs.length - 1]; | |
const newJsonConfigs = configs.slice(0, configs.length - 1); | |
// Add the missing JSON to the config list. | |
newJsonConfigs.forEach((v) => addItem(v).catch(debug)); | |
// Join everything together in one array for promise handoff. | |
return newJsonConfigs.concat(spData.existingData); | |
} | |
async function getDataFromMissingConfigs(data) { | |
const { results } = data.d; | |
const configKeys = Object.keys(dataConfigs); | |
// We'll look at this object and compare with items that already exist. | |
const configData = configKeys | |
.filter(function findNonExistentConfigs(v) { | |
return !results.some((item) => item.Title === v); | |
}) | |
// Now we have an array of the configs that *should* exist, but do not | |
// in SharePoint. Maybe they were deleted or never there... | |
// Let's fetch those from the API and return an array of promises. | |
.map((key) => dataConfigs[key]()) | |
// concat list data. | |
// this will allow usage later on. | |
.concat({ | |
existingData: results, | |
}); | |
const configs = await Promise.all(configData); | |
return createConfigs(configs); | |
} | |
function separateByExpiryDate(configs) { | |
const now = new Date(); | |
const expiredConfigs = configs.filter((v) => new Date(v.Expires) < now); | |
const nonExpiredConfigs = configs.filter( | |
(v) => new Date(v.Expires) >= now | |
); | |
return { | |
nonExpiredConfigs, | |
expiredConfigs, | |
}; | |
} | |
async function refreshStaleDataFromDefinitions(staleData) { | |
// Convert all staleData configs to an array of promises. | |
const updateDataCalls = staleData.reduce(function refreshData( | |
output, | |
v | |
) { | |
const freshDataFunc = dataConfigs[v.Title]; | |
const isFunction = typeof freshDataFunc === 'function'; | |
if (isFunction) { | |
output.push(freshDataFunc()); | |
} else { | |
console.warn( | |
`!!! ${v.Title} is not defined as a function in dataConfigs !!!` | |
); | |
} | |
return output; | |
}, | |
[]); | |
const freshData = await Promise.all(updateDataCalls); | |
return freshData; | |
} | |
function updateSharePointConfigs(dataNeedsUpdating, existingData) { | |
dataNeedsUpdating.forEach((v) => { | |
const currConfig = existingData.find((j) => j.Title === v.Title); | |
// eslint-disable-next-line no-param-reassign | |
v.Id = currConfig.Id; | |
updateItem(v).catch(debug); | |
}); | |
} | |
function signalEvent(data) { | |
const event = new CustomEvent('configList:ready', { detail: data }); | |
// jQuery will have the data on the event.originalEvent.detail property. | |
document.dispatchEvent(event); | |
} | |
let allConfigData; | |
try { | |
const configListData = await rest.get( | |
`/_api/web/lists/getByTitle('${CONFIG_LIST_NAME}')/items?$select=Title,JSON,Expires,Id` | |
); | |
allConfigData = await getDataFromMissingConfigs(configListData); | |
const { nonExpiredConfigs, expiredConfigs } = separateByExpiryDate( | |
allConfigData | |
); | |
const dataNeedsUpdatingInConfigList = await refreshStaleDataFromDefinitions( | |
expiredConfigs | |
); | |
const freshData = nonExpiredConfigs.concat( | |
dataNeedsUpdatingInConfigList | |
); | |
signalEvent(freshData); | |
updateSharePointConfigs(dataNeedsUpdatingInConfigList, allConfigData); | |
} catch (err) { | |
// Fallback to what is in the configList at this moment. | |
signalEvent(allConfigData); | |
debug(err); | |
} | |
})(); | |
/* | |
* Usage | |
* | |
* | |
// Bind to the ready event | |
document.addEventListener('configList:ready', (e) => console.log(e.detail)); | |
// jQuery: Bind to the ready event | |
$(document).one('configList:ready', function (event) { | |
console.log(event.originalEvent.detail); | |
}); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The list should have these columns: