diff --git a/code/test-data-tracker/test-data-tracker.css b/code/test-data-tracker/test-data-tracker.css index 68c83244..b382e624 100644 --- a/code/test-data-tracker/test-data-tracker.css +++ b/code/test-data-tracker/test-data-tracker.css @@ -335,4 +335,13 @@ img.knHeader__logo-image { width: 80% !important; /* Medium width on medium screens */ max-width: 800px !important; } +} + +/*********************************************/ +/******** Add Multiple Attachments ********/ +/*********************************************/ +#dropzone-field_4212 { + border: 2px dashed #A8B2BC; + border-radius: 5%; + font-weight: 500; } \ No newline at end of file diff --git a/code/test-data-tracker/test-data-tracker.js b/code/test-data-tracker/test-data-tracker.js index 751eb342..d1c6efd4 100644 --- a/code/test-data-tracker/test-data-tracker.js +++ b/code/test-data-tracker/test-data-tracker.js @@ -1177,4 +1177,181 @@ $(document).on("knack-scene-render.scene_1607", function (event, scene) { /***************************************************************/ $(document).on("knack-scene-render.any", function (event, scene) { $(".kn-modal-bg").off("click"); -}); \ No newline at end of file +}); + +/***************************************/ +/*** TEST - Add multiple attachments ***/ +/***************************************/ +const KNACK_APP_ID = Knack.application_id; +var fieldId = 'field_4212'; +var viewId = 'view_4214'; +var sceneId = 'scene_1688'; +var maxFilesLoaded = 10; // Maximum amount of files uploaded in dropzone + +$(document).on(`knack-view-render.${viewId}`, function (event, view, data) { + var knackHeaders = { + 'X-Knack-Application-ID': KNACK_APP_ID, + 'Authorization': Knack.getUserToken() + } + LazyLoad.css('https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.2/dropzone.min.css', function () {}); + LazyLoad.js(['https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.2/min/dropzone.min.js'], function () { + Dropzone.autoDiscover = false; + initialize(); + }); + + var $fileInputBtn=$(`#kn-input-${fieldId} input[type=file]`); + var $formSubmitBtn = $(`#${viewId} .kn-submit button[type=submit]`); + $formSubmitBtn.prop('disabled', true); + + $fileInputBtn.prop("multiple", true); + $fileInputBtn.after(`
`); + $fileInputBtn.hide(); + + var $buFiledrag=$(`#filedrag-${fieldId}`); + var $form = $(`#${viewId} form`); + var $formConfirmation = $(`#${viewId} .kn-form-confirmation`); + + var files = []; + + function initialize() { + var upurl = `${Knack.api_url}/v1/applications/${KNACK_APP_ID}/assets/file/upload`; + + var dz = new Dropzone(`div#dropzone-${fieldId}`, + { + url: upurl, + headers: knackHeaders, + paramName: 'files', + addRemoveLinks: true, + maxFiles: maxFilesLoaded, + dictDefaultMessage: '

', + }); + // Only allow submission when we are not actively processing images + dz.on("addedfile", function(file) { $formSubmitBtn.prop('disabled', true); }); + dz.on("queuecomplete", function(file) { $formSubmitBtn.prop('disabled', false); }); + dz.on("removedfile", deleteFile); + dz.on("maxfilesexceeded", function(file) { this.removeFile(file); }); + dz.on("success", function(file, responseText, e) { + file['responseText'] = responseText; + files.push(file); + }); + $formSubmitBtn.on('click', handleFormSubmit); + } + + function deleteFile(removedFile) { + // Update the files array + files = files.filter(file => file.name != removedFile.name); + } + + function showMessage(err, message) { + $formConfirmation.show(); + var clazz = err === true ? "is-error" : "success"; + $formConfirmation.append(`

${message}

`); + if (err === true) { + $formConfirmation.find('.kn-form-reload').hide(); + } else { + $formConfirmation.find('.kn-form-reload').show(); + } + } + + function clearMessages() { + // Form messages come from Knack JS - they know the form is still visible. form-confirm is used + // when form might have been hidden + $formConfirmation.find('.kn-message').remove(); + $form.find('.kn-message').remove(); + $formConfirmation.hide(); + } + + function handleFormSubmit(event) { + event.preventDefault(); + + // Show progress and prevent an impatient click + Knack.showSpinner(); + $formSubmitBtn.addClass("is-loading"); + $formSubmitBtn.prop('disabled', true); + + // Grab all other data items from the form into a simple key-value object + var formData = $form.serializeArray().reduce(function (output, value) { + var field = value.name.split('-').pop(); // Not sure why but multichoice does `view_id-field_id` + output[field] = value.value + return output + }, {}); + + var targetUrl = `${Knack.api_url}/v1/pages/${sceneId}/views/${viewId}/records` + + const upload = file => new Promise((res, rej) => { + // Add the one file we are submitting to the existing form data + formDataClone = { ...formData }; + formDataClone[fieldId] = file.responseText.id; + + var jqXHR = $.ajax({ + type: 'POST', + url: targetUrl, + headers: knackHeaders, + data: formDataClone, + enctype: 'multipart/form-data' + }); + + // Mark uploaded file with record created + // TODO - should we just remove it from the dropzone? That might be + // simpler than maintaining a list in memory + jqXHR.done(() => { + file['knackRecordCreated'] = true; + res(); + }); + + jqXHR.fail((xhr, textStatus, errorThrown) => { + let json = JSON.parse(xhr.responseText); + let errMsg = "Unknown Error"; + if (json && json.errors) { + errMsg = json.errors.reduce((acc, cur) => acc + `

${cur.message}

`, ""); + } + rej(errMsg); + }); + }); + + clearMessages(); + // TODO handle HTTP 429 events + // TODO try #s > 2 to see how well it works + asyncPool(2, files, upload) + .then(() => { + console.log("success"); + $form.hide(); + history.back(); // go back to parent page + //showMessage(false, "Form submitted"); // reload form + }) + .catch((errors) => { + log("errors"); + console.log(errors); + $formSubmitBtn.removeClass("is-loading"); + Knack.hideSpinner(); + showMessage(true, errors); + $formSubmitBtn.prop('disabled', false); + }); + } +}); + +function asyncPool(poolLimit, array, iteratorFn) { + let i = 0; + const ret = []; + const executing = []; + const enqueue = function() { + if (i === array.length) { + return Promise.resolve(); + } + const item = array[i++]; + const p = Promise.resolve().then(() => iteratorFn(item, array)); + ret.push(p); + + let r = Promise.resolve(); + + if (poolLimit <= array.length) { + const e = p.then(() => executing.splice(executing.indexOf(e), 1)); + executing.push(e); + if (executing.length >= poolLimit) { + r = Promise.race(executing); + } + } + return r.then(() => enqueue()); + }; + return enqueue().then(() => Promise.all(ret)); +} \ No newline at end of file