UNPKG

q42-cms-components

Version:

Front-end package that provides a UI on top of the QMS back-end

267 lines (227 loc) 8.21 kB
import Vue from 'vue'; const serverPath = (document.documentElement.dataset.apiDomain || '') + '/api'; function toServerJson(json) { if (json instanceof Array) { return json.map(toServerJson); } if (json instanceof Date) { return json.toISOString(); } if (json instanceof Object) { var result = {}; for (var p in json) { result[p.charAt(0).toUpperCase() + p.substr(1)] = toServerJson(json[p]); } return result; } return json; } function fromServerJson(json) { if (json instanceof Array) { return json.map(fromServerJson); } if (json instanceof Object) { var result = {}; for (var p in json) { result[p.charAt(0).toLowerCase() + p.substr(1)] = fromServerJson(json[p]); } return result; } if (json && json.match instanceof Function && json.match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|[-+]\d\d:\d\d)?$/)) { return new Date(json); } return json; } const globalHeaders = {}; export function setGlobalHeader(name, value) { globalHeaders[name] = value; } export class ServerStore { static get docs() { return `ServerStore is een class die helpt om een reactive data source (een Vue instance zonder template) te bouwen om een REST API. <code>options</code> is een object met als properties: <ul> <li><code>loaders</code> Object met voor elk data property een functie die de API aanroept, bijv: <div><code>{ persons: () => this.fetch('/items/persons') }</code></div> <li><code>initial</code> De initiele waarde voor elk data property. bijv: <div><code>{ persons: [] }</code></div> <li><code>computed</code> Computed properties o.b.v. de data properties. bijv: <div><code>{ isInhabited: () => this.persons.length > 0 }</code></div> </ul> Elk property in <code>loaders</code> wordt een reactive property op de store. Voor elk property wordt ook een updater aangemaakt die je kan aanroepen na een andere API call waarvan je weet dat die waarde van dat property verandert. Bijvoorbeeld na het posten van een nieuw persoon, moet de lijst van personen opnieuw worden opgehaald: <div><code>this.postJson('/items/persons', newPerson).then(this.updaters.persons)</code></div>` } constructor(options) { const { initial = {}, loaders = {}, computed = {}, watch = {} } = options || {}; // is a loader currently running? var isLoading = {}; // has a loader ever been called? var hasLoaded = {}; // has a loader an error while loading? var hasError = {}; // a loader just fetches data this.loaders = {}; // an updater triggers a loader if it is not already running, and stores the result in state this.updaters = {}; // a getter reads from the state and triggers the updater if it is called for the first time this.getters = {}; Object.keys(loaders).forEach(prop => { isLoading[prop] = false; hasLoaded[prop] = false; hasError[prop] = false; this.loaders[prop] = noCache => { if (hasLoaded[prop] && !noCache) { return Promise.resolve(this.state[prop]); } isLoading[prop] = true; hasError[prop] = false; return loaders[prop]() .then((value) => { this.state[prop] = value; hasLoaded[prop] = true; isLoading[prop] = false; return value; }) .catch(() => { isLoading[prop] = false; hasError[prop] = true; return null; }); }; this.updaters[prop] = (passThrough) => { if (!isLoading[prop]) { this.loaders[prop](true); } return passThrough; } this.getters[prop] = () => { if (!hasLoaded[prop]) { this.updaters[prop]() } return this.state[prop]; } Object.defineProperty(this, prop, { get: this.getters[prop] }); }); Object.keys(computed).forEach(prop => { Object.defineProperty(this, prop, { get: () => this.state[prop] }); }); this.state = new Vue({ data: { ...initial, isLoading, hasLoaded, hasError }, computed, watch }); } /** * checks if any loader is currently fetching for the first time */ get isLoading() { for (var prop in this.state.isLoading) { if (this.state.isLoading[prop] && !this.state.hasLoaded[prop]) { return true; } } return false; } static get isLoadingDocs() { return `Geeft aan of de store bezig is met laden van de data.` } /** * Parse response as json and the reject responses that are not "ok". */ handleResponse(response) { // case: singletonFactory can return a 204 on /{itemName}/latest if (response.status === 204) { return null; } return response.json() .then(fromServerJson) .then(result => { // status range 200-299 if (response.ok) { return result; } if (response.status === 401) { console.error(`Deze gebruiker heeft geen toegang tot ${response.url}`, result); return Promise.reject(result); } console.error('De server geeft een fout terug.', result); return Promise.reject(result); }) .catch(error => { console.error('Een error in handleResponse na een fetch call: ', error); return Promise.reject(error); }); } /** * Fetch has no url parameter support */ url(path, params) { return params ? serverPath + path + "?" + Object.keys(params).map(key => key + '=' + encodeURIComponent(params[key])).join('&') : serverPath + path; } fetch(path, params, options) { options = options || {}; options.credentials = 'include'; options.headers = {...globalHeaders, ...options.headers}; return fetch(this.url(path, params), options).then(this.handleResponse); } static get fetchDocs() { return `Een gewone (by default GET) <code>fetch</code> call maar met het juiste domein en met <code>params</code> als query parameters, en de geretourneerde Promise geeft JSON terug, of wordt gereject als je geen 200 OK terugkrijgt.` } fetchWithJson(path, json, params, options) { options = options || {}; options.headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; options.body = JSON.stringify(toServerJson(json)); return this.fetch(path, params, options); } static get fetchWithJsonDocs() { return `Zelfde als <code>.fetch()</code> maar met <code>json</code> als body.` } // Deprecated json(path, params) { return this.fetch(path, params); } post(path, params) { return this.fetch(path, params, { method: 'post' }); } static get postDocs() { return `Een POST <code>fetch</code>.` } del(path) { return this.fetch(path, undefined, { method: 'delete' }) .catch(error => { // Error code 3001 indicates the item is still referenced somewhere else if(error.errorCode === 3001) { return this.handleReferenceError(error); } throw error; }); } handleReferenceError(originalError) { const error = new Error(`Er zijn nog bestaande verwijzingen naar dit item, verwijder deze eerst.`); error.isReferenceError = true; error.originalError = originalError; throw error; } static get delDocs() { return `Een DELETE <code>fetch</code>.` } postJson(path, json, params) { return this.fetchWithJson(path, json, params, { method: 'post' }); } static get postJsonDocs() { return `Een POST <code>fetch</code> met <code>json</code> als body.` } putJson(path, json, params) { return this.fetchWithJson(path, json, params, { method: 'put' }); } static get putJsonDocs() { return `Een PUT <code>fetch</code> met <code>json</code> als body.` } }