(function (window, document) {
"use strict";
function qs(root, selector) {
return root ? root.querySelector(selector) : null;
}
function qsa(root, selector) {
return root ? Array.prototype.slice.call(root.querySelectorAll(selector)) : [];
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function uid() {
return "sig_" + Date.now().toString(36) + Math.random().toString(36).slice(2);
}
function parseJson(value, fallback) {
try {
return JSON.parse(value || "");
} catch (ignore) {
return fallback;
}
}
function parseState(value) {
var state = parseJson(value, {});
state.CurrentPage = state.CurrentPage || 1;
state.Scale = state.Scale || 1;
state.Placements = state.Placements || [];
state.SignMode = !!state.SignMode;
state.SelectedPlacementId = state.SelectedPlacementId || "";
state.ActiveTab = state.ActiveTab || "draw";
state.IsFullscreen = !!state.IsFullscreen;
var hasPropertiesPanelState =
typeof state.PropertiesPanelOpen ===
"boolean";
state.PropertiesPanelOpen =
hasPropertiesPanelState
? state.PropertiesPanelOpen
: !!state.SelectedPlacementId;
return state;
}
function executePostback(code) {
if (code) {
window.eval(code);
}
}
function formatSize(size) {
var value = Number(size || 0);
if (value < 1024) { return value + " B"; }
if (value < 1024 * 1024) { return (value / 1024).toFixed(1) + " KB"; }
return (value / (1024 * 1024)).toFixed(1) + " MB";
}
function postFile(url, kind, file, callback) {
var form = new FormData();
form.append("kind", kind);
form.append("file", file, file.name);
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.onreadystatechange = function () {
if (request.readyState !== 4) { return; }
var response = parseJson(request.responseText, null);
if (request.status >= 200 && request.status < 300 && response && response.success) {
callback(null, response);
} else {
callback(new Error(response && response.message ? response.message : "No fue posible cargar el archivo."));
}
};
request.send(form);
}
function postCommand(url, kind, id, callback) {
var form = new FormData();
form.append("kind", kind);
form.append("id", id || "");
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.onreadystatechange = function () {
if (request.readyState !== 4) { return; }
callback(request.status >= 200 && request.status < 300);
};
request.send(form);
}
function PdfControl(root) {
this.root = root;
this.uploadUrl = root.getAttribute("data-upload-url");
this.actionField = document.getElementById(root.getAttribute("data-action-id"));
this.stateField = document.getElementById(root.getAttribute("data-state-id"));
this.uploads = [];
this.viewer = null;
this.bindUploadScreen();
this.bindViewer();
}
PdfControl.prototype.setAction = function (action) {
if (this.actionField) {
this.actionField.value = JSON.stringify(action || {});
}
};
PdfControl.prototype.bindUploadScreen = function () {
var self = this;
var zone = qs(this.root, ".sig-pdf-upload-dropzone");
var input = qs(this.root, ".sig-pdf-upload-input");
if (!zone || !input) { return; }
function addFiles(files) {
var max = Number(self.root.getAttribute("data-max-pdf-files") || 20);
var allowMultiple = self.root.getAttribute("data-allow-multiple") === "true";
var list = Array.prototype.slice.call(files || []);
if (!allowMultiple && list.length > 1) {
list = [list[0]];
}
list.forEach(function (file) {
if (self.uploads.length >= max) { return; }
if (!/\.pdf$/i.test(file.name) && file.type !== "application/pdf") {
window.alert("Solo se permiten archivos PDF.");
return;
}
postFile(self.uploadUrl, "pdf", file, function (error, response) {
if (error) {
window.alert(error.message);
return;
}
self.uploads.push({
id: response.id,
name: response.name || file.name,
size: response.size || file.size
});
self.renderUploads();
});
});
}
var usingDropzone = false;
if (window.Dropzone) {
usingDropzone = true;
window.Dropzone.autoDiscover = false;
input.style.display = "none";
var pdfDropzone = new window.Dropzone(zone, {
url: self.uploadUrl,
paramName: "file",
params: { kind: "pdf" },
acceptedFiles: "application/pdf,.pdf",
maxFilesize: 50,
maxFiles: self.root.getAttribute("data-allow-multiple") === "true"
? Number(self.root.getAttribute("data-max-pdf-files") || 20)
: 1,
uploadMultiple: false,
parallelUploads: 1,
previewsContainer: false,
createImageThumbnails: false,
clickable: true,
accept: function (file, done) {
var max = Number(self.root.getAttribute("data-max-pdf-files") || 20);
if (self.uploads.length >= max) {
done("Se alcanzó el límite de archivos.");
} else {
done();
}
}
});
pdfDropzone.on("success", function (file, response) {
var data = typeof response === "string" ? parseJson(response, null) : response;
if (!data || !data.success) {
window.alert(data && data.message ? data.message : "No fue posible cargar el PDF.");
return;
}
self.uploads.push({
id: data.id,
name: data.name || file.name,
size: data.size || file.size
});
self.renderUploads();
pdfDropzone.removeFile(file);
});
pdfDropzone.on("error", function (file, message) {
var data = typeof message === "string" ? parseJson(message, null) : message;
window.alert(data && data.message ? data.message : (typeof message === "string" ? message : "No fue posible cargar el PDF."));
pdfDropzone.removeFile(file);
});
}
if (!usingDropzone) {
zone.addEventListener("click", function (event) {
if (event.target !== input) {
input.click();
}
});
input.addEventListener("change", function () {
addFiles(this.files);
this.value = "";
});
zone.addEventListener("dragover", function (event) {
event.preventDefault();
zone.classList.add("is-dragover");
});
zone.addEventListener("dragleave", function () {
zone.classList.remove("is-dragover");
});
zone.addEventListener("drop", function (event) {
event.preventDefault();
zone.classList.remove("is-dragover");
addFiles(event.dataTransfer.files);
});
}
var clearButton = qs(this.root, ".sig-pdf-upload-clear");
var combineButton = qs(this.root, ".sig-pdf-upload-combine");
if (clearButton) {
clearButton.addEventListener("click", function () {
self.uploads.forEach(function (item) {
postCommand(self.uploadUrl, "remove-pdf", item.id, function () {});
});
self.uploads = [];
self.renderUploads();
});
}
if (combineButton) {
combineButton.addEventListener("click", function () {
if (!self.uploads.length) { return; }
self.setAction({
Action: "combine-pdfs",
UploadIds: self.uploads.map(function (item) { return item.id; })
});
executePostback(self.root.getAttribute("data-postback-combine"));
});
}
self.renderUploads();
};
PdfControl.prototype.renderUploads = function () {
var self = this;
var list = qs(this.root, ".sig-pdf-upload-list");
var combineButton = qs(this.root, ".sig-pdf-upload-combine");
if (!list) { return; }
list.innerHTML = "";
this.uploads.forEach(function (item, index) {
var row = document.createElement("div");
row.className = "sig-pdf-upload-item";
row.innerHTML =
"" + (index + 1) + "" +
"" +
"" +
"" +
"" +
"" +
"";
qs(row, "strong").textContent = item.name;
qs(row, "small").textContent = formatSize(item.size);
qs(row, ".sig-upload-up").disabled = index === 0;
qs(row, ".sig-upload-down").disabled = index === self.uploads.length - 1;
qs(row, ".sig-upload-up").addEventListener("click", function () {
if (index <= 0) { return; }
var temp = self.uploads[index - 1];
self.uploads[index - 1] = self.uploads[index];
self.uploads[index] = temp;
self.renderUploads();
});
qs(row, ".sig-upload-down").addEventListener("click", function () {
if (index >= self.uploads.length - 1) { return; }
var temp = self.uploads[index + 1];
self.uploads[index + 1] = self.uploads[index];
self.uploads[index] = temp;
self.renderUploads();
});
qs(row, ".sig-upload-remove").addEventListener("click", function () {
postCommand(self.uploadUrl, "remove-pdf", item.id, function () {});
self.uploads.splice(index, 1);
self.renderUploads();
});
list.appendChild(row);
});
if (combineButton) {
combineButton.disabled = this.uploads.length === 0;
}
};
PdfControl.prototype.bindViewer = function () {
var viewerRoot = qs(this.root, ".sig-pdf-viewer");
if (!viewerRoot) { return; }
this.viewer = new Viewer(this, viewerRoot);
};
function Viewer(control, root) {
this.control = control;
this.root = root;
this.pdfUrl = root.getAttribute("data-pdf-url");
this.bestUrl = root.getAttribute("data-best-url");
this.downloadUrl = root.getAttribute("data-download-url");
this.workerUrl = root.getAttribute("data-worker-url");
this.state = parseState(control.stateField ? control.stateField.value : "");
this.currentPage = Number(this.state.CurrentPage || 1);
this.scale = Number(this.state.Scale || 1);
this.pdf = null;
this.renderTask = null;
this.pageWidth = 0;
this.pageHeight = 0;
this.selectedId = this.state.SelectedPlacementId || null;
this.signMode = !!this.state.SignMode;
this.propertiesPanelOpen = !!this.state.PropertiesPanelOpen;
this.currentSignature = null;
this.uploadedSignature = null;
this.strokes = [];
this.redoStrokes = [];
this.currentStroke = null;
this.drawColor = "#111111";
this.pendingDrawColor = this.drawColor;
this.bind();
this.load();
}
Viewer.prototype.bind = function () {
var self = this;
this.canvas = qs(this.root, ".sig-pdf-canvas");
this.layer = qs(this.root, ".sig-signature-layer");
this.shell = qs(this.root, ".sig-page-shell");
this.loading = qs(this.root, ".sig-loading");
this.leftPanel = qs(this.root, ".sig-left-panel");
this.rightPanel = qs(this.root, ".sig-right-panel");
this.startButton = qs(this.root, ".sig-start-signing");
this.finishButton = qs(this.root, ".sig-finish-signing");
this.propertiesToggle = qs(this.root, ".sig-toggle-properties");
this.modeLabel = qs(this.root, ".sig-mode-label");
this.pageInput = qs(this.root, ".sig-page-number");
this.pageCount = qs(this.root, ".sig-page-count");
this.zoomSelect = qs(this.root, ".sig-zoom-select");
this.drawCanvas = qs(this.root, ".sig-draw-canvas");
this.bindClick(".sig-prev-page", function () { self.goToPage(self.currentPage - 1); });
this.bindClick(".sig-next-page", function () { self.goToPage(self.currentPage + 1); });
this.bindClick(".sig-zoom-in", function () { self.setScale(self.scale + 0.25); });
this.bindClick(".sig-zoom-out", function () { self.setScale(self.scale - 0.25); });
this.bindClick(".sig-print", function () { window.open(self.bestUrl, "_blank", "noopener"); });
this.bindClick(".sig-download", function () { window.location.href = self.downloadUrl; });
this.bindClick(".sig-start-signing", function () { self.enterSignMode(); });
this.bindClick(".sig-finish-signing", function () { self.postback("data-postback-finalize"); });
this.bindClick(".sig-toggle-properties", function () { self.togglePropertiesPanel(); });
this.bindClick(".sig-refresh-document", function () { self.reloadDocument(); });
this.bindClick(".sig-change-documents", function () { self.postback("data-postback-reset-upload"); });
this.bindClick(".sig-expand-viewer", function () { self.setFullscreen(true); });
this.bindClick(".sig-collapse-viewer", function () { self.setFullscreen(false); });
this.bindClick(".sig-delete-signature", function () { self.deleteSelected(); });
this.bindClick(".sig-clear-all-signatures", function () { self.clearAll(); });
this.bindClick(".sig-download-signed", function () { self.postback("data-postback-download-signed"); });
this.bindClick(".sig-save-signed", function () { self.postback("data-postback-save-signed"); });
this.bindClick(".sig-save-exit", function () { self.postback("data-postback-save-exit"); });
if (this.pageInput) {
this.pageInput.addEventListener("change", function () {
self.goToPage(parseInt(this.value, 10));
});
}
if (this.zoomSelect) {
this.zoomSelect.addEventListener("change", function () {
self.setScale(parseFloat(this.value));
});
}
if (this.shell) {
this.shell.addEventListener("click", function () { self.select(null); });
}
this.bindTabs();
this.bindDrawPad();
this.bindDrawColorPicker();
this.bindSignatureUpload();
this.bindSavedSignatures();
this.bindPropertyEditors();
this.restoreUiState();
document.addEventListener("keydown", function (event) {
if (event.keyCode === 27 && self.root.classList.contains("is-fullscreen")) {
self.setFullscreen(false);
}
});
};
Viewer.prototype.bindClick = function (selector, handler) {
var element = qs(this.root, selector);
if (element) { element.addEventListener("click", handler); }
};
Viewer.prototype.postback = function (attributeName) {
this.syncState();
executePostback(this.control.root.getAttribute(attributeName));
};
Viewer.prototype.updatePropertiesToggle = function () {
if (!this.propertiesToggle) {
return;
}
var isOpen =
this.signMode &&
this.propertiesPanelOpen;
this.propertiesToggle.hidden =
!this.signMode;
this.propertiesToggle.classList.toggle(
"is-active",
isOpen
);
this.propertiesToggle.setAttribute(
"aria-expanded",
isOpen ? "true" : "false"
);
this.propertiesToggle.setAttribute(
"title",
isOpen
? "Ocultar propiedades de la firma"
: "Mostrar propiedades de la firma"
);
this.propertiesToggle.setAttribute(
"aria-label",
isOpen
? "Ocultar propiedades de la firma"
: "Mostrar propiedades de la firma"
);
};
Viewer.prototype.applyPropertiesPanelState = function () {
var visible =
this.signMode &&
this.propertiesPanelOpen;
if (this.rightPanel) {
this.rightPanel.classList.toggle(
"is-open",
visible
);
}
this.root.classList.toggle(
"properties-open",
visible
);
this.updatePropertiesToggle();
};
Viewer.prototype.setPropertiesPanel = function (
open,
sync
) {
this.propertiesPanelOpen = !!open;
this.state.PropertiesPanelOpen =
this.propertiesPanelOpen;
this.applyPropertiesPanelState();
if (sync !== false) {
this.syncState();
}
};
Viewer.prototype.showPropertiesForSignature = function () {
this.setPropertiesPanel(
true,
false
);
};
Viewer.prototype.togglePropertiesPanel = function () {
if (!this.signMode) {
return;
}
this.setPropertiesPanel(
!this.propertiesPanelOpen,
true
);
};
Viewer.prototype.restoreUiState = function () {
var self = this;
var activeTab = this.state.ActiveTab || "draw";
qsa(this.root, ".sig-signature-tab").forEach(function (button) {
button.classList.toggle(
"is-active",
button.getAttribute("data-tab") === activeTab
);
});
qsa(this.root, ".sig-signature-tab-panel").forEach(function (panel) {
panel.classList.toggle(
"is-active",
panel.classList.contains("sig-tab-" + activeTab)
);
});
if (this.signMode) {
this.root.classList.add("is-signing");
if (this.leftPanel) {
this.leftPanel.classList.add("is-open");
}
if (this.startButton) {
this.startButton.hidden = true;
}
if (this.finishButton) {
this.finishButton.hidden = false;
}
if (this.modeLabel) {
this.modeLabel.textContent =
"Modo de firma activo";
}
}
if (this.state.IsFullscreen) {
this.root.classList.add("is-fullscreen");
document.body.classList.add(
"sig-viewer-fullscreen-open"
);
var expand =
qs(this.root, ".sig-expand-viewer");
var collapse =
qs(this.root, ".sig-collapse-viewer");
if (expand) {
expand.hidden = true;
}
if (collapse) {
collapse.hidden = false;
}
}
if (
!this.selectedId ||
!this.state.Placements.some(
function (placement) {
return placement.Id ===
self.selectedId;
}
)
) {
this.selectedId = null;
this.state.SelectedPlacementId = "";
}
/*
* Respeta la decisión manual del usuario.
* Un postback no vuelve a abrir el panel
* si el usuario lo había cerrado.
*/
this.applyPropertiesPanelState();
};
Viewer.prototype.finishSigningClient = function () {
this.signMode = false;
this.selectedId = null;
this.state.SignMode = false;
this.state.SelectedPlacementId = "";
this.root.classList.remove("is-signing");
this.root.classList.remove("properties-open");
if (this.leftPanel) {
this.leftPanel.classList.remove("is-open");
}
if (this.rightPanel) {
this.rightPanel.classList.remove("is-open");
}
if (this.startButton) {
this.startButton.hidden = false;
}
if (this.finishButton) {
this.finishButton.hidden = true;
}
if (this.modeLabel) {
this.modeLabel.textContent = "Modo lectura";
}
this.updatePropertiesToggle();
this.renderPlacements();
this.syncState();
};
Viewer.prototype.reloadDocument = function () {
var cleanUrl = this.pdfUrl
.replace(/([?&])_sigRefresh=\d+/g, "$1")
.replace(/[?&]$/, "");
var separator = cleanUrl.indexOf("?") >= 0 ? "&" : "?";
this.pdfUrl = cleanUrl + separator + "_sigRefresh=" + new Date().getTime();
if (this.pdf && typeof this.pdf.destroy === "function") {
try { this.pdf.destroy(); } catch (ignore) {}
}
this.pdf = null;
this.load();
};
Viewer.prototype.addStoredSignatureClient = function (item) {
if (!item || !item.Id) { return; }
var list = qs(this.root, ".sig-saved-signature-list");
if (!list) { return; }
var existing = qs(
list,
'.sig-saved-signature-item[data-asset-id="' + item.Id + '"]'
);
if (existing) { return; }
var button = document.createElement("button");
button.type = "button";
button.className = "sig-saved-signature-item";
button.setAttribute("data-asset-id", item.Id);
button.setAttribute("data-image-url", item.ImageUrl || "");
button.setAttribute("data-name", item.Name || "Firma");
button.innerHTML =
'' +
'';
qs(button, ".sig-saved-signature-name").textContent = item.Name || "Firma";
var self = this;
button.addEventListener("click", function () {
var signature = {
assetId: button.getAttribute("data-asset-id"),
name: button.getAttribute("data-name"),
imageUrl: button.getAttribute("data-image-url"),
dataUrl: ""
};
self.currentSignature = signature;
self.previewSavedSignature(signature);
self.placeCurrentSignature();
});
list.appendChild(button);
};
Viewer.prototype.bindTabs = function () {
var self = this;
qsa(this.root, ".sig-signature-tab").forEach(function (button) {
button.addEventListener("click", function () {
var tab = button.getAttribute("data-tab");
self.state.ActiveTab = tab || "draw";
self.syncState();
qsa(self.root, ".sig-signature-tab").forEach(function (item) {
item.classList.toggle("is-active", item === button);
});
qsa(self.root, ".sig-signature-tab-panel").forEach(function (panel) {
panel.classList.toggle("is-active", panel.classList.contains("sig-tab-" + tab));
});
});
});
};
Viewer.prototype.bindDrawPad = function () {
var self = this;
var canvas = this.drawCanvas;
if (!canvas) { return; }
var context = canvas.getContext("2d");
var drawing = false;
function position(event) {
var rect = canvas.getBoundingClientRect();
var source = event.touches && event.touches.length ? event.touches[0] : event;
return {
x: (source.clientX - rect.left) * canvas.width / rect.width,
y: (source.clientY - rect.top) * canvas.height / rect.height
};
}
function begin(event) {
event.preventDefault();
drawing = true;
self.redoStrokes = [];
self.currentStroke = {
color: self.drawColor,
width: Number(qs(self.root, ".sig-draw-width").value || 3),
points: [position(event)]
};
}
function move(event) {
if (!drawing || !self.currentStroke) { return; }
event.preventDefault();
self.currentStroke.points.push(position(event));
self.redrawPad();
}
function end(event) {
if (!drawing) { return; }
if (event) { event.preventDefault(); }
drawing = false;
if (self.currentStroke && self.currentStroke.points.length > 1) {
self.strokes.push(self.currentStroke);
}
self.currentStroke = null;
self.redrawPad();
}
canvas.addEventListener("mousedown", begin);
canvas.addEventListener("mousemove", move);
document.addEventListener("mouseup", end);
canvas.addEventListener("touchstart", begin, { passive: false });
canvas.addEventListener("touchmove", move, { passive: false });
canvas.addEventListener("touchend", end, { passive: false });
var widthInput = qs(this.root, ".sig-draw-width");
var widthOutput = qs(this.root, ".sig-draw-width-output");
if (widthInput && widthOutput) {
widthInput.addEventListener("input", function () { widthOutput.textContent = this.value + "px"; });
}
this.bindClick(".sig-draw-undo", function () {
if (self.strokes.length) {
self.redoStrokes.push(self.strokes.pop());
self.redrawPad();
}
});
this.bindClick(".sig-draw-redo", function () {
if (self.redoStrokes.length) {
self.strokes.push(self.redoStrokes.pop());
self.redrawPad();
}
});
this.bindClick(".sig-clear-signature-pad", function () {
self.strokes = [];
self.redoStrokes = [];
self.currentStroke = null;
self.currentSignature = null;
self.redrawPad();
});
this.bindClick(".sig-use-signature-pad", function () {
var dataUrl = self.getDrawnSignatureDataUrl();
if (!dataUrl) { window.alert("Dibuja una firma antes de continuar."); return; }
self.currentSignature = { name: "Firma", dataUrl: dataUrl, assetId: "", imageUrl: dataUrl };
self.placeCurrentSignature();
});
this.bindClick(".sig-save-current-signature", function () {
var dataUrl = self.getDrawnSignatureDataUrl();
if (!dataUrl) { window.alert("Dibuja una firma antes de guardarla."); return; }
var nameInput = qs(self.root, ".sig-signature-name");
self.control.setAction({
Action: "save-signature",
Name: nameInput ? nameInput.value : "Firma",
DataUrl: dataUrl,
AssetId: ""
});
self.syncState();
executePostback(self.control.root.getAttribute("data-postback-save-signature"));
});
};
Viewer.prototype.normalizeHexColor = function (value) {
var color = String(value || "").replace(/\s+/g, "").toUpperCase();
if (/^[0-9A-F]{6}$/.test(color)) {
color = "#" + color;
}
if (!/^#[0-9A-F]{6}$/.test(color)) {
return "";
}
return color;
};
Viewer.prototype.setPendingDrawColor = function (value) {
var color = this.normalizeHexColor(value);
var input = qs(this.root, ".sig-draw-color-hex");
var preview = qs(this.root, ".sig-draw-color-pending-preview");
var error = qs(this.root, ".sig-draw-color-error");
if (input && input.value !== String(value || "").toUpperCase()) {
input.value = String(value || "").toUpperCase();
}
if (!color) {
if (error) { error.hidden = false; }
return false;
}
this.pendingDrawColor = color;
if (input) { input.value = color; }
if (preview) { preview.style.backgroundColor = color; }
if (error) { error.hidden = true; }
qsa(this.root, ".sig-draw-color-swatch").forEach(function (button) {
button.classList.toggle(
"is-selected",
String(button.getAttribute("data-color") || "").toUpperCase() === color
);
});
return true;
};
Viewer.prototype.openDrawColorPicker = function () {
var popover = qs(this.root, ".sig-draw-color-popover");
var trigger = qs(this.root, ".sig-draw-color-trigger");
if (!popover || !trigger) { return; }
this.pendingDrawColor = this.drawColor;
this.setPendingDrawColor(this.drawColor);
popover.hidden = false;
trigger.setAttribute("aria-expanded", "true");
var input = qs(this.root, ".sig-draw-color-hex");
if (input) {
window.setTimeout(function () {
input.focus();
input.select();
}, 0);
}
};
Viewer.prototype.closeDrawColorPicker = function (restore) {
var popover = qs(this.root, ".sig-draw-color-popover");
var trigger = qs(this.root, ".sig-draw-color-trigger");
if (!popover || !trigger) { return; }
if (restore) {
this.pendingDrawColor = this.drawColor;
this.setPendingDrawColor(this.drawColor);
}
popover.hidden = true;
trigger.setAttribute("aria-expanded", "false");
};
Viewer.prototype.applyDrawColor = function () {
var input = qs(this.root, ".sig-draw-color-hex");
var value = input ? input.value : this.pendingDrawColor;
if (!this.setPendingDrawColor(value)) {
return;
}
this.drawColor = this.pendingDrawColor;
var preview = qs(this.root, ".sig-draw-color-preview");
var label = qs(this.root, ".sig-draw-color-value");
if (preview) { preview.style.backgroundColor = this.drawColor; }
if (label) { label.textContent = this.drawColor; }
this.closeDrawColorPicker(false);
};
Viewer.prototype.bindDrawColorPicker = function () {
var self = this;
var picker = qs(this.root, ".sig-draw-color-picker");
var trigger = qs(this.root, ".sig-draw-color-trigger");
var input = qs(this.root, ".sig-draw-color-hex");
if (!picker || !trigger || !input) { return; }
trigger.addEventListener("click", function (event) {
event.preventDefault();
event.stopPropagation();
var popover = qs(self.root, ".sig-draw-color-popover");
if (popover && !popover.hidden) {
self.closeDrawColorPicker(true);
} else {
self.openDrawColorPicker();
}
});
qsa(this.root, ".sig-draw-color-swatch").forEach(function (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
event.stopPropagation();
self.setPendingDrawColor(button.getAttribute("data-color"));
});
});
input.addEventListener("input", function () {
self.setPendingDrawColor(this.value);
});
input.addEventListener("keydown", function (event) {
if (event.keyCode === 13) {
event.preventDefault();
self.applyDrawColor();
} else if (event.keyCode === 27) {
event.preventDefault();
self.closeDrawColorPicker(true);
}
});
this.bindClick(".sig-draw-color-accept", function () {
self.applyDrawColor();
});
this.bindClick(".sig-draw-color-cancel", function () {
self.closeDrawColorPicker(true);
});
picker.addEventListener("click", function (event) {
event.stopPropagation();
});
this.root.addEventListener("click", function (event) {
if (!picker.contains(event.target)) {
self.closeDrawColorPicker(true);
}
});
};
Viewer.prototype.redrawPad = function () {
var canvas = this.drawCanvas;
if (!canvas) { return; }
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
var strokes = this.strokes.slice(0);
if (this.currentStroke) { strokes.push(this.currentStroke); }
strokes.forEach(function (stroke) {
if (!stroke.points || stroke.points.length < 2) { return; }
context.beginPath();
context.strokeStyle = stroke.color;
context.lineWidth = stroke.width;
context.lineCap = "round";
context.lineJoin = "round";
context.moveTo(stroke.points[0].x, stroke.points[0].y);
for (var index = 1; index < stroke.points.length; index += 1) {
context.lineTo(stroke.points[index].x, stroke.points[index].y);
}
context.stroke();
context.closePath();
});
};
Viewer.prototype.getDrawnSignatureDataUrl = function () {
if (!this.drawCanvas || !this.strokes.length) { return ""; }
return this.drawCanvas.toDataURL("image/png");
};
Viewer.prototype.bindSignatureUpload = function () {
var self = this;
var zone = qs(this.root, ".sig-signature-upload-dropzone");
var input = qs(this.root, ".sig-signature-upload-input");
if (!zone || !input) { return; }
function upload(file) {
if (!file) { return; }
if (!/\.png$/i.test(file.name) && file.type !== "image/png") {
window.alert("La firma debe ser un archivo PNG.");
return;
}
postFile(self.control.uploadUrl, "signature", file, function (error, response) {
if (error) { window.alert(error.message); return; }
self.uploadedSignature = {
assetId: response.id,
name: response.name || file.name,
imageUrl: response.url,
dataUrl: ""
};
self.renderUploadedSignature();
});
}
var usingDropzone = false;
if (window.Dropzone) {
usingDropzone = true;
window.Dropzone.autoDiscover = false;
input.style.display = "none";
var signatureDropzone = new window.Dropzone(zone, {
url: self.control.uploadUrl,
paramName: "file",
params: { kind: "signature" },
acceptedFiles: "image/png,.png",
maxFilesize: 5,
maxFiles: 1,
uploadMultiple: false,
previewsContainer: false,
createImageThumbnails: false,
clickable: true
});
signatureDropzone.on("success", function (file, response) {
var data = typeof response === "string" ? parseJson(response, null) : response;
if (!data || !data.success) {
window.alert(data && data.message ? data.message : "No fue posible cargar la firma.");
return;
}
self.uploadedSignature = {
assetId: data.id,
name: data.name || file.name,
imageUrl: data.url,
dataUrl: ""
};
self.renderUploadedSignature();
signatureDropzone.removeAllFiles(true);
});
signatureDropzone.on("error", function (file, message) {
var data = typeof message === "string" ? parseJson(message, null) : message;
window.alert(data && data.message ? data.message : (typeof message === "string" ? message : "No fue posible cargar la firma."));
signatureDropzone.removeAllFiles(true);
});
}
if (!usingDropzone) {
zone.addEventListener("click", function (event) {
if (event.target !== input) { input.click(); }
});
input.addEventListener("change", function () {
upload(this.files && this.files[0]);
this.value = "";
});
zone.addEventListener("dragover", function (event) { event.preventDefault(); zone.classList.add("is-dragover"); });
zone.addEventListener("dragleave", function () { zone.classList.remove("is-dragover"); });
zone.addEventListener("drop", function (event) {
event.preventDefault();
zone.classList.remove("is-dragover");
upload(event.dataTransfer.files && event.dataTransfer.files[0]);
});
}
this.bindClick(".sig-use-uploaded-signature", function () {
if (!self.uploadedSignature) { return; }
self.currentSignature = self.uploadedSignature;
self.placeCurrentSignature();
});
this.bindClick(".sig-save-uploaded-signature", function () {
if (!self.uploadedSignature) { return; }
var inputName = qs(self.root, ".sig-upload-signature-name");
self.control.setAction({
Action: "save-signature",
Name: inputName && inputName.value ? inputName.value : self.uploadedSignature.name,
DataUrl: "",
AssetId: self.uploadedSignature.assetId
});
self.syncState();
executePostback(self.control.root.getAttribute("data-postback-save-signature"));
});
};
Viewer.prototype.renderUploadedSignature = function () {
var preview = qs(this.root, ".sig-uploaded-signature-preview");
var useButton = qs(this.root, ".sig-use-uploaded-signature");
var saveButton = qs(this.root, ".sig-save-uploaded-signature");
if (!preview) { return; }
preview.innerHTML = "";
if (this.uploadedSignature) {
var image = document.createElement("img");
image.src = this.uploadedSignature.imageUrl;
image.alt = this.uploadedSignature.name;
preview.appendChild(image);
}
if (useButton) { useButton.disabled = !this.uploadedSignature; }
if (saveButton) { saveButton.disabled = !this.uploadedSignature; }
};
Viewer.prototype.bindSavedSignatures = function () {
var self = this;
qsa(this.root, ".sig-saved-signature-item").forEach(function (button) {
button.addEventListener("click", function () {
var signature = {
assetId: button.getAttribute("data-asset-id"),
name: button.getAttribute("data-name"),
imageUrl: button.getAttribute("data-image-url"),
dataUrl: ""
};
self.currentSignature = signature;
self.previewSavedSignature(signature);
self.placeCurrentSignature();
});
});
var filter = qs(this.root, ".sig-saved-signature-filter");
if (filter) {
filter.addEventListener("input", function () {
var value = this.value.toLowerCase();
qsa(self.root, ".sig-saved-signature-item").forEach(function (item) {
var name = (item.getAttribute("data-name") || "").toLowerCase();
item.style.display = name.indexOf(value) >= 0 ? "" : "none";
});
});
}
};
Viewer.prototype.previewSavedSignature = function (signature) {
var preview = qs(this.root, ".sig-saved-signature-preview");
if (!preview) { return; }
preview.innerHTML = "";
var image = document.createElement("img");
image.src = signature.imageUrl;
image.alt = signature.name;
preview.appendChild(image);
};
Viewer.prototype.bindPropertyEditors = function () {
var self = this;
["width", "height", "x", "y"].forEach(function (name) {
var input = qs(self.root, ".sig-prop-" + name);
if (input) {
input.addEventListener("input", function () { self.updateSelectedFromInputs(name); });
}
});
var rotation = qs(this.root, ".sig-prop-rotation");
var opacity = qs(this.root, ".sig-prop-opacity");
if (rotation) {
rotation.addEventListener("input", function () {
qs(self.root, ".sig-rotation-output").textContent = this.value + "°";
self.updateSelectedFromInputs("rotation");
});
}
if (opacity) {
opacity.addEventListener("input", function () {
qs(self.root, ".sig-opacity-output").textContent = this.value + "%";
self.updateSelectedFromInputs("opacity");
});
}
qsa(this.root, ".sig-pages-fieldset input[type=radio]").forEach(function (radio) {
radio.addEventListener("change", function () {
var specific = qs(self.root, ".sig-specific-pages");
if (specific) { specific.disabled = this.value !== "specific"; }
self.updateSelectedFromInputs("pages");
});
});
var pages = qs(this.root, ".sig-specific-pages");
if (pages) { pages.addEventListener("input", function () { self.updateSelectedFromInputs("pages"); }); }
};
Viewer.prototype.load = function () {
var self = this;
if (!window.pdfjsLib) {
this.showError("No se cargó PDF.js.");
return;
}
window.pdfjsLib.GlobalWorkerOptions.workerSrc = this.workerUrl;
window.pdfjsLib.getDocument(this.pdfUrl).promise.then(function (pdf) {
self.pdf = pdf;
self.pageCount.textContent = "/ " + pdf.numPages;
self.pageInput.max = pdf.numPages;
self.goToPage(clamp(self.currentPage, 1, pdf.numPages));
}).catch(function (error) {
self.showError("No fue posible abrir el PDF: " + error.message);
});
};
Viewer.prototype.goToPage = function (pageNumber) {
if (!this.pdf) { return; }
this.currentPage = clamp(Number(pageNumber) || 1, 1, this.pdf.numPages);
this.pageInput.value = this.currentPage;
this.state.CurrentPage = this.currentPage;
this.renderPage();
};
Viewer.prototype.setScale = function (value) {
this.scale = clamp(Number(value) || 1, 0.5, 3);
this.state.Scale = this.scale;
this.zoomSelect.value = String(this.scale);
this.renderPage();
};
Viewer.prototype.renderPage = function () {
var self = this;
this.loading.hidden = false;
this.pdf.getPage(this.currentPage).then(function (page) {
var viewport = page.getViewport({ scale: self.scale });
self.pageWidth = viewport.width / self.scale;
self.pageHeight = viewport.height / self.scale;
self.canvas.width = Math.floor(viewport.width);
self.canvas.height = Math.floor(viewport.height);
self.canvas.style.width = viewport.width + "px";
self.canvas.style.height = viewport.height + "px";
self.shell.style.width = viewport.width + "px";
self.shell.style.height = viewport.height + "px";
self.layer.style.width = viewport.width + "px";
self.layer.style.height = viewport.height + "px";
if (self.renderTask) {
try { self.renderTask.cancel(); } catch (ignore) {}
}
self.renderTask = page.render({ canvasContext: self.canvas.getContext("2d"), viewport: viewport });
return self.renderTask.promise;
}).then(function () {
self.loading.hidden = true;
self.renderPlacements();
if (self.selectedId) {
self.loadInputsFromSelected();
}
self.syncState();
}).catch(function (error) {
if (error && error.name === "RenderingCancelledException") { return; }
self.showError(error.message || "No fue posible mostrar la página.");
});
};
Viewer.prototype.enterSignMode = function () {
this.signMode = true;
this.root.classList.add("is-signing");
this.leftPanel.classList.add("is-open");
this.startButton.hidden = true;
this.finishButton.hidden = false;
this.modeLabel.textContent =
"Modo de firma activo";
this.state.SignMode = true;
this.applyPropertiesPanelState();
this.renderPlacements();
this.syncState();
};
Viewer.prototype.setFullscreen = function (expanded) {
this.root.classList.toggle("is-fullscreen", !!expanded);
var expand = qs(this.root, ".sig-expand-viewer");
var collapse = qs(this.root, ".sig-collapse-viewer");
if (expand) { expand.hidden = !!expanded; }
if (collapse) { collapse.hidden = !expanded; }
document.body.classList.toggle("sig-viewer-fullscreen-open", !!expanded);
this.state.IsFullscreen = !!expanded;
this.syncState();
if (this.pdf) {
window.setTimeout(this.renderPage.bind(this), 40);
}
};
Viewer.prototype.placeCurrentSignature = function () {
if (!this.currentSignature) { return; }
if (!this.signMode) { this.enterSignMode(); }
var width = 180;
var height = 70;
var x = Math.max(0, (this.pageWidth - width) / 2);
var y = Math.max(0, this.pageHeight - height - 45);
var placement = {
Id: uid(),
AssetId: this.currentSignature.assetId || "",
Name: this.currentSignature.name || "Firma",
PageNumber: this.currentPage,
X: x,
Y: y,
Width: width,
Height: height,
PageWidth: this.pageWidth,
PageHeight: this.pageHeight,
Rotation: 0,
Opacity: 1,
ApplyToAllPages: false,
SpecificPages: "",
ImageDataUrl: this.currentSignature.dataUrl || "",
ImageUrl: this.currentSignature.imageUrl || ""
};
this.state.Placements.push(placement);
this.select(placement.Id);
this.syncState();
};
Viewer.prototype.renderPlacements = function () {
var self = this;
this.layer.innerHTML = "";
this.state.Placements.filter(function (placement) {
return Number(placement.PageNumber) === self.currentPage;
}).forEach(function (placement) {
var element = document.createElement("div");
element.className = "sig-placement" + (placement.Id === self.selectedId ? " is-selected" : "");
element.style.left = Number(placement.X) * self.scale + "px";
element.style.top = Number(placement.Y) * self.scale + "px";
element.style.width = Number(placement.Width) * self.scale + "px";
element.style.height = Number(placement.Height) * self.scale + "px";
element.style.transform = "rotate(" + Number(placement.Rotation || 0) + "deg)";
element.style.opacity = Number(placement.Opacity || 1);
element.setAttribute("data-id", placement.Id);
var image = document.createElement("img");
image.draggable = false;
image.src = placement.ImageDataUrl || placement.ImageUrl || "";
element.appendChild(image);
var handle = document.createElement("span");
handle.className = "sig-resize-handle";
element.appendChild(handle);
element.addEventListener("mousedown", function (event) {
self.startPointer(event, placement, event.target === handle ? "resize" : "move");
});
element.addEventListener("click", function (event) {
event.stopPropagation();
self.select(placement.Id);
});
self.layer.appendChild(element);
});
this.renderPlacementList();
};
Viewer.prototype.renderPlacementList = function () {
var self = this;
var list = qs(this.root, ".sig-placement-list");
if (!list) { return; }
list.innerHTML = "";
this.state.Placements.forEach(function (placement, index) {
var item = document.createElement("div");
item.className = "sig-placement-list-item" + (placement.Id === self.selectedId ? " is-selected" : "");
item.innerHTML = "";
qs(item, "span").textContent = placement.Name || ("Firma " + (index + 1));
qs(item, "small").textContent = "Página " + placement.PageNumber;
qs(item, ".sig-placement-select").addEventListener("click", function () {
self.goToPage(placement.PageNumber);
self.select(placement.Id);
});
qs(item, ".sig-placement-remove").addEventListener("click", function () {
self.removePlacement(placement.Id);
});
list.appendChild(item);
});
if (!this.state.Placements.length) {
list.innerHTML = "No hay firmas colocadas.";
}
};
Viewer.prototype.startPointer = function (event, placement, mode) {
if (!this.signMode) { return; }
event.preventDefault();
event.stopPropagation();
this.select(placement.Id);
var self = this;
var start = {
x: event.clientX,
y: event.clientY,
px: Number(placement.X),
py: Number(placement.Y),
w: Number(placement.Width),
h: Number(placement.Height)
};
var keepRatio = qs(this.root, ".sig-keep-ratio").checked;
function move(currentEvent) {
var dx = (currentEvent.clientX - start.x) / self.scale;
var dy = (currentEvent.clientY - start.y) / self.scale;
if (mode === "move") {
placement.X = clamp(start.px + dx, 0, Math.max(0, self.pageWidth - placement.Width));
placement.Y = clamp(start.py + dy, 0, Math.max(0, self.pageHeight - placement.Height));
} else {
var ratio = start.w / Math.max(1, start.h);
placement.Width = clamp(start.w + dx, 20, Math.max(20, self.pageWidth - placement.X));
placement.Height = keepRatio ? placement.Width / ratio : clamp(start.h + dy, 20, Math.max(20, self.pageHeight - placement.Y));
}
self.renderPlacements();
self.loadInputsFromSelected();
}
function up() {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", up);
self.syncState();
}
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", up);
};
Viewer.prototype.select = function (id) {
this.selectedId = id;
this.state.SelectedPlacementId = id || "";
/*
* Seleccionar o colocar una firma abre el panel.
* Deseleccionar no lo cierra.
*/
if (id) {
this.showPropertiesForSignature();
}
this.renderPlacements();
this.loadInputsFromSelected();
this.syncState();
};
Viewer.prototype.selected = function () {
var id = this.selectedId;
return this.state.Placements.filter(function (placement) { return placement.Id === id; })[0] || null;
};
Viewer.prototype.loadInputsFromSelected = function () {
var placement = this.selected();
if (!placement) { return; }
qs(this.root, ".sig-prop-width").value = Math.round(Number(placement.Width));
qs(this.root, ".sig-prop-height").value = Math.round(Number(placement.Height));
qs(this.root, ".sig-prop-x").value = Math.round(Number(placement.X));
qs(this.root, ".sig-prop-y").value = Math.round(Number(placement.Y));
qs(this.root, ".sig-prop-rotation").value = Number(placement.Rotation || 0);
qs(this.root, ".sig-prop-opacity").value = Math.round(Number(placement.Opacity || 1) * 100);
qs(this.root, ".sig-rotation-output").textContent = Number(placement.Rotation || 0) + "°";
qs(this.root, ".sig-opacity-output").textContent = Math.round(Number(placement.Opacity || 1) * 100) + "%";
var mode = placement.ApplyToAllPages ? "all" : (placement.SpecificPages ? "specific" : "current");
var radio = qs(this.root, ".sig-pages-fieldset input[value=\"" + mode + "\"]");
if (radio) { radio.checked = true; }
var specific = qs(this.root, ".sig-specific-pages");
if (specific) {
specific.value = placement.SpecificPages || "";
specific.disabled = mode !== "specific";
}
};
Viewer.prototype.updateSelectedFromInputs = function (changed) {
var placement = this.selected();
if (!placement) { return; }
var keep = qs(this.root, ".sig-keep-ratio").checked;
var ratio = Number(placement.Width) / Math.max(1, Number(placement.Height));
var width = Number(qs(this.root, ".sig-prop-width").value) || 1;
var height = Number(qs(this.root, ".sig-prop-height").value) || 1;
if (changed === "width" && keep) {
height = width / ratio;
qs(this.root, ".sig-prop-height").value = Math.round(height);
}
if (changed === "height" && keep) {
width = height * ratio;
qs(this.root, ".sig-prop-width").value = Math.round(width);
}
placement.Width = clamp(width, 1, this.pageWidth);
placement.Height = clamp(height, 1, this.pageHeight);
placement.X = clamp(Number(qs(this.root, ".sig-prop-x").value) || 0, 0, Math.max(0, this.pageWidth - placement.Width));
placement.Y = clamp(Number(qs(this.root, ".sig-prop-y").value) || 0, 0, Math.max(0, this.pageHeight - placement.Height));
placement.Rotation = Number(qs(this.root, ".sig-prop-rotation").value) || 0;
placement.Opacity = (Number(qs(this.root, ".sig-prop-opacity").value) || 100) / 100;
var checked = qs(this.root, ".sig-pages-fieldset input[type=radio]:checked");
var mode = checked ? checked.value : "current";
placement.ApplyToAllPages = mode === "all";
placement.SpecificPages = mode === "specific" ? qs(this.root, ".sig-specific-pages").value : "";
this.renderPlacements();
this.syncState();
};
Viewer.prototype.deleteSelected = function () {
if (!this.selectedId) { return; }
this.removePlacement(this.selectedId);
};
Viewer.prototype.removePlacement = function (id) {
this.state.Placements =
this.state.Placements.filter(
function (placement) {
return placement.Id !== id;
}
);
if (this.selectedId === id) {
this.selectedId = null;
this.state.SelectedPlacementId = "";
}
/*
* El panel permanece en el estado elegido
* por el usuario.
*/
this.renderPlacements();
this.syncState();
};
Viewer.prototype.clearAll = function () {
if (!this.state.Placements.length) {
return;
}
if (
!window.confirm(
"¿Deseas eliminar todas las firmas colocadas?"
)
) {
return;
}
this.state.Placements = [];
this.selectedId = null;
this.state.SelectedPlacementId = "";
/*
* Limpiar las firmas no cambia la visibilidad
* del panel de propiedades.
*/
this.renderPlacements();
this.syncState();
};
Viewer.prototype.syncState = function () {
this.state.CurrentPage = this.currentPage;
this.state.Scale = this.scale;
this.state.SignMode = !!this.signMode;
this.state.SelectedPlacementId = this.selectedId || "";
this.state.IsFullscreen = this.root.classList.contains("is-fullscreen");
this.state.PropertiesPanelOpen = !!this.propertiesPanelOpen;
this.state.Placements.forEach(function (placement) {
delete placement.ImageUrl;
});
if (this.control.stateField) {
this.control.stateField.value = JSON.stringify(this.state);
}
};
Viewer.prototype.showError = function (message) {
if (!this.loading) { return; }
this.loading.hidden = false;
this.loading.textContent = message;
this.loading.classList.add("is-error");
};
function initialize(root) {
if (!root || root.__sigPdfControl) { return; }
root.__sigPdfControl = new PdfControl(root);
}
function initializeAll() {
qsa(document, ".sig-pdf-control").forEach(initialize);
}
window.SigPdfViewer = {
init: function (id) {
initialize(document.getElementById(id));
},
initAll: initializeAll,
command: function (controlId, commandName, payload) {
var controlRoot = document.getElementById(controlId);
if (!controlRoot) { return; }
initialize(controlRoot);
var control = controlRoot.__sigPdfControl;
var viewer = control ? control.viewer : null;
if (!viewer) { return; }
if (commandName === "finish-signing") {
viewer.finishSigningClient();
} else if (commandName === "add-stored-signature") {
viewer.addStoredSignatureClient(payload || {});
} else if (commandName === "close-drawer") {
var drawerId = controlRoot.getAttribute("data-host-drawer-id");
if (drawerId && window.SIGPdfDrawer) {
window.SIGPdfDrawer.close(drawerId);
}
}
}
};
if (window.Sys && window.Sys.Application && typeof window.Sys.Application.add_load === "function") {
window.Sys.Application.add_load(initializeAll);
} else if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeAll);
} else {
initializeAll();
}
}(window, document));