diff --git a/README.md b/README.md index bc7a4e1..b5d6d42 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# VDM Uikit Uploader Integration Guide +# VDM Uikit File Integration Guide ## Overview -Uploader is an intuitive and lightweight JavaScript solution for embedding upload functionality into your website. By simply adding the `Uploader` class to any element that triggers an upload, you can enable users to upload files easily. +Upload/Delete is an intuitive and lightweight JavaScript solution for embedding upload functionality into your website. By simply adding the `vdm-uikit` class to any element that triggers an upload, you can enable users to upload files easily. ## How to Add Uploader to Your Website @@ -12,12 +12,12 @@ Uploader is an intuitive and lightweight JavaScript solution for embedding uploa ```html - + ``` 2. **Markup Your Upload Area:** - In the body of your HTML document, apply the `vdm-uikit-uploader` class to any element that should trigger an upload action. Here is an example using a custom div: + In the body of your HTML document, apply the `vdm-uikit` class to any element that should trigger an upload action. Here is an example using a custom div: ```html -
- window.vdmUploaderConfig = { - endpoint: 'https://your-type-endpoint.com', - targetClass: 'vdm-uikit-uploader', + window.vdm.uikit.config = { + endpoint_type: 'https://your-type-endpoint.com', + target_class: 'vdm-uikit', upload_id: { - endpoint: 'https://your-upload-endpoint.com', - endpoint_diplay: 'https://your-display-endpoint.com' + endpoint_upload: 'https://your-upload-endpoint.com', + endpoint_diplay: 'https://your-display-endpoint.com', + endpoint_delete: 'https://your-delete-endpoint.com' } }; - + ``` ### Preventing UIkit Collisions diff --git a/dist/js/Uploader.min.js b/dist/js/Uploader.min.js deleted file mode 100644 index 0b2e26f..0000000 --- a/dist/js/Uploader.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! VDM Uikit Uploader v2.1.2 | https://git.vdm.dev/joomla/uikit | (c) 2020 - 2024 Llewellyn van der Merwe | MIT License */ -!function(){"use strict";function t(t,e,r){if("function"==typeof t?t===e:t.has(e))return arguments.length<3?e:r;throw new TypeError("Private element is not present on this object")}function e(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function r(t){return function(){var r=this,n=arguments;return new Promise((function(o,i){var a=t.apply(r,n);function c(t){e(a,o,i,c,u,"next",t)}function u(t){e(a,o,i,c,u,"throw",t)}c(void 0)}))}}function n(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(e,r){return e.get(t(e,r))}function a(t,e,r){n(t,e),e.set(t,r)}function c(e,r,n){return e.set(t(e,r),n),n}function u(t,e){for(var r=0;r=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),s=n.call(a,"finallyLoc");if(u&&s){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),T(r),m}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;T(r)}return o}}throw Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:M(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),m}},e}function p(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var r=t[Symbol.toPrimitive];if(void 0!==r){var n=r.call(t,e||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:e+""}function v(t){return v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},v(t)}var y=new WeakMap,m=new WeakMap,g=new WeakMap,b=new WeakMap,w=s((function t(e){var n=this;o(this,t),a(this,y,void 0),a(this,m,{}),l(this,"set",(function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;i(m,n)[t]=i(m,n)[t]||{},"object"===v(e)?Object.assign(i(m,n)[t],e):i(m,n)[t][e]=r})),l(this,"get",(function(t){var e,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=i(m,n)[t];return a?null===r?a:null!==(e=a[r])&&void 0!==e?e:o:o})),l(this,"init",function(){var t=r(h().mark((function t(e,r){var o,a,c,u=arguments;return h().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(o=u.length>2&&void 0!==u[2]&&u[2],!i(m,n)[e]||o){t.next=4;break}return t.abrupt("return");case 4:return t.prev=4,a=i(b,n).call(n,i(y,n),r),t.next=8,i(g,n).call(n,a);case 8:if(null==(c=t.sent)||!c.data||"object"!==v(c.data)){t.next=14;break}n.set(e,c.data),t.next=16;break;case 14:if(null==c||!c.error){t.next=16;break}throw new Error(c.error||"An error occurred during the file type request.");case 16:t.next=21;break;case 18:t.prev=18,t.t0=t.catch(4);case 21:case"end":return t.stop()}}),t,null,[[4,18]])})));return function(e,r){return t.apply(this,arguments)}}()),a(this,g,function(){var t=r(h().mark((function t(e){var r;return h().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,fetch(e,{method:"GET",headers:{"Content-Type":"application/json"}});case 2:if((r=t.sent).ok){t.next=6;break}return t.abrupt("return");case 6:return t.next=8,r.json();case 8:return t.abrupt("return",t.sent);case 9:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),l(this,"getParams",(function(t){var e={};return Array.isArray(t)&&0!==t.length?(t.forEach((function(t){var r=document.getElementById(t);r&&(e[t]=r.value)})),e):e})),a(this,b,(function(t,e){var r=t.includes("?")?"&":"?";return"".concat(t).concat(r,"guid=").concat(e)})),c(y,this,e)})),E=new WeakMap,x=s((function t(){var e=this;o(this,t),l(this,"set",function(){var t=r(h().mark((function t(r,n,o){var a,c,u,s,l,f,d;return h().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,a=i(E,e).call(e,r,o),t.next=4,fetch(a,{method:"GET",headers:{"Content-Type":"application/json"}});case 4:if((c=t.sent).ok){t.next=8;break}return t.abrupt("return");case 8:return t.next=10,c.json();case 10:if(!(u=t.sent).error){t.next=14;break}return t.abrupt("return");case 14:u.data&&""!==u.data.trim()?(f=new CustomEvent("vdm.uikit.uploader.beforeFilesDisplay",{detail:{result:u,displayArea:n}}),document.dispatchEvent(f),n.innerHTML=u.data,n.removeAttribute("hidden"),d=new CustomEvent("vdm.uikit.uploader.afterFilesDisplay",{detail:{result:u,displayArea:n}}),document.dispatchEvent(d)):(s=new CustomEvent("vdm.uikit.uploader.beforeHideFilesDisplay",{detail:{result:u,displayArea:n}}),document.dispatchEvent(s),n.innerHTML="",n.setAttribute("hidden","hidden"),l=new CustomEvent("vdm.uikit.uploader.afterHideFilesDisplay",{detail:{result:u,displayArea:n}}),document.dispatchEvent(l)),t.next=20;break;case 17:t.prev=17,t.t0=t.catch(0);case 20:case"end":return t.stop()}}),t,null,[[0,17]])})));return function(e,r,n){return t.apply(this,arguments)}}()),a(this,E,(function(t,e){if(!e||0===Object.keys(e).length)return t;var r=t.includes("?")?"&":"?",n=new URLSearchParams(e);return"".concat(t).concat(r).concat(n.toString())}))})),k=new WeakMap,O=new WeakMap,j=new WeakMap,I=new WeakMap,L=new WeakSet,S=s((function e(r,i,u){var s,l;o(this,e),n(s=this,l=L),l.add(s),a(this,k,void 0),a(this,O,void 0),a(this,j,void 0),a(this,I,{}),c(k,this,new w(i)),c(O,this,new x),c(j,this,u),t(L,this,P).call(this,r)}));function P(e){var r=this;Object.keys(e).forEach((function(n){t(L,r,T).call(r,n,e[n])}))}function T(e,n){var o=this,i=n.bar,a=n.typeId,c=n.endpoint,u=n.successId,s=n.errorId,l=n.allowedFormatId,f=n.fileTypeId,d=n.displayId,p=n.displayEndpoint;t(L,this,C).call(this,p,d);var v=document.getElementById(a);if(v){var y=function(){var n=r(h().mark((function r(n){return h().wrap((function(r){for(;;)switch(r.prev=r.next){case 0:if(!(n&&n.length>1)){r.next=9;break}return r.prev=1,r.next=4,t(L,o,A).call(o,e,n,i,c,u,s,l,f,d,p);case 4:r.next=9;break;case 6:r.prev=6,r.t0=r.catch(1),t(L,o,F).call(o,r.t0.message);case 9:case"end":return r.stop()}}),r,null,[[1,6]])})));return function(t){return n.apply(this,arguments)}}();v.addEventListener("change",(function(){return y(v.value)})),y(v.value).catch((function(e){return t(L,o,F).call(o,e.message)}))}else t(L,this,B).call(this,"Type field with ID ".concat(a," not found"))}function A(t,e,r,n,o,i,a,c,u,s){return M.apply(this,arguments)}function M(){return(M=r(h().mark((function e(r,n,o,a,c,u,s,l,f,p){var v,y,m=this;return h().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,t(L,this,G).call(this,"beforeInit",{id:r,typeGuid:n,progressBarId:o,uploadEndpoint:a,successId:c,errorId:u,allowedFormatId:s,fileTypeId:l,displayId:f,displayEndpoint:p}),v="".concat(r).concat(n),e.next=5,i(k,this).init(v,n,!0);case 5:y=t(L,this,_).call(this,o,c,u,s,l,f),t(L,this,G).call(this,"afterElementsInit",d({},y)),t(L,this,U).call(this,y,v,c,u),i(j,this).upload("#".concat(r),{url:t(L,this,D).call(this,a,n),multiple:!0,allow:i(k,this).get(v,"allow",!1),name:i(k,this).get(v,"name","files"),beforeSend:function(e){return t(L,m,W).call(m,v,e)},beforeAll:function(e){return t(L,m,G).call(m,"beforeAll",{files:e})},load:function(e){return t(L,m,G).call(m,"load",{event:e})},error:function(e){return t(L,m,N).call(m,e,y.errorMessage)},complete:function(e){return t(L,m,H).call(m,e,y.successMessage)},loadStart:function(e){return t(L,m,q).call(m,e,y.progressBar)},progress:function(e){return t(L,m,Y).call(m,e,y.progressBar)},loadEnd:function(e){return t(L,m,z).call(m,e,y.progressBar)},completeAll:function(e){return t(L,m,R).call(m,e,y.progressBar,y.successMessage,y.errorMessage,p,f,v)}}),e.next=14;break;case 11:throw e.prev=11,e.t0=e.catch(0),e.t0;case 14:case"end":return e.stop()}}),e,this,[[0,11]])})))).apply(this,arguments)}function _(t,e,r,n,o,i){return{progressBar:document.getElementById(t),successMessage:document.getElementById(e),errorMessage:document.getElementById(r),allowedFormatSpan:document.getElementById(n),fileTypeSpan:document.getElementById(o),displayArea:document.getElementById(i)}}function C(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=document.getElementById(e);t&&n&&i(O,this).set(t,n,r)}function F(t){i(j,this).notification({message:t,status:"danger",timeout:7e3})}function B(t){}function G(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};document.dispatchEvent(new CustomEvent("vdm.uikit.uploader.".concat(t),{detail:e}))}function D(t,e){var r=t.includes("?")?"&":"?";return"".concat(t).concat(r,"guid=").concat(e)}function U(t,e,r,n){t.successMessage&&t.successMessage.setAttribute("hidden","hidden"),t.errorMessage&&t.errorMessage.setAttribute("hidden","hidden"),t.allowedFormatSpan&&(t.allowedFormatSpan.innerHTML=i(k,this).get(e,"allow_span","")),t.fileTypeSpan&&(t.fileTypeSpan.innerHTML=i(k,this).get(e,"file_type_span","file"))}function W(e,r){t(L,this,G).call(this,"beforeSend",{environment:r}),r.data.params=i(k,this).getParams(i(k,this).get(e,"param_fields")),t(L,this,G).call(this,"afterSendPreparation",{environment:r})}function N(e,r){t(L,this,G).call(this,"error",{error:e}),r&&(r.removeAttribute("hidden"),r.textContent="Upload failed.")}function H(e,r){t(L,this,G).call(this,"complete",{xhr:e}),r&&(r.removeAttribute("hidden"),r.textContent="Upload completed successfully.")}function q(e,r){t(L,this,G).call(this,"loadStart",{event:e}),r&&(r.removeAttribute("hidden"),r.max=e.total,r.value=e.loaded)}function Y(e,r){t(L,this,G).call(this,"progress",{event:e}),r&&(r.max=e.total,r.value=e.loaded)}function z(e,r){t(L,this,G).call(this,"loadEnd",{event:e}),r&&(r.max=e.total,r.value=e.loaded)}function R(e,r,n,o,a,c,u){t(L,this,G).call(this,"completeAll",{xhr:e}),r&&setTimeout((function(){r.setAttribute("hidden","hidden"),n&&n.setAttribute("hidden","hidden"),o&&o.setAttribute("hidden","hidden")}),5e3),t(L,this,C).call(this,a,c,i(k,this).getParams(i(k,this).get(u,"display_fields")))}var J,K=["endpoint","targetClass"];J=window,document.addEventListener("DOMContentLoaded",(function(){var t;t=J.UIkit?J.UIkit:require("uikit").default;var e=J.vdmUploaderConfig||{},r=e.endpoint,n=e.targetClass;if(function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if({}.hasOwnProperty.call(t,n)){if(e.includes(n))continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n0&&new S(i,r,t)}}))}(); diff --git a/dist/js/Uploader.js b/dist/js/vdm.js similarity index 65% rename from dist/js/Uploader.js rename to dist/js/vdm.js index eca78aa..9406f24 100644 --- a/dist/js/Uploader.js +++ b/dist/js/vdm.js @@ -1,5 +1,5 @@ /** - * VDM Uikit Uploader v2.1.2 + * VDM Uikit v3.0.0 * https://git.vdm.dev/joomla/uikit * (c) 2020 - 2024 Llewellyn van der Merwe * MIT License @@ -9,14 +9,14 @@ 'use strict'; /** - * `UploadHelper` is a utility class that simplifies operations related to file uploading. + * `FileType` is a utility class that simplifies operations related to file uploading. * It handles the storage and retrieval of metadata associated with each upload and initializes * the upload process by setting up endpoint configuration. It also provides methods for * triggering the upload activities in an asynchronous manner. * * @class * @example - * const helper = new UploadHelper('http://example.com/upload'); + * const helper = new FileType('http://example.com/upload'); * const uniqueId = 'file123'; * const globalId = 'glob124'; * const data = { user: 'John Doe', file: 'myfile.txt' }; @@ -24,7 +24,7 @@ * helper.set(uniqueId, data); * await helper.init(uniqueId, globalId); */ - class UploadHelper { + class FileType { /** * The endpoint to which files would be uploaded. * Stored as a private property and used internally within the class methods. @@ -36,7 +36,7 @@ #endpoint; /** - * It is a private object used to store the data associated with an instance of `UploadHelper`. + * It is a private object used to store the data associated with an instance of `FileType`. * Default is an empty object. * This data is used when performing uploads. * @@ -46,9 +46,9 @@ #data = {}; /** - * Constructor for the UploadHelper class. + * Constructor for the FileType class. * - * @param {string} endpoint - The endpoint to be associated with the instance of the UploadHelper. + * @param {string} endpoint - The endpoint to be associated with the instance of the FileType. */ constructor(endpoint) { // Initialize private field with passed endpoint argument @@ -104,7 +104,7 @@ }; /** - * Asynchronously initializes the UploadHelper object. + * Asynchronously initializes the FileType object. * * @param {string} id - The unique identifier associated with the initialization. * @param {string} guid - The globally unique identifier used to build the URL for fetching. @@ -120,7 +120,7 @@ } try { - const url = this.#buildUrl(this.#endpoint, guid); + const url = this.#buildUrl(guid); const result = await this.#fetchData(url); if (true) console.log('Data fetched:', result); @@ -188,17 +188,16 @@ /** * Builds a URL appending a unique identifier as a parameter. * - * @param {string} endpoint - The base endpoint of the URL. * @param {string} guid - The globally unique identifier to append to the URL. * @returns {string} The constructed URL with the appended unique identifier. * @private */ - #buildUrl = (endpoint, guid) => { + #buildUrl = (guid) => { // Determine the appropriate separator for the query parameter - const separator = endpoint.includes('?') ? '&' : '?'; + const separator = this.#endpoint.includes('?') ? '&' : '?'; // Return the constructed URL - return `${endpoint}${separator}guid=${guid}`; + return `${this.#endpoint}${separator}guid=${guid}`; }; } @@ -216,7 +215,7 @@ * * await helper.set(endpoint, area, params); */ - class DisplayHelper { + class Display { constructor() { } @@ -233,6 +232,9 @@ */ set = async (displayEndpoint, displayArea, params) => { try { + // Trigger a custom event before hide files display the entity files + this.#dispatchEvent('beforeGetFilesDisplay', {endpoint: displayEndpoint, element: displayArea, params: params}); + // Build the URL with the query parameters const url = this.#buildUrl(displayEndpoint, params); @@ -266,35 +268,23 @@ // If there's no response.data or it's empty, clear the display area if (!result.data || result.data.trim() === '') { // Trigger a custom event before hide files display the entity files - const beforeHideFilesDisplay = new CustomEvent('vdm.uikit.uploader.beforeHideFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(beforeHideFilesDisplay); + this.#dispatchEvent('beforeHideFilesDisplay', {result: result, element: displayArea}); displayArea.innerHTML = ''; // Empty the display area displayArea.setAttribute('hidden', 'hidden'); // Trigger a custom event after hide files display the entity files - const afterHideFilesDisplay = new CustomEvent('vdm.uikit.uploader.afterHideFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(afterHideFilesDisplay); + this.#dispatchEvent('afterHideFilesDisplay', {result: result, element: displayArea}); } else { // Trigger a custom event before displaying the entity files - const beforeFilesDisplayEvent = new CustomEvent('vdm.uikit.uploader.beforeFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(beforeFilesDisplayEvent); + this.#dispatchEvent('beforeFilesDisplay', {result: result, element: displayArea}); // Replace the display area content with the new HTML displayArea.innerHTML = result.data; displayArea.removeAttribute('hidden'); // Trigger a custom event after displaying the entity files - const afterFilesDisplayEvent = new CustomEvent('vdm.uikit.uploader.afterFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(afterFilesDisplayEvent); + this.#dispatchEvent('afterFilesDisplay', {result: result, element: displayArea}); } } catch (error) { // If an error occurs, log it in debug mode @@ -304,6 +294,17 @@ } }; + /** + * Dispatches a custom event with optional detail data. + * + * @param {string} eventName - The name of the event to dispatch. + * @param {object} [detail={}] - The optional detail data to include with the event. + * @return {void} + */ + #dispatchEvent(eventName, detail = {}) { + document.dispatchEvent(new CustomEvent(`vdm.uikit.display.${eventName}`, {detail})); + } + /** * It's a private method that builds a complete URL from the endpoint and an object containing parameters. * It uses the URLSearchParams interface to turn the parameters object to a query string, @@ -329,27 +330,41 @@ } /** - * Helper class for uploading files. + * Class for uploading files. * * @class * @classdesc This class provides methods for uploading files to a server. */ - class UikitUploader { - #uploadHelper; - #displayHelper; - #uikit; - #uploadInstances = {}; + class UploadFile { + /** + * Utility class for uploading files. + * + * @class + */ + #fileType; /** - * Creates an instance of the UikitUploader class. + * Helper class for displaying elements on the UI. + * + * @class + */ + #display; + + /** + * The UIKit variable is a reference to a UI framework for building web applications. + */ + #uikit; + + /** + * Creates an instance of the UploadFile class. * * @param {Object} config - Configuration details for uploader instances. * @param {string} endpoint - The endpoint where the files will be uploaded. * @param {any} uikit - Reference to UIKit. */ constructor(config, endpoint, uikit) { - this.#uploadHelper = new UploadHelper(endpoint); - this.#displayHelper = new DisplayHelper(); + this.#fileType = new FileType(endpoint); + this.#display = new Display(); this.#uikit = uikit; this.#initializeFields(config); @@ -390,13 +405,13 @@ try { await this.#initUpload(id, guid, bar, endpoint, successId, errorId, allowedFormatId, fileTypeId, displayId, displayEndpoint); } catch (error) { - this.#notifyError(error.message); + this.#showNotification(error.message, 'danger'); } } }; typeField.addEventListener('change', () => initializeUpload(typeField.value)); - initializeUpload(typeField.value).catch(error => this.#notifyError(error.message)); + initializeUpload(typeField.value).catch(error => this.#showNotification(error.message, 'danger')); } /** @@ -429,7 +444,7 @@ }); const call = `${id}${typeGuid}`; - await this.#uploadHelper.init(call, typeGuid, true); + await this.#fileType.init(call, typeGuid, true); const elements = this.#getUploadElements(progressBarId, successId, errorId, allowedFormatId, fileTypeId, displayId); @@ -440,8 +455,8 @@ this.#uikit.upload(`#${id}`, { url: this.#buildUrl(uploadEndpoint, typeGuid), multiple: true, - allow: this.#uploadHelper.get(call, 'allow', false), - name: this.#uploadHelper.get(call, 'name', 'files'), + allow: this.#fileType.get(call, 'allow', false), + name: this.#fileType.get(call, 'name', 'files'), beforeSend: (env) => this.#handleBeforeSend(call, env), beforeAll: (files) => this.#dispatchEvent('beforeAll', {files}), load: (e) => this.#dispatchEvent('load', {event: e}), @@ -459,50 +474,57 @@ /** * Returns the required HTML elements by their IDs. + * If an element ID is null or the element does not exist on the page, + * the corresponding value in the returned object will be null. * - * @param {string} progressBarId - The ID of the progress bar element. - * @param {string} successId - The ID of the success message element. - * @param {string} errorId - The ID of the error message element. - * @param {string} allowedFormatId - The ID of the allowed format span element. - * @param {string} fileTypeId - The ID of the file type span element. - * @param {string} displayId - The ID of the display area element. - * @returns {object} - An object containing the required HTML elements. + * @param {string|null} progressBarId - The ID of the progress bar element, or null. + * @param {string|null} successId - The ID of the success message element, or null. + * @param {string|null} errorId - The ID of the error message element, or null. + * @param {string|null} allowedFormatId - The ID of the allowed format span element, or null. + * @param {string|null} fileTypeId - The ID of the file type span element, or null. + * @param {string|null} displayId - The ID of the display area element, or null. + * @returns {object} - An object containing the required HTML elements or null if they do not exist. */ #getUploadElements(progressBarId, successId, errorId, allowedFormatId, fileTypeId, displayId) { return { - progressBar: document.getElementById(progressBarId), - successMessage: document.getElementById(successId), - errorMessage: document.getElementById(errorId), - allowedFormatSpan: document.getElementById(allowedFormatId), - fileTypeSpan: document.getElementById(fileTypeId), - displayArea: document.getElementById(displayId) + progressBar: progressBarId ? document.getElementById(progressBarId) : null, + successMessage: successId ? document.getElementById(successId) : null, + errorMessage: errorId ? document.getElementById(errorId) : null, + allowedFormatSpan: allowedFormatId ? document.getElementById(allowedFormatId) : null, + fileTypeSpan: fileTypeId ? document.getElementById(fileTypeId) : null, + displayArea: displayId ? document.getElementById(displayId) : null }; } /** * Initializes the display area with data from the display endpoint. * - * @param {string} displayEndpoint - The endpoint to retrieve the display data from. - * @param {string} displayId - The id of the display area element in the DOM. + * @param {string|null} displayEndpoint - The endpoint to retrieve the display data from. + * @param {string|null} displayId - The id of the display area element in the DOM. * @param {object} params - Additional parameters to be passed to the display helper. - * * @return {void} */ #setupDisplayArea(displayEndpoint, displayId, params = {}) { - const displayArea = document.getElementById(displayId); + const displayArea = displayId ? document.getElementById(displayId) : null; if (displayEndpoint && displayArea) { - this.#displayHelper.set(displayEndpoint, displayArea, params); + this.#display.set(displayEndpoint, displayArea, params); } } /** - * Notifies the user of an error using UIKit notifications. + * Displays a notification with the given message and status. * - * @param {string} message - The error message to display. - * @return {void} + * @param {string} message - The message to be displayed in the notification. + * @param {string} status - The status of the notification (e.g., 'success', 'error', 'warning'). + * @return {void} - Does not return a value. */ - #notifyError(message) { - this.#uikit.notification({message, status: 'danger', timeout: 7000}); + #showNotification(message, status) { + this.#uikit.notification({ + message, + status, + pos: 'top-center', + timeout: 7000 + }); } /** @@ -552,8 +574,8 @@ #prepareUploadUI(elements, call, successId, errorId) { if (elements.successMessage) elements.successMessage.setAttribute('hidden', 'hidden'); if (elements.errorMessage) elements.errorMessage.setAttribute('hidden', 'hidden'); - if (elements.allowedFormatSpan) elements.allowedFormatSpan.innerHTML = this.#uploadHelper.get(call, 'allow_span', ''); - if (elements.fileTypeSpan) elements.fileTypeSpan.innerHTML = this.#uploadHelper.get(call, 'file_type_span', 'file'); + if (elements.allowedFormatSpan) elements.allowedFormatSpan.innerHTML = this.#fileType.get(call, 'allow_span', ''); + if (elements.fileTypeSpan) elements.fileTypeSpan.innerHTML = this.#fileType.get(call, 'file_type_span', 'file'); } /** @@ -565,7 +587,7 @@ */ #handleBeforeSend(call, environment) { this.#dispatchEvent('beforeSend', {environment}); - environment.data.params = this.#uploadHelper.getParams(this.#uploadHelper.get(call, 'param_fields')); + environment.data.params = this.#fileType.getParams(this.#fileType.get(call, 'param_fields')); this.#dispatchEvent('afterSendPreparation', {environment}); } @@ -589,13 +611,15 @@ * Handles the upload completion. * * @param {XMLHttpRequest} xhr - The XMLHttpRequest object representing the upload request. - * @param {HTMLElement} successMessage - The success message element to display. + * @param {HTMLElement|null} successMessage - The success message element to display. */ #handleComplete(xhr, successMessage) { this.#dispatchEvent('complete', {xhr}); if (successMessage) { successMessage.removeAttribute('hidden'); successMessage.textContent = 'Upload completed successfully.'; + } else { + this.#showNotification('Upload completed successfully.', 'primary'); } } @@ -603,7 +627,7 @@ * Handles the loadStart event. * * @param {Event} e - The loadStart event object. - * @param {HTMLElement} progressBar - The progress bar element. Optional. + * @param {HTMLElement|null} progressBar - The progress bar element. Optional. * @return {void} */ #handleLoadStart(e, progressBar) { @@ -619,7 +643,7 @@ * Handles the progress event. * * @param {Event} e - The progress event. - * @param {Element} progressBar - The progress bar element. + * @param {Element|null} progressBar - The progress bar element. * * @return {void} */ @@ -635,7 +659,7 @@ * Handles the loadEnd event. * * @param {Event} e - The loadEnd event object. - * @param {Element} progressBar - The progress bar element to update. + * @param {Element|null} progressBar - The progress bar element to update. * * @return {void} */ @@ -651,11 +675,11 @@ * Handles the completion of all uploads. * * @param {XMLHttpRequest} xhr - The XMLHttpRequest object used for the uploads. - * @param {HTMLElement} progressBar - The progress bar element. - * @param {HTMLElement} successMessage - The success message element. - * @param {HTMLElement} errorMessage - The error message element. - * @param {string} displayEndpoint - The display endpoint. - * @param {string} displayId - The display ID. + * @param {HTMLElement|null} progressBar - The progress bar element. + * @param {HTMLElement|null} successMessage - The success message element. + * @param {HTMLElement|null} errorMessage - The error message element. + * @param {string|null} displayEndpoint - The display endpoint. + * @param {string|null} displayId - The display ID. * @param {Object} call - The call object. * * @return {void} @@ -669,7 +693,162 @@ if (errorMessage) errorMessage.setAttribute('hidden', 'hidden'); }, 5000); } - this.#setupDisplayArea(displayEndpoint, displayId, this.#uploadHelper.getParams(this.#uploadHelper.get(call, 'display_fields'))); + this.#setupDisplayArea(displayEndpoint, displayId, this.#fileType.getParams(this.#fileType.get(call, 'display_fields'))); + } + } + + /** + * Helper class for deleting files from the server. + * + * @class + * @classdesc This class provides methods for deleting files from the server. + */ + class DeleteFile { + /** + * The endpoint to which files would be deleted. + * Stored as a private property and used internally within the class methods. + * This field must be a string representing a valid URL. + * + * @type {string} + * @private + */ + #endpoint; + + /** + * The UIKit variable is a reference to a UI framework for building web applications. + */ + #uikit; + + /** + * The error message that is displayed when the delete endpoint is not configured. + * + * @type {string} + */ + static ERROR_ENDPOINT = 'Error: The delete endpoint is not configured.'; + + /** + * Creates an instance of the DeleteHelper class. + * + * @param {string} endpoint - The endpoint where the files will be uploaded. + * @param {any} uikit - Reference to UIKit. + */ + constructor(endpoint, uikit) { + this.#endpoint = endpoint; + this.#uikit = uikit; + } + + /** + * Deletes a file with the given fileGuid. + * + * @param {string} fileGuid - The unique identifier of the file to delete. + * @return {void} + */ + delete(fileGuid) { + if (!fileGuid || fileGuid.length <= 30) { + return; + } + + this.#uikit.modal.confirm('Are you sure you want to delete this file! It can not be undone!') + .then(() => this.#serverDelete(fileGuid)); + } + + /** + * Deletes a file from the server. + * + * @param {string} fileGuid - The unique identifier of the file to be deleted. + * @return {void} + */ + #serverDelete(fileGuid) { + if (!this.#endpoint) { + console.error(DeleteFile.ERROR_ENDPOINT); + return; + } + + this.#dispatchEvent('beforeFileDelete', {guid: fileGuid}); + + fetch(this.#buildUrl(fileGuid), { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(this.#handleResponse.bind(this, fileGuid)) + .catch(console.error); + } + + /** + * Handles the response from the server after a file upload. + * Removes the file from the UI if the response is successful and shows a success notification. + * Shows an error notification if the response contains an error. + * @param {string} fileGuid - The unique identifier of the file that was uploaded. + * @param {object} data - The response data from the server. + * @return {void} + */ + #handleResponse(fileGuid, data) { + if (data.success) { + this.#fileRemoveFromUI(fileGuid); + this.#showNotification(data.success, 'primary'); + this.#dispatchEvent('afterFileDelete', {data: data, guid: fileGuid}); + } else if (data.error) { + this.#dispatchEvent('onFileDeleteError', {data: data, guid: fileGuid}); + this.#showNotification(data.error, 'danger'); + } + } + + /** + * Displays a notification with the given message and status. + * + * @param {string} message - The message to be displayed in the notification. + * @param {string} status - The status of the notification (e.g., 'success', 'error', 'warning'). + * @return {void} - Does not return a value. + */ + #showNotification(message, status) { + this.#uikit.notification({ + message, + status, + pos: 'top-center', + timeout: 7000 + }); + } + + /** + * Remove file from user interface by fileGuid. + * + * @param {string} fileGuid - The unique identifier of the file. + * @example + * removeFileFromUI('file123') + * + * @return {void} - No return value. + */ + #fileRemoveFromUI(fileGuid) { + const listItem = document.getElementById(fileGuid); + if (listItem) { + this.#dispatchEvent('beforeFileRemoveFromUI', {element: listItem, guid: fileGuid}); + listItem.remove(); + } + } + + /** + * Dispatches a custom event with optional detail data. + * + * @param {string} eventName - The name of the event to dispatch. + * @param {object} [detail={}] - The optional detail data to include with the event. + * @return {void} + */ + #dispatchEvent(eventName, detail = {}) { + document.dispatchEvent(new CustomEvent(`vdm.uikit.delete.${eventName}`, {detail})); + } + + /** + * Builds a URL by appending the GUID parameter. + * + * @param {string} guid - The GUID parameter to be appended to the URL. + * @return {string} - The constructed URL with the GUID parameter appended. + */ + #buildUrl(guid) { + const separator = this.#endpoint.includes('?') ? '&' : '?'; + return `${this.#endpoint}${separator}guid=${guid}`; } } @@ -683,35 +862,47 @@ UIkitLocal = global.UIkit; } - const { endpoint, targetClass, ...additionalConfig } = global.vdmUploaderConfig || {}; - - if (!endpoint) { - console.error('Endpoint is not defined, exiting initialization.'); + if (!global.VDM) { + console.error('VDM is not defined, exiting initialization.'); return; } - if (!targetClass) { + const { endpoint_type, target_class, ...additionalConfig } = global.VDM.uikit.config || {}; + + if (!endpoint_type) { + console.error('File Type Endpoint is not defined, exiting initialization.'); + return; + } + + if (!target_class) { console.error('The target class is not defined, exiting initialization.'); return; } - const uploadElements = document.querySelectorAll('.' + targetClass); + const uploadElements = document.querySelectorAll('.' + target_class); const config = {}; + // Ensure the global.VDM.uikit.delete_file exists, or initialize it + if (!global.VDM.uikit.delete_file) { + global.VDM.uikit.delete_file = {}; // Initialize delete_file object if it doesn't exist + } + uploadElements.forEach(element => { const id = element.getAttribute('id'); - const uploadEndpoint = global.vdmUploaderConfig[id] ? global.vdmUploaderConfig[id].endpoint : null; + const uploadEndpoint = additionalConfig[id]?.endpoint_upload ?? null; if (!uploadEndpoint) { console.error(`Upload Endpoint for ${id} is not defined, exiting initialization for this field.`); return; // Skip this field if no upload endpoint is found } - const progressBarId = element.dataset.progressbarId; const typeId = element.dataset.typeId; + // optional - const displayEndpoint = global.vdmUploaderConfig[id] ? global.vdmUploaderConfig[id].endpoint_display : null; + const progressBarId = element.dataset.progressbarId ?? null; + const displayEndpoint = additionalConfig[id]?.endpoint_display ?? null; const displayId = element.dataset.displayId || null; + const deleteEndpoint = additionalConfig[id]?.endpoint_delete ?? null; const successId = element.dataset.successId || null; const errorId = element.dataset.errorId || null; const allowedFormatId = element.dataset.allowedFormatId || null; @@ -728,12 +919,39 @@ displayId: displayId, displayEndpoint: displayEndpoint }; + + // if delete endpoint found + if (deleteEndpoint) + { + global.VDM.uikit.delete_file[id] = new DeleteFile(deleteEndpoint, UIkitLocal); + } }); if (Object.keys(config).length > 0) { - new UikitUploader(config, endpoint, UIkitLocal); + new UploadFile(config, endpoint_type, UIkitLocal); } + }); + + /** + * Performs a delete operation on the specified file. + * + * @param {string} id - The identifier of the delete_file object. + * @param {string} guid - The file GUID to delete. + * + * @return {void} - No return value. + */ + global.VDMDeleteFile = function(id, guid) { + // Check if the delete_file object exists and is an instance of DeleteFile + if (global.VDM.uikit.delete_file[id] && global.VDM.uikit.delete_file[id] instanceof DeleteFile) { + // Call the delete method on the DeleteFile instance + global.VDM.uikit.delete_file[id].delete(guid); + } else { + // Log an error or handle the case where the object is missing or invalid + console.error(`Error: delete_file with id ${id} is either not defined or not an instance of DeleteFile.`); + } + }; + })(window); })(); diff --git a/dist/js/vdm.min.js b/dist/js/vdm.min.js new file mode 100644 index 0000000..01bb140 --- /dev/null +++ b/dist/js/vdm.min.js @@ -0,0 +1,2 @@ +/*! VDM Uikit v3.0.0 | https://git.vdm.dev/joomla/uikit | (c) 2020 - 2024 Llewellyn van der Merwe | MIT License */ +!function(){"use strict";function t(t,e,n){if("function"==typeof t?t===e:t.has(e))return arguments.length<3?e:n;throw new TypeError("Private element is not present on this object")}function e(t,e,n,r,i,o,a){try{var c=t[o](a),l=c.value}catch(t){return void n(t)}c.done?e(l):Promise.resolve(l).then(r,i)}function n(t){return function(){var n=this,r=arguments;return new Promise((function(i,o){var a=t.apply(n,r);function c(t){e(a,i,o,c,l,"next",t)}function l(t){e(a,i,o,c,l,"throw",t)}c(void 0)}))}}function r(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(e,n){return e.get(t(e,n))}function a(t,e,n){r(t,e),e.set(t,n)}function c(e,n,r){return e.set(t(e,n),r),r}function l(t,e){r(t,e),e.add(t)}function u(t,e){for(var n=0;n=0;--o){var a=this.tryEntries[o],c=a.completion;if("root"===a.tryLoc)return i("end");if(a.tryLoc<=this.prev){var l=r.call(a,"catchLoc"),u=r.call(a,"finallyLoc");if(l&&u){if(this.prev=0;--n){var i=this.tryEntries[n];if(i.tryLoc<=this.prev&&r.call(i,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),_(n),m}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var r=n.completion;if("throw"===r.type){var i=r.arg;_(n)}return i}}throw Error("illegal catch attempt")},delegateYield:function(e,n,r){return this.delegate={iterator:T(e),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=t),m}},e}function v(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var r=n.call(t,e||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:e+""}function y(t){return y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},y(t)}var m=new WeakMap,g=new WeakMap,b=new WeakMap,w=new WeakMap,E=s((function t(e){var r=this;i(this,t),a(this,m,void 0),a(this,g,{}),f(this,"set",(function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;o(g,r)[t]=o(g,r)[t]||{},"object"===y(e)?Object.assign(o(g,r)[t],e):o(g,r)[t][e]=n})),f(this,"get",(function(t){var e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=o(g,r)[t];return a?null===n?a:null!==(e=a[n])&&void 0!==e?e:i:i})),f(this,"init",function(){var t=n(p().mark((function t(e,n){var i,a,c,l=arguments;return p().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(i=l.length>2&&void 0!==l[2]&&l[2],!o(g,r)[e]||i){t.next=4;break}return t.abrupt("return");case 4:return t.prev=4,a=o(w,r).call(r,n),t.next=8,o(b,r).call(r,a);case 8:if(null==(c=t.sent)||!c.data||"object"!==y(c.data)){t.next=14;break}r.set(e,c.data),t.next=16;break;case 14:if(null==c||!c.error){t.next=16;break}throw new Error(c.error||"An error occurred during the file type request.");case 16:t.next=21;break;case 18:t.prev=18,t.t0=t.catch(4);case 21:case"end":return t.stop()}}),t,null,[[4,18]])})));return function(e,n){return t.apply(this,arguments)}}()),a(this,b,function(){var t=n(p().mark((function t(e){var n;return p().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,fetch(e,{method:"GET",headers:{"Content-Type":"application/json"}});case 2:if((n=t.sent).ok){t.next=6;break}return t.abrupt("return");case 6:return t.next=8,n.json();case 8:return t.abrupt("return",t.sent);case 9:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()),f(this,"getParams",(function(t){var e={};return Array.isArray(t)&&0!==t.length?(t.forEach((function(t){var n=document.getElementById(t);n&&(e[t]=n.value)})),e):e})),a(this,w,(function(t){var e=o(m,r).includes("?")?"&":"?";return"".concat(o(m,r)).concat(e,"guid=").concat(t)})),c(m,this,e)})),k=new WeakSet,x=new WeakMap,I=s((function e(){var r=this;i(this,e),l(this,k),f(this,"set",function(){var e=n(p().mark((function e(n,i,a){var c,l,u;return p().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,t(k,r,O).call(r,"beforeGetFilesDisplay",{endpoint:n,element:i,params:a}),c=o(x,r).call(r,n,a),e.next=5,fetch(c,{method:"GET",headers:{"Content-Type":"application/json"}});case 5:if((l=e.sent).ok){e.next=9;break}return e.abrupt("return");case 9:return e.next=11,l.json();case 11:if(!(u=e.sent).error){e.next=15;break}return e.abrupt("return");case 15:u.data&&""!==u.data.trim()?(t(k,r,O).call(r,"beforeFilesDisplay",{result:u,element:i}),i.innerHTML=u.data,i.removeAttribute("hidden"),t(k,r,O).call(r,"afterFilesDisplay",{result:u,element:i})):(t(k,r,O).call(r,"beforeHideFilesDisplay",{result:u,element:i}),i.innerHTML="",i.setAttribute("hidden","hidden"),t(k,r,O).call(r,"afterHideFilesDisplay",{result:u,element:i})),e.next=21;break;case 18:e.prev=18,e.t0=e.catch(0);case 21:case"end":return e.stop()}}),e,null,[[0,18]])})));return function(t,n,r){return e.apply(this,arguments)}}()),a(this,x,(function(t,e){if(!e||0===Object.keys(e).length)return t;var n=t.includes("?")?"&":"?",r=new URLSearchParams(e);return"".concat(t).concat(n).concat(r.toString())}))}));function O(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};document.dispatchEvent(new CustomEvent("vdm.uikit.display.".concat(t),{detail:e}))}var j=new WeakMap,L=new WeakMap,M=new WeakMap,S=new WeakSet,_=s((function e(n,r,o){i(this,e),l(this,S),a(this,j,void 0),a(this,L,void 0),a(this,M,void 0),c(j,this,new E(r)),c(L,this,new I),c(M,this,o),t(S,this,P).call(this,n)}));function P(e){var n=this;Object.keys(e).forEach((function(r){t(S,n,T).call(n,r,e[r])}))}function T(e,r){var i=this,o=r.bar,a=r.typeId,c=r.endpoint,l=r.successId,u=r.errorId,s=r.allowedFormatId,f=r.fileTypeId,h=r.displayId,d=r.displayEndpoint;t(S,this,B).call(this,d,h);var v=document.getElementById(a);if(v){var y=function(){var r=n(p().mark((function n(r){return p().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:if(!(r&&r.length>1)){n.next=9;break}return n.prev=1,n.next=4,t(S,i,D).call(i,e,r,o,c,l,u,s,f,h,d);case 4:n.next=9;break;case 6:n.prev=6,n.t0=n.catch(1),t(S,i,G).call(i,n.t0.message,"danger");case 9:case"end":return n.stop()}}),n,null,[[1,6]])})));return function(t){return r.apply(this,arguments)}}();v.addEventListener("change",(function(){return y(v.value)})),y(v.value).catch((function(e){return t(S,i,G).call(i,e.message,"danger")}))}else t(S,this,W).call(this,"Type field with ID ".concat(a," not found"))}function D(t,e,n,r,i,o,a,c,l,u){return A.apply(this,arguments)}function A(){return(A=n(p().mark((function e(n,r,i,a,c,l,u,s,f,h){var v,y,m=this;return p().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,t(S,this,C).call(this,"beforeInit",{id:n,typeGuid:r,progressBarId:i,uploadEndpoint:a,successId:c,errorId:l,allowedFormatId:u,fileTypeId:s,displayId:f,displayEndpoint:h}),v="".concat(n).concat(r),e.next=5,o(j,this).init(v,r,!0);case 5:y=t(S,this,F).call(this,i,c,l,u,s,f),t(S,this,C).call(this,"afterElementsInit",d({},y)),t(S,this,V).call(this,y,v,c,l),o(M,this).upload("#".concat(n),{url:t(S,this,N).call(this,a,r),multiple:!0,allow:o(j,this).get(v,"allow",!1),name:o(j,this).get(v,"name","files"),beforeSend:function(e){return t(S,m,U).call(m,v,e)},beforeAll:function(e){return t(S,m,C).call(m,"beforeAll",{files:e})},load:function(e){return t(S,m,C).call(m,"load",{event:e})},error:function(e){return t(S,m,H).call(m,e,y.errorMessage)},complete:function(e){return t(S,m,R).call(m,e,y.successMessage)},loadStart:function(e){return t(S,m,q).call(m,e,y.progressBar)},progress:function(e){return t(S,m,Y).call(m,e,y.progressBar)},loadEnd:function(e){return t(S,m,z).call(m,e,y.progressBar)},completeAll:function(e){return t(S,m,J).call(m,e,y.progressBar,y.successMessage,y.errorMessage,h,f,v)}}),e.next=14;break;case 11:throw e.prev=11,e.t0=e.catch(0),e.t0;case 14:case"end":return e.stop()}}),e,this,[[0,11]])})))).apply(this,arguments)}function F(t,e,n,r,i,o){return{progressBar:t?document.getElementById(t):null,successMessage:e?document.getElementById(e):null,errorMessage:n?document.getElementById(n):null,allowedFormatSpan:r?document.getElementById(r):null,fileTypeSpan:i?document.getElementById(i):null,displayArea:o?document.getElementById(o):null}}function B(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=e?document.getElementById(e):null;t&&r&&o(L,this).set(t,r,n)}function G(t,e){o(M,this).notification({message:t,status:e,pos:"top-center",timeout:7e3})}function W(t){}function C(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};document.dispatchEvent(new CustomEvent("vdm.uikit.uploader.".concat(t),{detail:e}))}function N(t,e){var n=t.includes("?")?"&":"?";return"".concat(t).concat(n,"guid=").concat(e)}function V(t,e,n,r){t.successMessage&&t.successMessage.setAttribute("hidden","hidden"),t.errorMessage&&t.errorMessage.setAttribute("hidden","hidden"),t.allowedFormatSpan&&(t.allowedFormatSpan.innerHTML=o(j,this).get(e,"allow_span","")),t.fileTypeSpan&&(t.fileTypeSpan.innerHTML=o(j,this).get(e,"file_type_span","file"))}function U(e,n){t(S,this,C).call(this,"beforeSend",{environment:n}),n.data.params=o(j,this).getParams(o(j,this).get(e,"param_fields")),t(S,this,C).call(this,"afterSendPreparation",{environment:n})}function H(e,n){t(S,this,C).call(this,"error",{error:e}),n&&(n.removeAttribute("hidden"),n.textContent="Upload failed.")}function R(e,n){t(S,this,C).call(this,"complete",{xhr:e}),n?(n.removeAttribute("hidden"),n.textContent="Upload completed successfully."):t(S,this,G).call(this,"Upload completed successfully.","primary")}function q(e,n){t(S,this,C).call(this,"loadStart",{event:e}),n&&(n.removeAttribute("hidden"),n.max=e.total,n.value=e.loaded)}function Y(e,n){t(S,this,C).call(this,"progress",{event:e}),n&&(n.max=e.total,n.value=e.loaded)}function z(e,n){t(S,this,C).call(this,"loadEnd",{event:e}),n&&(n.max=e.total,n.value=e.loaded)}function J(e,n,r,i,a,c,l){t(S,this,C).call(this,"completeAll",{xhr:e}),n&&setTimeout((function(){n.setAttribute("hidden","hidden"),r&&r.setAttribute("hidden","hidden"),i&&i.setAttribute("hidden","hidden")}),5e3),t(S,this,B).call(this,a,c,o(j,this).getParams(o(j,this).get(l,"display_fields")))}var K=new WeakMap,Q=new WeakMap,X=new WeakSet,Z=function(){return s((function t(e,n){i(this,t),l(this,X),a(this,K,void 0),a(this,Q,void 0),c(K,this,e),c(Q,this,n)}),[{key:"delete",value:function(e){var n=this;!e||e.length<=30||o(Q,this).modal.confirm("Are you sure you want to delete this file! It can not be undone!").then((function(){return t(X,n,$).call(n,e)}))}}])}();function $(e){o(K,this)&&(t(X,this,rt).call(this,"beforeFileDelete",{guid:e}),fetch(t(X,this,it).call(this,e),{method:"GET",headers:{"Content-Type":"application/json"}}).then((function(t){return t.json()})).then(t(X,this,tt).bind(this,e)).catch(console.error))}function tt(e,n){n.success?(t(X,this,nt).call(this,e),t(X,this,et).call(this,n.success,"primary"),t(X,this,rt).call(this,"afterFileDelete",{data:n,guid:e})):n.error&&(t(X,this,rt).call(this,"onFileDeleteError",{data:n,guid:e}),t(X,this,et).call(this,n.error,"danger"))}function et(t,e){o(Q,this).notification({message:t,status:e,pos:"top-center",timeout:7e3})}function nt(e){var n=document.getElementById(e);n&&(t(X,this,rt).call(this,"beforeFileRemoveFromUI",{element:n,guid:e}),n.remove())}function rt(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};document.dispatchEvent(new CustomEvent("vdm.uikit.delete.".concat(t),{detail:e}))}function it(t){var e=o(K,this).includes("?")?"&":"?";return"".concat(o(K,this)).concat(e,"guid=").concat(t)}f(Z,"ERROR_ENDPOINT","Error: The delete endpoint is not configured.");var ot,at=["endpoint_type","target_class"];ot=window,document.addEventListener("DOMContentLoaded",(function(){var t;if(t=ot.UIkit?ot.UIkit:require("uikit").default,ot.VDM){var e=ot.VDM.uikit.config||{},n=e.endpoint_type,r=e.target_class,i=function(t,e){if(null==t)return{};var n,r,i=function(t,e){if(null==t)return{};var n={};for(var r in t)if({}.hasOwnProperty.call(t,r)){if(e.includes(r))continue;n[r]=t[r]}return n}(t,e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);for(r=0;r0&&new _(a,n,t)}}})),ot.VDMDeleteFile=function(t,e){ot.VDM.uikit.delete_file[t]&&ot.VDM.uikit.delete_file[t]instanceof Z&&ot.VDM.uikit.delete_file[t].delete(e)}}(); diff --git a/package.json b/package.json index bb13f30..16ff2f7 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "vdm.uikit.uploader", - "title": "Uploader", - "description": "Uploader is an intuitive and lightweight JavaScript solution for embedding upload functionality into your website.", - "version": "2.1.2", - "main": "dist/js/Uploader.min.js", + "name": "vdm.uikit", + "title": "VDM Uikit", + "description": "VDM-Uikit is an intuitive and lightweight JavaScript (extension) to Uikit for embedding upload/delete functionality of server files into your website.", + "version": "3.0.0", + "main": "dist/js/vdm.min.js", "scripts": { "build": "rollup -c" }, diff --git a/rollup.config.js b/rollup.config.js index 78312b9..cf08d8a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,12 +6,12 @@ import license from 'rollup-plugin-license'; import replace from '@rollup/plugin-replace'; const licenseLine = { - banner: `/*! VDM Uikit Uploader v${require('./package.json').version} | https://git.vdm.dev/joomla/uikit | (c) 2020 - ${new Date().getFullYear()} Llewellyn van der Merwe | MIT License */` + banner: `/*! VDM Uikit v${require('./package.json').version} | https://git.vdm.dev/joomla/uikit | (c) 2020 - ${new Date().getFullYear()} Llewellyn van der Merwe | MIT License */` }; const licenseHeader = { banner: `/** - * VDM Uikit Uploader v${require('./package.json').version} + * VDM Uikit v${require('./package.json').version} * https://git.vdm.dev/joomla/uikit * (c) 2020 - ${new Date().getFullYear()} Llewellyn van der Merwe * MIT License @@ -20,7 +20,7 @@ const licenseHeader = { export default [ { - input: 'src/js/Uploader.js', + input: 'src/js/vdm.js', plugins: [ license(licenseHeader), replace({ @@ -31,17 +31,17 @@ export default [ commonjs() ], output: { - file: 'dist/js/Uploader.js', + file: 'dist/js/vdm.js', format: 'iife', - name: 'Uploader', + name: 'VDMUikit', globals: { - uikit: 'UIkit', + uikit: 'UIkit' } }, external: ['uikit'], // UIkit is treated as external }, { - input: 'src/js/Uploader.js', + input: 'src/js/vdm.js', plugins: [ resolve(), // Resolves local and node modules commonjs(), @@ -58,11 +58,11 @@ export default [ ], external: ['uikit'], // UIkit is treated as external output: { - file: 'dist/js/Uploader.min.js', + file: 'dist/js/vdm.min.js', format: 'iife', - name: 'Uploader', + name: 'VDMUikit', globals: { - uikit: 'UIkit', + uikit: 'UIkit' }, }, }, diff --git a/src/js/Uploader.js b/src/js/Uploader.js deleted file mode 100644 index 5bdd082..0000000 --- a/src/js/Uploader.js +++ /dev/null @@ -1,64 +0,0 @@ -import { UikitUploader } from './core/UikitUploader'; - -(function(global) { - document.addEventListener('DOMContentLoaded', function() { - let UIkitLocal; - - if (!global.UIkit) { - UIkitLocal = require('uikit').default; - } else { - UIkitLocal = global.UIkit; - } - - const { endpoint, targetClass, ...additionalConfig } = global.vdmUploaderConfig || {}; - - if (!endpoint) { - if (process.env.DEBUG) console.error('Endpoint is not defined, exiting initialization.'); - return; - } - - if (!targetClass) { - if (process.env.DEBUG) console.error('The target class is not defined, exiting initialization.'); - return; - } - - const uploadElements = document.querySelectorAll('.' + targetClass); - const config = {}; - - uploadElements.forEach(element => { - const id = element.getAttribute('id'); - const uploadEndpoint = global.vdmUploaderConfig[id] ? global.vdmUploaderConfig[id].endpoint : null; - - if (!uploadEndpoint) { - if (process.env.DEBUG) console.error(`Upload Endpoint for ${id} is not defined, exiting initialization for this field.`); - return; // Skip this field if no upload endpoint is found - } - - const progressBarId = element.dataset.progressbarId; - const typeId = element.dataset.typeId; - // optional - const displayEndpoint = global.vdmUploaderConfig[id] ? global.vdmUploaderConfig[id].endpoint_display : null; - const displayId = element.dataset.displayId || null; - const successId = element.dataset.successId || null; - const errorId = element.dataset.errorId || null; - const allowedFormatId = element.dataset.allowedFormatId || null; - const fileTypeId = element.dataset.fileTypeId || null; - - config[id] = { - bar: progressBarId, - typeId: typeId, - endpoint: uploadEndpoint, - successId: successId, - errorId: errorId, - allowedFormatId: allowedFormatId, - fileTypeId: fileTypeId, - displayId: displayId, - displayEndpoint: displayEndpoint - }; - }); - - if (Object.keys(config).length > 0) { - new UikitUploader(config, endpoint, UIkitLocal); - } - }); -})(window); diff --git a/src/js/core/delete-file.js b/src/js/core/delete-file.js new file mode 100644 index 0000000..6c3a420 --- /dev/null +++ b/src/js/core/delete-file.js @@ -0,0 +1,154 @@ +/** + * Helper class for deleting files from the server. + * + * @class + * @classdesc This class provides methods for deleting files from the server. + */ +export class DeleteFile { + /** + * The endpoint to which files would be deleted. + * Stored as a private property and used internally within the class methods. + * This field must be a string representing a valid URL. + * + * @type {string} + * @private + */ + #endpoint; + + /** + * The UIKit variable is a reference to a UI framework for building web applications. + */ + #uikit; + + /** + * The error message that is displayed when the delete endpoint is not configured. + * + * @type {string} + */ + static ERROR_ENDPOINT = 'Error: The delete endpoint is not configured.'; + + /** + * Creates an instance of the DeleteHelper class. + * + * @param {string} endpoint - The endpoint where the files will be uploaded. + * @param {any} uikit - Reference to UIKit. + */ + constructor(endpoint, uikit) { + this.#endpoint = endpoint; + this.#uikit = uikit; + } + + /** + * Deletes a file with the given fileGuid. + * + * @param {string} fileGuid - The unique identifier of the file to delete. + * @return {void} + */ + delete(fileGuid) { + if (!fileGuid || fileGuid.length <= 30) { + return; + } + + this.#uikit.modal.confirm('Are you sure you want to delete this file! It can not be undone!') + .then(() => this.#serverDelete(fileGuid)); + } + + /** + * Deletes a file from the server. + * + * @param {string} fileGuid - The unique identifier of the file to be deleted. + * @return {void} + */ + #serverDelete(fileGuid) { + if (!this.#endpoint) { + if (process.env.DEBUG) console.error(DeleteFile.ERROR_ENDPOINT); + return; + } + + this.#dispatchEvent('beforeFileDelete', {guid: fileGuid}); + + fetch(this.#buildUrl(fileGuid), { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(this.#handleResponse.bind(this, fileGuid)) + .catch(console.error); + } + + /** + * Handles the response from the server after a file upload. + * Removes the file from the UI if the response is successful and shows a success notification. + * Shows an error notification if the response contains an error. + * @param {string} fileGuid - The unique identifier of the file that was uploaded. + * @param {object} data - The response data from the server. + * @return {void} + */ + #handleResponse(fileGuid, data) { + if (data.success) { + this.#fileRemoveFromUI(fileGuid); + this.#showNotification(data.success, 'primary'); + this.#dispatchEvent('afterFileDelete', {data: data, guid: fileGuid}); + } else if (data.error) { + this.#dispatchEvent('onFileDeleteError', {data: data, guid: fileGuid}); + this.#showNotification(data.error, 'danger'); + } + } + + /** + * Displays a notification with the given message and status. + * + * @param {string} message - The message to be displayed in the notification. + * @param {string} status - The status of the notification (e.g., 'success', 'error', 'warning'). + * @return {void} - Does not return a value. + */ + #showNotification(message, status) { + this.#uikit.notification({ + message, + status, + pos: 'top-center', + timeout: 7000 + }); + } + + /** + * Remove file from user interface by fileGuid. + * + * @param {string} fileGuid - The unique identifier of the file. + * @example + * removeFileFromUI('file123') + * + * @return {void} - No return value. + */ + #fileRemoveFromUI(fileGuid) { + const listItem = document.getElementById(fileGuid); + if (listItem) { + this.#dispatchEvent('beforeFileRemoveFromUI', {element: listItem, guid: fileGuid}); + listItem.remove(); + } + } + + /** + * Dispatches a custom event with optional detail data. + * + * @param {string} eventName - The name of the event to dispatch. + * @param {object} [detail={}] - The optional detail data to include with the event. + * @return {void} + */ + #dispatchEvent(eventName, detail = {}) { + document.dispatchEvent(new CustomEvent(`vdm.uikit.delete.${eventName}`, {detail})); + } + + /** + * Builds a URL by appending the GUID parameter. + * + * @param {string} guid - The GUID parameter to be appended to the URL. + * @return {string} - The constructed URL with the GUID parameter appended. + */ + #buildUrl(guid) { + const separator = this.#endpoint.includes('?') ? '&' : '?'; + return `${this.#endpoint}${separator}guid=${guid}`; + } +} \ No newline at end of file diff --git a/src/js/core/UikitUploader.js b/src/js/core/upload-file.js similarity index 71% rename from src/js/core/UikitUploader.js rename to src/js/core/upload-file.js index fed916a..7eb03eb 100644 --- a/src/js/core/UikitUploader.js +++ b/src/js/core/upload-file.js @@ -1,28 +1,42 @@ -import {UploadHelper} from './UploadHelper.js'; -import {DisplayHelper} from './DisplayHelper.js'; +import {FileType} from '../util/file-type.js'; +import {Display} from '../util/display.js'; /** - * Helper class for uploading files. + * Class for uploading files. * * @class * @classdesc This class provides methods for uploading files to a server. */ -export class UikitUploader { - #uploadHelper; - #displayHelper; - #uikit; - #uploadInstances = {}; +export class UploadFile { + /** + * Utility class for uploading files. + * + * @class + */ + #fileType; /** - * Creates an instance of the UikitUploader class. + * Helper class for displaying elements on the UI. + * + * @class + */ + #display; + + /** + * The UIKit variable is a reference to a UI framework for building web applications. + */ + #uikit; + + /** + * Creates an instance of the UploadFile class. * * @param {Object} config - Configuration details for uploader instances. * @param {string} endpoint - The endpoint where the files will be uploaded. * @param {any} uikit - Reference to UIKit. */ constructor(config, endpoint, uikit) { - this.#uploadHelper = new UploadHelper(endpoint); - this.#displayHelper = new DisplayHelper(); + this.#fileType = new FileType(endpoint); + this.#display = new Display(); this.#uikit = uikit; this.#initializeFields(config); @@ -63,13 +77,13 @@ export class UikitUploader { try { await this.#initUpload(id, guid, bar, endpoint, successId, errorId, allowedFormatId, fileTypeId, displayId, displayEndpoint); } catch (error) { - this.#notifyError(error.message); + this.#showNotification(error.message, 'danger'); } } }; typeField.addEventListener('change', () => initializeUpload(typeField.value)); - initializeUpload(typeField.value).catch(error => this.#notifyError(error.message)); + initializeUpload(typeField.value).catch(error => this.#showNotification(error.message, 'danger')); } /** @@ -102,7 +116,7 @@ export class UikitUploader { }); const call = `${id}${typeGuid}`; - await this.#uploadHelper.init(call, typeGuid, true); + await this.#fileType.init(call, typeGuid, true); const elements = this.#getUploadElements(progressBarId, successId, errorId, allowedFormatId, fileTypeId, displayId); @@ -113,8 +127,8 @@ export class UikitUploader { this.#uikit.upload(`#${id}`, { url: this.#buildUrl(uploadEndpoint, typeGuid), multiple: true, - allow: this.#uploadHelper.get(call, 'allow', false), - name: this.#uploadHelper.get(call, 'name', 'files'), + allow: this.#fileType.get(call, 'allow', false), + name: this.#fileType.get(call, 'name', 'files'), beforeSend: (env) => this.#handleBeforeSend(call, env), beforeAll: (files) => this.#dispatchEvent('beforeAll', {files}), load: (e) => this.#dispatchEvent('load', {event: e}), @@ -132,50 +146,57 @@ export class UikitUploader { /** * Returns the required HTML elements by their IDs. + * If an element ID is null or the element does not exist on the page, + * the corresponding value in the returned object will be null. * - * @param {string} progressBarId - The ID of the progress bar element. - * @param {string} successId - The ID of the success message element. - * @param {string} errorId - The ID of the error message element. - * @param {string} allowedFormatId - The ID of the allowed format span element. - * @param {string} fileTypeId - The ID of the file type span element. - * @param {string} displayId - The ID of the display area element. - * @returns {object} - An object containing the required HTML elements. + * @param {string|null} progressBarId - The ID of the progress bar element, or null. + * @param {string|null} successId - The ID of the success message element, or null. + * @param {string|null} errorId - The ID of the error message element, or null. + * @param {string|null} allowedFormatId - The ID of the allowed format span element, or null. + * @param {string|null} fileTypeId - The ID of the file type span element, or null. + * @param {string|null} displayId - The ID of the display area element, or null. + * @returns {object} - An object containing the required HTML elements or null if they do not exist. */ #getUploadElements(progressBarId, successId, errorId, allowedFormatId, fileTypeId, displayId) { return { - progressBar: document.getElementById(progressBarId), - successMessage: document.getElementById(successId), - errorMessage: document.getElementById(errorId), - allowedFormatSpan: document.getElementById(allowedFormatId), - fileTypeSpan: document.getElementById(fileTypeId), - displayArea: document.getElementById(displayId) + progressBar: progressBarId ? document.getElementById(progressBarId) : null, + successMessage: successId ? document.getElementById(successId) : null, + errorMessage: errorId ? document.getElementById(errorId) : null, + allowedFormatSpan: allowedFormatId ? document.getElementById(allowedFormatId) : null, + fileTypeSpan: fileTypeId ? document.getElementById(fileTypeId) : null, + displayArea: displayId ? document.getElementById(displayId) : null }; } /** * Initializes the display area with data from the display endpoint. * - * @param {string} displayEndpoint - The endpoint to retrieve the display data from. - * @param {string} displayId - The id of the display area element in the DOM. + * @param {string|null} displayEndpoint - The endpoint to retrieve the display data from. + * @param {string|null} displayId - The id of the display area element in the DOM. * @param {object} params - Additional parameters to be passed to the display helper. - * * @return {void} */ #setupDisplayArea(displayEndpoint, displayId, params = {}) { - const displayArea = document.getElementById(displayId); + const displayArea = displayId ? document.getElementById(displayId) : null; if (displayEndpoint && displayArea) { - this.#displayHelper.set(displayEndpoint, displayArea, params); + this.#display.set(displayEndpoint, displayArea, params); } } /** - * Notifies the user of an error using UIKit notifications. + * Displays a notification with the given message and status. * - * @param {string} message - The error message to display. - * @return {void} + * @param {string} message - The message to be displayed in the notification. + * @param {string} status - The status of the notification (e.g., 'success', 'error', 'warning'). + * @return {void} - Does not return a value. */ - #notifyError(message) { - this.#uikit.notification({message, status: 'danger', timeout: 7000}); + #showNotification(message, status) { + this.#uikit.notification({ + message, + status, + pos: 'top-center', + timeout: 7000 + }); } /** @@ -225,8 +246,8 @@ export class UikitUploader { #prepareUploadUI(elements, call, successId, errorId) { if (elements.successMessage) elements.successMessage.setAttribute('hidden', 'hidden'); if (elements.errorMessage) elements.errorMessage.setAttribute('hidden', 'hidden'); - if (elements.allowedFormatSpan) elements.allowedFormatSpan.innerHTML = this.#uploadHelper.get(call, 'allow_span', ''); - if (elements.fileTypeSpan) elements.fileTypeSpan.innerHTML = this.#uploadHelper.get(call, 'file_type_span', 'file'); + if (elements.allowedFormatSpan) elements.allowedFormatSpan.innerHTML = this.#fileType.get(call, 'allow_span', ''); + if (elements.fileTypeSpan) elements.fileTypeSpan.innerHTML = this.#fileType.get(call, 'file_type_span', 'file'); } /** @@ -238,7 +259,7 @@ export class UikitUploader { */ #handleBeforeSend(call, environment) { this.#dispatchEvent('beforeSend', {environment}); - environment.data.params = this.#uploadHelper.getParams(this.#uploadHelper.get(call, 'param_fields')); + environment.data.params = this.#fileType.getParams(this.#fileType.get(call, 'param_fields')); this.#dispatchEvent('afterSendPreparation', {environment}); } @@ -262,13 +283,15 @@ export class UikitUploader { * Handles the upload completion. * * @param {XMLHttpRequest} xhr - The XMLHttpRequest object representing the upload request. - * @param {HTMLElement} successMessage - The success message element to display. + * @param {HTMLElement|null} successMessage - The success message element to display. */ #handleComplete(xhr, successMessage) { this.#dispatchEvent('complete', {xhr}); if (successMessage) { successMessage.removeAttribute('hidden'); successMessage.textContent = 'Upload completed successfully.'; + } else { + this.#showNotification('Upload completed successfully.', 'primary'); } } @@ -276,7 +299,7 @@ export class UikitUploader { * Handles the loadStart event. * * @param {Event} e - The loadStart event object. - * @param {HTMLElement} progressBar - The progress bar element. Optional. + * @param {HTMLElement|null} progressBar - The progress bar element. Optional. * @return {void} */ #handleLoadStart(e, progressBar) { @@ -292,7 +315,7 @@ export class UikitUploader { * Handles the progress event. * * @param {Event} e - The progress event. - * @param {Element} progressBar - The progress bar element. + * @param {Element|null} progressBar - The progress bar element. * * @return {void} */ @@ -308,7 +331,7 @@ export class UikitUploader { * Handles the loadEnd event. * * @param {Event} e - The loadEnd event object. - * @param {Element} progressBar - The progress bar element to update. + * @param {Element|null} progressBar - The progress bar element to update. * * @return {void} */ @@ -324,11 +347,11 @@ export class UikitUploader { * Handles the completion of all uploads. * * @param {XMLHttpRequest} xhr - The XMLHttpRequest object used for the uploads. - * @param {HTMLElement} progressBar - The progress bar element. - * @param {HTMLElement} successMessage - The success message element. - * @param {HTMLElement} errorMessage - The error message element. - * @param {string} displayEndpoint - The display endpoint. - * @param {string} displayId - The display ID. + * @param {HTMLElement|null} progressBar - The progress bar element. + * @param {HTMLElement|null} successMessage - The success message element. + * @param {HTMLElement|null} errorMessage - The error message element. + * @param {string|null} displayEndpoint - The display endpoint. + * @param {string|null} displayId - The display ID. * @param {Object} call - The call object. * * @return {void} @@ -342,6 +365,6 @@ export class UikitUploader { if (errorMessage) errorMessage.setAttribute('hidden', 'hidden'); }, 5000); } - this.#setupDisplayArea(displayEndpoint, displayId, this.#uploadHelper.getParams(this.#uploadHelper.get(call, 'display_fields'))); + this.#setupDisplayArea(displayEndpoint, displayId, this.#fileType.getParams(this.#fileType.get(call, 'display_fields'))); } } diff --git a/src/js/core/DisplayHelper.js b/src/js/util/display.js similarity index 81% rename from src/js/core/DisplayHelper.js rename to src/js/util/display.js index b6b7f7d..3684d6c 100644 --- a/src/js/core/DisplayHelper.js +++ b/src/js/util/display.js @@ -12,7 +12,7 @@ * * await helper.set(endpoint, area, params); */ -export class DisplayHelper { +export class Display { constructor() { } @@ -29,6 +29,9 @@ export class DisplayHelper { */ set = async (displayEndpoint, displayArea, params) => { try { + // Trigger a custom event before hide files display the entity files + this.#dispatchEvent('beforeGetFilesDisplay', {endpoint: displayEndpoint, element: displayArea, params: params}); + // Build the URL with the query parameters const url = this.#buildUrl(displayEndpoint, params); @@ -62,35 +65,23 @@ export class DisplayHelper { // If there's no response.data or it's empty, clear the display area if (!result.data || result.data.trim() === '') { // Trigger a custom event before hide files display the entity files - const beforeHideFilesDisplay = new CustomEvent('vdm.uikit.uploader.beforeHideFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(beforeHideFilesDisplay); + this.#dispatchEvent('beforeHideFilesDisplay', {result: result, element: displayArea}); displayArea.innerHTML = ''; // Empty the display area displayArea.setAttribute('hidden', 'hidden'); // Trigger a custom event after hide files display the entity files - const afterHideFilesDisplay = new CustomEvent('vdm.uikit.uploader.afterHideFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(afterHideFilesDisplay); + this.#dispatchEvent('afterHideFilesDisplay', {result: result, element: displayArea}); } else { // Trigger a custom event before displaying the entity files - const beforeFilesDisplayEvent = new CustomEvent('vdm.uikit.uploader.beforeFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(beforeFilesDisplayEvent); + this.#dispatchEvent('beforeFilesDisplay', {result: result, element: displayArea}); // Replace the display area content with the new HTML displayArea.innerHTML = result.data; displayArea.removeAttribute('hidden'); // Trigger a custom event after displaying the entity files - const afterFilesDisplayEvent = new CustomEvent('vdm.uikit.uploader.afterFilesDisplay', { - detail: {result, displayArea} - }); - document.dispatchEvent(afterFilesDisplayEvent); + this.#dispatchEvent('afterFilesDisplay', {result: result, element: displayArea}); } } catch (error) { // If an error occurs, log it in debug mode @@ -100,6 +91,17 @@ export class DisplayHelper { } }; + /** + * Dispatches a custom event with optional detail data. + * + * @param {string} eventName - The name of the event to dispatch. + * @param {object} [detail={}] - The optional detail data to include with the event. + * @return {void} + */ + #dispatchEvent(eventName, detail = {}) { + document.dispatchEvent(new CustomEvent(`vdm.uikit.display.${eventName}`, {detail})); + } + /** * It's a private method that builds a complete URL from the endpoint and an object containing parameters. * It uses the URLSearchParams interface to turn the parameters object to a query string, diff --git a/src/js/core/UploadHelper.js b/src/js/util/file-type.js similarity index 91% rename from src/js/core/UploadHelper.js rename to src/js/util/file-type.js index 94dba14..92e4bdb 100644 --- a/src/js/core/UploadHelper.js +++ b/src/js/util/file-type.js @@ -1,12 +1,12 @@ /** - * `UploadHelper` is a utility class that simplifies operations related to file uploading. + * `FileType` is a utility class that simplifies operations related to file uploading. * It handles the storage and retrieval of metadata associated with each upload and initializes * the upload process by setting up endpoint configuration. It also provides methods for * triggering the upload activities in an asynchronous manner. * * @class * @example - * const helper = new UploadHelper('http://example.com/upload'); + * const helper = new FileType('http://example.com/upload'); * const uniqueId = 'file123'; * const globalId = 'glob124'; * const data = { user: 'John Doe', file: 'myfile.txt' }; @@ -14,7 +14,7 @@ * helper.set(uniqueId, data); * await helper.init(uniqueId, globalId); */ -export class UploadHelper { +export class FileType { /** * The endpoint to which files would be uploaded. * Stored as a private property and used internally within the class methods. @@ -26,7 +26,7 @@ export class UploadHelper { #endpoint; /** - * It is a private object used to store the data associated with an instance of `UploadHelper`. + * It is a private object used to store the data associated with an instance of `FileType`. * Default is an empty object. * This data is used when performing uploads. * @@ -36,9 +36,9 @@ export class UploadHelper { #data = {}; /** - * Constructor for the UploadHelper class. + * Constructor for the FileType class. * - * @param {string} endpoint - The endpoint to be associated with the instance of the UploadHelper. + * @param {string} endpoint - The endpoint to be associated with the instance of the FileType. */ constructor(endpoint) { // Initialize private field with passed endpoint argument @@ -94,7 +94,7 @@ export class UploadHelper { }; /** - * Asynchronously initializes the UploadHelper object. + * Asynchronously initializes the FileType object. * * @param {string} id - The unique identifier associated with the initialization. * @param {string} guid - The globally unique identifier used to build the URL for fetching. @@ -110,7 +110,7 @@ export class UploadHelper { } try { - const url = this.#buildUrl(this.#endpoint, guid); + const url = this.#buildUrl(guid); const result = await this.#fetchData(url); if (process.env.DEBUG) console.log('Data fetched:', result); @@ -178,16 +178,15 @@ export class UploadHelper { /** * Builds a URL appending a unique identifier as a parameter. * - * @param {string} endpoint - The base endpoint of the URL. * @param {string} guid - The globally unique identifier to append to the URL. * @returns {string} The constructed URL with the appended unique identifier. * @private */ - #buildUrl = (endpoint, guid) => { + #buildUrl = (guid) => { // Determine the appropriate separator for the query parameter - const separator = endpoint.includes('?') ? '&' : '?'; + const separator = this.#endpoint.includes('?') ? '&' : '?'; // Return the constructed URL - return `${endpoint}${separator}guid=${guid}`; + return `${this.#endpoint}${separator}guid=${guid}`; }; } diff --git a/src/js/vdm.js b/src/js/vdm.js new file mode 100644 index 0000000..e159555 --- /dev/null +++ b/src/js/vdm.js @@ -0,0 +1,104 @@ +import { UploadFile } from './core/upload-file'; +import { DeleteFile } from './core/delete-file'; + +(function(global) { + document.addEventListener('DOMContentLoaded', function() { + let UIkitLocal; + + if (!global.UIkit) { + UIkitLocal = require('uikit').default; + } else { + UIkitLocal = global.UIkit; + } + + if (!global.VDM) { + if (process.env.DEBUG) console.error('VDM is not defined, exiting initialization.'); + return; + } + + const { endpoint_type, target_class, ...additionalConfig } = global.VDM.uikit.config || {}; + + if (!endpoint_type) { + if (process.env.DEBUG) console.error('File Type Endpoint is not defined, exiting initialization.'); + return; + } + + if (!target_class) { + if (process.env.DEBUG) console.error('The target class is not defined, exiting initialization.'); + return; + } + + const uploadElements = document.querySelectorAll('.' + target_class); + const config = {}; + + // Ensure the global.VDM.uikit.delete_file exists, or initialize it + if (!global.VDM.uikit.delete_file) { + global.VDM.uikit.delete_file = {}; // Initialize delete_file object if it doesn't exist + } + + uploadElements.forEach(element => { + const id = element.getAttribute('id'); + const uploadEndpoint = additionalConfig[id]?.endpoint_upload ?? null; + + if (!uploadEndpoint) { + if (process.env.DEBUG) console.error(`Upload Endpoint for ${id} is not defined, exiting initialization for this field.`); + return; // Skip this field if no upload endpoint is found + } + + const typeId = element.dataset.typeId; + + // optional + const progressBarId = element.dataset.progressbarId ?? null; + const displayEndpoint = additionalConfig[id]?.endpoint_display ?? null; + const displayId = element.dataset.displayId || null; + const deleteEndpoint = additionalConfig[id]?.endpoint_delete ?? null; + const successId = element.dataset.successId || null; + const errorId = element.dataset.errorId || null; + const allowedFormatId = element.dataset.allowedFormatId || null; + const fileTypeId = element.dataset.fileTypeId || null; + + config[id] = { + bar: progressBarId, + typeId: typeId, + endpoint: uploadEndpoint, + successId: successId, + errorId: errorId, + allowedFormatId: allowedFormatId, + fileTypeId: fileTypeId, + displayId: displayId, + displayEndpoint: displayEndpoint + }; + + // if delete endpoint found + if (deleteEndpoint) + { + global.VDM.uikit.delete_file[id] = new DeleteFile(deleteEndpoint, UIkitLocal); + } + }); + + if (Object.keys(config).length > 0) { + new UploadFile(config, endpoint_type, UIkitLocal); + } + + }); + + /** + * Performs a delete operation on the specified file. + * + * @param {string} id - The identifier of the delete_file object. + * @param {string} guid - The file GUID to delete. + * + * @return {void} - No return value. + */ + global.VDMDeleteFile = function(id, guid) { + // Check if the delete_file object exists and is an instance of DeleteFile + if (global.VDM.uikit.delete_file[id] && global.VDM.uikit.delete_file[id] instanceof DeleteFile) { + // Call the delete method on the DeleteFile instance + global.VDM.uikit.delete_file[id].delete(guid); + } else { + // Log an error or handle the case where the object is missing or invalid + if (process.env.DEBUG) console.error(`Error: delete_file with id ${id} is either not defined or not an instance of DeleteFile.`); + } + }; + +})(window); \ No newline at end of file