From 0347d7dfda310c40d2334fe1ea06eff0268bece8 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Tue, 15 Dec 2020 15:49:07 +0100 Subject: [PATCH] chg: [js:api-helper] Added documentation --- src/Controller/AppController.php | 2 +- webroot/js/api-helper.js | 91 +++++++++++++++++++++++++++++--- webroot/js/bootstrap-helper.js | 4 +- 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index dacb95f..70d77a2 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -110,7 +110,7 @@ class AppController extends Controller $this->ACL->checkAccess(); $this->set('menu', $this->ACL->getMenu()); $this->set('ajax', $this->request->is('ajax')); - if (!empty($this->request->getHeader('X-Request-Html-On-Failure'))) { + if (!empty($this->request->getHeader('X-Force-HTML-On-Validation-Failure'))) { $this->ajax_with_html_on_failure = true; } $this->request->getParam('prefix'); diff --git a/webroot/js/api-helper.js b/webroot/js/api-helper.js index d4f4e1c..62728cc 100644 --- a/webroot/js/api-helper.js +++ b/webroot/js/api-helper.js @@ -1,3 +1,4 @@ +/** AJAXApi class providing helpers to perform AJAX request */ class AJAXApi { static genericRequestHeaders = { 'X-Requested-With': 'XMLHttpRequest' @@ -11,14 +12,21 @@ class AJAXApi { method: 'POST', } static renderHTMLOnFailureHeader = { - name: 'X-Request-HTML-On-Failure', + name: 'X-Force-HTML-On-Validation-Failure', value: '1' } + /** + * @namespace + * @property {boolean} provideFeedback - The ID to be used for the toast's container + * @property {(jQuery|string)} statusNode - The node on which the loading overlay should be placed (OverlayFactory.node) + * @property {boolean} forceHTMLOnValidationFailure - If true, attach a special header to ask for HTML instead of JSON in case of form validation failure + * @property {Object} errorToast - The options supported by Toaster#defaultOptions + */ static defaultOptions = { provideFeedback: true, statusNode: false, - renderedHTMLOnFailureRequested: false, + forceHTMLOnValidationFailure: false, errorToast: { delay: 10000 } @@ -26,11 +34,21 @@ class AJAXApi { options = {} loadingOverlay = false + /** + * Instantiate an AJAXApi object. + * @param {Object} options - The options supported by AJAXApi#defaultOptions + */ constructor(options) { this.mergeOptions(AJAXApi.defaultOptions) this.mergeOptions(options) } + /** + * Based on the current configuration, provide feedback to the user via toast, console or do not + * @param {Object} toastOptions - The options supported by Toaster#defaultOptions + * @param {boolean} isError - If true and toast feedback is disable, write the feedback in the console + * @param {boolean} skip - If true, skip the feedback regardless of the configuration + */ provideFeedback(toastOptions, isError=false, skip=false) { const alteredToastOptions = isError ? Object.assign({}, AJAXApi.defaultOptions.errorToast, toastOptions) : toastOptions if (!skip) { @@ -44,10 +62,20 @@ class AJAXApi { } } + /** + * Merge newOptions configuration into the current object + * @param {Object} The options supported by AJAXApi#defaultOptions + */ mergeOptions(newOptions) { this.options = Object.assign({}, this.options, newOptions) } + /** + * + * @param {FormData} formData - The data of a form + * @param {Object} dataToMerge - Data to be merge into formData + * @return {FormData} The form data merged with the additional dataToMerge data + */ static mergeFormData(formData, dataToMerge) { for (const [fieldName, value] of Object.entries(dataToMerge)) { formData.set(fieldName, value) @@ -55,30 +83,57 @@ class AJAXApi { return formData } + /** + * @param {string} url - The URL to fetch + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @return {Promise} Promise object resolving to the fetched HTML + */ static async quickFetchURL(url, options={}) { const constAlteredOptions = Object.assign({}, {provideFeedback: false}, options) const tmpApi = new AJAXApi(constAlteredOptions) return tmpApi.fetchURL(url, constAlteredOptions.skipRequestHooks) } + /** + * @param {string} url - The URL to fetch + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @return {Promise} Promise object resolving to the fetched form + */ static async quickFetchForm(url, options={}) { const constAlteredOptions = Object.assign({}, {provideFeedback: false}, options) const tmpApi = new AJAXApi(constAlteredOptions) return tmpApi.fetchForm(url, constAlteredOptions.skipRequestHooks) } + /** + * @param {HTMLFormElement} form - The form to be posted + * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @return {Promise} Promise object resolving to the result of the POST operation + */ static async quickPostForm(form, dataToMerge={}, options={}) { const constAlteredOptions = Object.assign({}, {}, options) const tmpApi = new AJAXApi(constAlteredOptions) return tmpApi.postForm(form, dataToMerge, constAlteredOptions.skipRequestHooks) } + /** + * @param {string} url - The URL from which to fetch the form + * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form + * @return {Promise} Promise object resolving to the result of the POST operation + */ static async quickFetchAndPostForm(url, dataToMerge={}, options={}) { const constAlteredOptions = Object.assign({}, {}, options) const tmpApi = new AJAXApi(constAlteredOptions) return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks) } + /** + * @param {string} url - The URL to fetch + * @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped + * @param {boolean} [skipFeedback=false] - Pass this value to the AJAXApi.provideFeedback function + * @return {Promise} Promise object resolving to the fetched HTML + */ async fetchURL(url, skipRequestHooks=false, skipFeedback=false) { if (!skipRequestHooks) { this.beforeRequest() @@ -89,12 +144,12 @@ class AJAXApi { if (!response.ok) { throw new Error('Network response was not ok') } - const data = await response.text(); + const dataHtml = await response.text(); this.provideFeedback({ variant: 'success', title: 'URL fetched', }, false, skipFeedback); - toReturn = data; + toReturn = dataHtml; } catch (error) { this.provideFeedback({ variant: 'danger', @@ -110,6 +165,12 @@ class AJAXApi { return toReturn } + /** + * @param {string} url - The URL to fetch + * @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped + * @param {boolean} [skipFeedback=false] - Pass this value to the AJAXApi.provideFeedback function + * @return {Promise} Promise object resolving to the fetched HTML + */ async fetchForm(url, skipRequestHooks=false, skipFeedback=false) { if (!skipRequestHooks) { this.beforeRequest() @@ -143,6 +204,13 @@ class AJAXApi { return toReturn } + /** + * @param {HTMLFormElement} form - The form to be posted + * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form + * @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped + * @param {boolean} [skipFeedback=false] - Pass this value to the AJAXApi.provideFeedback function + * @return {Promise} Promise object resolving to the result of the POST operation + */ async postForm(form, dataToMerge={}, skipRequestHooks=false, skipFeedback=false) { if (!skipRequestHooks) { this.beforeRequest() @@ -154,7 +222,7 @@ class AJAXApi { let formData = new FormData(form) formData = AJAXApi.mergeFormData(formData, dataToMerge) let requestConfig = AJAXApi.genericRequestConfigPOST - if (this.options.renderedHTMLOnFailureRequested) { + if (this.options.forceHTMLOnValidationFailure) { requestConfig.headers.append(AJAXApi.renderHTMLOnFailureHeader.name, AJAXApi.renderHTMLOnFailureHeader.value) } let options = { @@ -184,7 +252,7 @@ class AJAXApi { toReturn = Promise.reject(data.errors); } } catch (error) { // could not parse JSON - if (this.options.renderedHTMLOnFailureRequested) { + if (this.options.forceHTMLOnValidationFailure) { const data = await clonedResponse.text(); toReturn = { success: 0, @@ -210,6 +278,12 @@ class AJAXApi { return toReturn } + /** + * @param {string} url - The URL from which to fetch the form + * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form + * @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped + * @return {Promise} Promise object resolving to the result of the POST operation + */ async fetchAndPostForm(url, dataToMerge={}, skipRequestHooks=false) { if (!skipRequestHooks) { this.beforeRequest() @@ -228,18 +302,21 @@ class AJAXApi { return toReturn } + /** Based on the configuration, show the loading overlay */ beforeRequest() { if (this.options.statusNode !== false) { this.toggleLoading(true) } } - + + /** Based on the configuration, hide the loading overlay */ afterRequest() { if (this.options.statusNode !== false) { this.toggleLoading(false) } } + /** Show or hide the loading overlay */ toggleLoading(loading) { if (this.loadingOverlay === false) { this.loadingOverlay = new OverlayFactory(this.options.statusNode); diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js index b846389..66c65b0 100644 --- a/webroot/js/bootstrap-helper.js +++ b/webroot/js/bootstrap-helper.js @@ -50,7 +50,7 @@ class UIFactory { * @param {string} url - The URL from which the $container's content should be fetched * @param {(jQuery|string)} $container - The container that should hold the data fetched * @param {(jQuery|string)} [$statusNode=null] - A reference to a HTML node on which the loading animation should be displayed. If not provided, $container will be used - * @return {Promise} Promise object resolving to the $container object after its content has been replaced + * @return {Promise} Promise object resolving to the $container object after its content has been replaced */ reload (url, $container, $statusNode=null) { $container = $($container) @@ -514,7 +514,7 @@ class ModalFactory { } this.options.APIConfirm = (tmpApi) => { - tmpApi.mergeOptions({renderedHTMLOnFailureRequested: true}) + tmpApi.mergeOptions({forceHTMLOnValidationFailure: true}) return tmpApi.postForm($form[0]) .then((data) => { if (data.success) {