445 lines
16 KiB
JavaScript
445 lines
16 KiB
JavaScript
/**
|
|
* Plugin JQuery para trabalhar com anexos nos formulários dentro do processo
|
|
*
|
|
* @author Bruno Gasparetto
|
|
* @see https://github.com/brunogasparetto/fluig-form-attachment
|
|
*/
|
|
|
|
|
|
/**
|
|
* Configurações
|
|
*
|
|
* @typedef AttachmentSettings
|
|
* @property {boolean} showActionButton Exibe o botão de upload/delete. True por padrão.
|
|
* @property {boolean} filename Nome que será salvo como descrição do Anexo.
|
|
* @property {boolean|string} prefixName Adiciona prefixo ao anexo. False por padrão, True para prefixo aleatório, String para prefixo fixo.
|
|
* @property {string} accept Tipos de arquivos aceitos. Segue a regra do accept do input tipo file.
|
|
*/
|
|
|
|
;(function ($) {
|
|
"use strict";
|
|
|
|
const pluginName = "fluigFormAttachment";
|
|
|
|
const deleteFileClassName = "BtnDeleteFile";
|
|
const uploadFileClassname = "BtnUploadFile";
|
|
const viewerFileClassname = "BtnViewerFile";
|
|
const compressedExtensions = [
|
|
'.7z', '.zip', '.rar', '.gz', '.tar', '.tbz2', '.tgz', '.bz2', '.lz', '.lz4','.txz',
|
|
'.xz', '.z', '.zst', '.zstd', '.war', '.ear', '.jar','.apk', '.arj', '.ace', '.cab',
|
|
];
|
|
|
|
const isString = item => typeof item === "string";
|
|
|
|
const normalizeAttachmentDescription = (value) => String(value || "").trim();
|
|
|
|
const attachmentMatchesDescription = (attachment, filename) => {
|
|
const target = normalizeAttachmentDescription(filename);
|
|
const description = normalizeAttachmentDescription(attachment && attachment.description);
|
|
return !!target && (description === target || description.indexOf(target + " - ") === 0);
|
|
};
|
|
|
|
/**
|
|
* Procura o índice do anexo de acordo com sua descrição
|
|
*
|
|
* @param {string} filename
|
|
* @returns {number} -1 se não encontrar
|
|
*/
|
|
const attachmentFindIndex = (filename) =>
|
|
parent.ECM.attachmentTable.getData().findIndex(attachment => attachmentMatchesDescription(attachment, filename));
|
|
|
|
const attachmentFindIndexes = (filename) =>
|
|
parent.ECM.attachmentTable
|
|
.getData()
|
|
.map((attachment, index) => attachmentMatchesDescription(attachment, filename) ? index : -1)
|
|
.filter(index => index !== -1);
|
|
|
|
/**
|
|
* Configuração padrão
|
|
*
|
|
* @type {AttachmentSettings}
|
|
*/
|
|
const defaults = {
|
|
showActionButton: true,
|
|
filename: "Anexo",
|
|
prefixName: false,
|
|
accept: "*",
|
|
};
|
|
|
|
class Plugin {
|
|
/**
|
|
* @type {AttachmentSettings}
|
|
*/
|
|
#settings;
|
|
|
|
/**
|
|
* Elemento do arquivo. Pode ser um input ou span (no modo leitura).
|
|
*
|
|
* @type {JQuery<HTMLElement>}
|
|
*/
|
|
#input;
|
|
|
|
/**
|
|
* @type {JQuery<HTMLElement>}
|
|
*/
|
|
#container;
|
|
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
#attachmentFilename;
|
|
|
|
/**
|
|
* @param {HTMLElement} element
|
|
* @param {AttachmentSettings} options
|
|
*/
|
|
constructor(element, options) {
|
|
|
|
// Garantir um ID para o Input
|
|
if (!element.id && element.nodeName.toLowerCase() === "input") {
|
|
element.id = FLUIGC.utilities.randomUUID();
|
|
}
|
|
|
|
this.#settings = $.extend({}, defaults, options);
|
|
this.#input = $(element);
|
|
this.#attachmentFilename = this.#input.val() || this.#input.text().trim();
|
|
|
|
this.#input
|
|
.prop("readonly", true)
|
|
.on("change", () => {
|
|
this.#attachmentFilename = this.#input.val();
|
|
this.#changeButtonsState();
|
|
})
|
|
.wrap(`<div class="${pluginName}Component"></div>`)
|
|
.after(`<div class="${pluginName}Component_buttons">${this.#getButtonsTemplate()}</div>`);
|
|
|
|
this.#container = this.#input.closest(`.${pluginName}Component`);
|
|
|
|
this.#container
|
|
.on("click", `.${pluginName}${deleteFileClassName}`, () => this.#confirmDeleteAttachment())
|
|
.on("click", `.${pluginName}${uploadFileClassname}`, () => this.#uploadAttachment())
|
|
.on("click", `.${pluginName}${viewerFileClassname}`, () => this.#viewAttachment())
|
|
;
|
|
}
|
|
|
|
/**
|
|
* Indica que o campo está válido
|
|
*
|
|
* Caso o campo possua algum valor é obrigatório que o anexo
|
|
* esteja na tabela de anexos.
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
isValid() {
|
|
return this.#attachmentFilename.length
|
|
? this.hasAttachment()
|
|
: true
|
|
;
|
|
}
|
|
|
|
/**
|
|
* Indica se o anexo está na tabela de anexos
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
hasAttachment() {
|
|
const filename = this.#attachmentFilename || this.#input.val() || this.#input.text().trim();
|
|
|
|
return filename.length > 0 && attachmentFindIndex(filename) !== -1;
|
|
}
|
|
|
|
/**
|
|
* Remove o anexo
|
|
*
|
|
* Método útil para excluir anexos em tabela Pai x Filho.
|
|
*/
|
|
deleteAttachment() {
|
|
const attachmentIndex = parent.ECM.attachmentTable.getData().findIndex(
|
|
attachment => attachment.description === this.#attachmentFilename
|
|
);
|
|
|
|
setTimeout(() => this.#input.val("").trigger("change"), 500);
|
|
|
|
if (attachmentIndex === -1) {
|
|
return;
|
|
}
|
|
|
|
parent.WKFViewAttachment.removeAttach([attachmentIndex]);
|
|
}
|
|
|
|
showActionButton() {
|
|
this.#settings.showActionButton = true;
|
|
this.#input.trigger("change");
|
|
}
|
|
|
|
hideActionButton() {
|
|
this.#settings.showActionButton = false;
|
|
this.#input.trigger("change");
|
|
}
|
|
|
|
filename(fileName, prefixName) {
|
|
if (fileName === undefined) {
|
|
return this.#input.data("filename") || this.#settings.filename;
|
|
}
|
|
|
|
this.#settings.filename = fileName;
|
|
this.#input.data("filename", fileName);
|
|
|
|
if (prefixName !== undefined) {
|
|
this.prefixName(prefixName);
|
|
}
|
|
}
|
|
|
|
prefixName(prefixName) {
|
|
if (prefixName === undefined) {
|
|
return this.#settings.prefixName;
|
|
}
|
|
|
|
this.#settings.prefixName = prefixName;
|
|
}
|
|
|
|
#getButtonsTemplate() {
|
|
const hasFileSelected = this.#attachmentFilename.length !== 0;
|
|
const canShowActionButton = this.#canDisplayActionButton();
|
|
|
|
return `<button type="button" class="${pluginName}BtnAction ${pluginName}${deleteFileClassName} btn btn-danger btn-sm ${(canShowActionButton && hasFileSelected) ? '' : 'hide'}" title="Remover Anexo"><i class="flaticon flaticon-trash icon-sm"></i></button>`
|
|
+ `<button type="button" class="${pluginName}BtnAction ${pluginName}${uploadFileClassname} btn btn-success btn-sm ${(canShowActionButton && !hasFileSelected) ? '' : 'hide'}" title="Enviar Anexo"><i class="flaticon flaticon-upload icon-sm"></i></button>`
|
|
+ `<button type="button" class="${pluginName}${viewerFileClassname} btn btn-info btn-sm ${hasFileSelected ? '' : 'hide'}" title="Visualizar Anexo"><i class="flaticon flaticon-view icon-sm"></i></button>`
|
|
;
|
|
}
|
|
|
|
#canDisplayActionButton() {
|
|
const element = this.#input.get(0);
|
|
const workflowView = (parent.ECM && parent.ECM.workflowView) ? parent.ECM.workflowView : {};
|
|
const userPermissions = String(workflowView.userPermissions || "");
|
|
const hasEditPermission = userPermissions.indexOf("P") >= 0;
|
|
const isTokenView = location.href.includes('token');
|
|
const hasMobileCameraBridge = (
|
|
(window.JSInterface && typeof window.JSInterface.showCamera === "function")
|
|
|| (parent && parent.JSInterface && typeof parent.JSInterface.showCamera === "function")
|
|
);
|
|
const isMobileUA = /android|iphone|ipad|ipod|mobile/i.test(navigator.userAgent || "");
|
|
const allowByContext = !isTokenView || hasMobileCameraBridge || isMobileUA;
|
|
const allowByPermission = hasEditPermission || hasMobileCameraBridge || isMobileUA;
|
|
|
|
return this.#settings.showActionButton
|
|
&& allowByPermission
|
|
&& allowByContext
|
|
&& element.nodeName.toLowerCase() === "input"
|
|
&& !element.disabled
|
|
;
|
|
}
|
|
|
|
#changeButtonsState() {
|
|
const hasFileSelected = this.#attachmentFilename.length !== 0;
|
|
|
|
if (this.#canDisplayActionButton()) {
|
|
if (hasFileSelected) {
|
|
this.#container.find(`.${pluginName}${uploadFileClassname}`).addClass("hide");
|
|
this.#container.find(`.${pluginName}${deleteFileClassName}`).removeClass("hide");
|
|
} else {
|
|
this.#container.find(`.${pluginName}${deleteFileClassName}`).addClass("hide");
|
|
this.#container.find(`.${pluginName}${uploadFileClassname}`).removeClass("hide");
|
|
}
|
|
} else {
|
|
this.#container.find(`.${pluginName}BtnAction`).addClass("hide");
|
|
}
|
|
|
|
if (hasFileSelected) {
|
|
this.#container.find(`.${pluginName}${viewerFileClassname}`).removeClass("hide");
|
|
} else {
|
|
this.#container.find(`.${pluginName}${viewerFileClassname}`).addClass("hide");
|
|
}
|
|
}
|
|
|
|
#confirmDeleteAttachment() {
|
|
if (!this.#canDisplayActionButton()) {
|
|
return;
|
|
}
|
|
|
|
FLUIGC.message.confirm({
|
|
message: `Deseja remover o anexo <b>${this.#attachmentFilename}</b>?`,
|
|
title: 'Confirmação',
|
|
labelYes: 'Sim, quero remover',
|
|
labelNo: 'Não, quero cancelar',
|
|
}, result => {
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
this.deleteAttachment();
|
|
});
|
|
}
|
|
|
|
#uploadAttachment() {
|
|
if (!this.#canDisplayActionButton()) {
|
|
return;
|
|
}
|
|
|
|
let filename = this.#input.data("filename") || this.#settings.filename;
|
|
|
|
if (this.#settings.prefixName === true) {
|
|
filename = FLUIGC.utilities.randomUUID().substring(0, 9) + filename;
|
|
} else if (this.#settings.prefixName !== false && isString(this.#settings.prefixName)) {
|
|
filename = `${this.#settings.prefixName}-${filename}`;
|
|
}
|
|
|
|
// Evita bloqueio de duplicidade: substitui anexo existente com mesma descrição.
|
|
const duplicatedIndexes = attachmentFindIndexes(filename).sort((a, b) => b - a);
|
|
if (duplicatedIndexes.length) {
|
|
parent.WKFViewAttachment.removeAttach(duplicatedIndexes);
|
|
}
|
|
|
|
parent.$("#ecm-navigation-inputFile-clone")
|
|
.attr({
|
|
"data-on-camera": "true",
|
|
"data-file-name-camera": filename,
|
|
"data-inputid": this.#input.attr("id"),
|
|
"data-filename": filename,
|
|
"multiple": false,
|
|
"accept": this.#input.data("accept") || this.#settings.accept,
|
|
})
|
|
.trigger("click")
|
|
;
|
|
}
|
|
|
|
#viewAttachment() {
|
|
const attachmentIndex = parent.ECM.attachmentTable.getData().findIndex(
|
|
attachment => attachment.description === this.#attachmentFilename
|
|
);
|
|
|
|
if (attachmentIndex === -1) {
|
|
FLUIGC.toast({
|
|
title: "Atenção",
|
|
message: "Anexo não encontrado",
|
|
type: "warning"
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const attachment = parent.ECM.attachmentTable.getRow(attachmentIndex);
|
|
const physicalFileName = String(
|
|
attachment.physicalFileName || attachment.fileName || attachment.name || ""
|
|
).toLowerCase();
|
|
const isCompressedFile = compressedExtensions.some(extension => physicalFileName.endsWith(extension));
|
|
|
|
if (attachment.documentId && !isCompressedFile) {
|
|
parent.WKFViewAttachment.openAttachmentView(parent.WCMAPI.userCode, attachment.documentId, attachment.version);
|
|
} else {
|
|
parent.WKFViewAttachment.downloadAttach([attachmentIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Instancia o Plugin ou executa algum método do plugin
|
|
*
|
|
* @param {AttachmentSettings|string} options
|
|
* @returns {undefined|boolean|void}
|
|
*/
|
|
$.fn[pluginName] = function (options) {
|
|
if (!parent.WKFViewAttachment || !parent.ECM || !parent.ECM.attachmentTable) {
|
|
console.error(`Plugin ${pluginName} executado fora de um processo.`)
|
|
return this;
|
|
}
|
|
|
|
// Executa o Método
|
|
if (isString(options)) {
|
|
const methodName = options;
|
|
const methodArgs = Array.prototype.slice.call(arguments, 1);
|
|
|
|
let returnedValue = undefined;
|
|
|
|
this.each(function () {
|
|
let pluginData = $.data(this, pluginName);
|
|
|
|
if (!pluginData) {
|
|
pluginData = new Plugin(this, {});
|
|
$.data(this, pluginName, pluginData);
|
|
}
|
|
|
|
if (!pluginData[methodName]) {
|
|
return;
|
|
}
|
|
|
|
returnedValue = pluginData[methodName](...methodArgs);
|
|
|
|
if (returnedValue !== undefined) {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return returnedValue !== undefined
|
|
? returnedValue
|
|
: this
|
|
;
|
|
}
|
|
|
|
return this.each(function () {
|
|
if (!$.data(this, pluginName)) {
|
|
$.data(this, pluginName, new Plugin(this, options));
|
|
}
|
|
});
|
|
};
|
|
|
|
if (!parent.WKFViewAttachment || !parent.ECM || !parent.ECM.attachmentTable) {
|
|
return;
|
|
}
|
|
|
|
const loading = FLUIGC.loading(window, {
|
|
title: "Aguarde",
|
|
textMessage: "Enviando arquivo",
|
|
})
|
|
|
|
$(() => {
|
|
// Oculta aba anexos
|
|
$("#tab-attachments", parent.document).hide();
|
|
|
|
parent.$("#ecm_navigation_fileupload")
|
|
.on(`fileuploadadd.${pluginName}`, function(e, data) {
|
|
// Impede abrir o Loading caso tenha erro no arquivo
|
|
|
|
const file = data.files[0];
|
|
|
|
if (parent.ECM.maxUploadSize > 0 && file.size >= (parent.ECM.maxUploadSize * 1024 * 1024)) {
|
|
return;
|
|
}
|
|
|
|
if (parent.ECM.newAttachmentsDocs.length
|
|
&& parent.ECM.newAttachmentsDocs.findIndex(attachment => attachment.name === file.name) !== -1
|
|
) {
|
|
return;
|
|
}
|
|
|
|
loading.show();
|
|
})
|
|
.on(`fileuploadfail.${pluginName}`, () => loading.hide())
|
|
.on(`fileuploaddone.${pluginName}`, function() {
|
|
// Atualiza o campo do arquivo caso o upload tenha ocorrido
|
|
|
|
loading.hide();
|
|
|
|
const btnUpload = parent.document.getElementById("ecm-navigation-inputFile-clone");
|
|
const filename = btnUpload.getAttribute("data-filename");
|
|
|
|
if (attachmentFindIndex(filename) === -1) {
|
|
return;
|
|
}
|
|
|
|
$(`#${btnUpload.getAttribute("data-inputid")}`).val(filename).trigger("change");
|
|
});
|
|
|
|
parent.$(document).on(`fileuploadstop.${pluginName}`, () => loading.hide());
|
|
});
|
|
|
|
|
|
$("head").append(`<style>
|
|
.${pluginName}Component { display: flex; align-items: center; flex-wrap: nowrap; }
|
|
.${pluginName}Component input { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; }
|
|
.${pluginName}Component_buttons { display: flex; align-items: center; justify-content: flex-end; }
|
|
.${pluginName}Component_buttons .btn { outline: none !important; outline-offset: unset !important; border-radius: 0 !important; height: 32px; }
|
|
</style>`);
|
|
|
|
}(jQuery));
|