(function ($, window, document) {
'use strict';
function ensureStyles() {
if (document.getElementById('ekyc-slot-uploader-style')) {
return;
}
var css = '' +
'.ekyc-slot-grid{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;}' +
'.ekyc-slot-wrap{width:66px;position:relative;}' +
'.ekyc-plus-slot{width:66px;height:66px;border:1px solid #cfd3d7;background:#f7f7f7;color:#9aa0a6;font-size:34px;line-height:62px;text-align:center;cursor:pointer;border-radius:2px;padding:0;}' +
'.ekyc-plus-slot.is-uploaded{color:transparent;}' +
'.ekyc-plus-slot.has-thumb{background-size:cover;background-position:center;background-repeat:no-repeat;}' +
'.ekyc-slot-name{margin-top:2px;font-size:10px;line-height:1.2;color:#666;word-break:break-all;}' +
'.ekyc-slot-delete{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border:none;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;font-size:12px;line-height:18px;text-align:center;cursor:pointer;padding:0;}';
var style = document.createElement('style');
style.id = 'ekyc-slot-uploader-style';
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
function ensurePreviewModal(modalSelector, imageSelector) {
if ($(modalSelector).length > 0) {
return;
}
var modalId = modalSelector.replace('#', '');
var imgId = imageSelector.replace('#', '');
var html = '' +
'
' +
'
' +
'
' +
'
' +
'
![preview]()
' +
'
' +
'
' +
'
' +
'
';
$('body').append(html);
}
function escHtml(s) {
return String(s).replace(/[&<>"']/g, function (m) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m];
});
}
window.EkycSlotUploader = {
init: function (opts) {
ensureStyles();
var options = $.extend({
maxCount: 10,
initialSlots: 4,
existingResults: [],
previewModalSelector: '#ekycPreviewModal',
previewImageSelector: '#ekycPreviewImage',
enableDelete: true,
errorModalSelector: '#modal_box',
errorMessageSelector: '#file_error_message'
}, opts || {});
var $root = $(options.containerSelector);
if ($root.length === 0) {
return null;
}
ensurePreviewModal(options.previewModalSelector, options.previewImageSelector);
var uploadedResults = Array.isArray(options.existingResults) ? options.existingResults.slice(0, options.maxCount) : [];
function showError(msg) {
if (options.errorMessageSelector) {
$(options.errorMessageSelector).html(msg);
}
if (options.errorModalSelector) {
$(options.errorModalSelector).modal('show');
}
}
function isAllowedImage(file) {
if (!file) {
return false;
}
var t = (file.type || '').toLowerCase();
if (t.indexOf('image/') === 0) {
return true;
}
var n = (file.name || '').toLowerCase();
return /\.(jpg|jpeg|png|gif|webp)$/i.test(n);
}
function syncHiddenInputs() {
var $inputContainer = $(options.hiddenSelector);
$inputContainer.empty();
uploadedResults.forEach(function (r) {
$inputContainer.append('');
});
}
function syncSummary() {
if (options.countSelector) {
$(options.countSelector).text(uploadedResults.length);
}
if (options.listSelector) {
var names = uploadedResults.map(function (r) {
return escHtml(r.originalName || r.name || (r.key ? String(r.key).split('/').pop() : 'uploaded'));
});
$(options.listSelector).html(names.map(function (n) { return '' + n + ''; }).join(''));
}
if (options.summaryBoxSelector) {
if (uploadedResults.length > 0) {
$(options.summaryBoxSelector).show();
} else {
$(options.summaryBoxSelector).hide();
}
}
}
function openPreview(src) {
if (!src) {
return;
}
$(options.previewImageSelector).attr('src', src);
$(options.previewModalSelector).modal('show');
}
function makeSlot() {
if ($root.find('.ekyc-slot-wrap').length >= options.maxCount) {
return;
}
var $wrap = $('');
var $button = $('');
var $name = $('');
var $input = $('');
$button.on('click', function () {
if (!$button.hasClass('is-uploaded')) {
$input.trigger('click');
}
});
$input.on('change', async function () {
var file = this.files && this.files[0];
if (!file) {
return;
}
if (!isAllowedImage(file)) {
this.value = '';
showError('画像ファイル(jpg / jpeg / png / gif / webp)のみアップロードできます。');
return;
}
this.value = '';
$button.prop('disabled', true).text('...');
if (typeof options.onStart === 'function') {
options.onStart();
}
try {
var urlRes = await fetch(options.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: file.name, type: file.type || 'application/octet-stream' }),
credentials: 'same-origin'
});
if (!urlRes.ok) {
throw new Error('failed_to_get_upload_url');
}
var payload = await urlRes.json();
var putRes = await fetch(payload.url, {
method: 'PUT',
headers: { 'Content-Type': file.type || payload.type || 'application/octet-stream' },
body: file
});
if (!putRes.ok) {
throw new Error('failed_to_upload_file');
}
uploadedResults.push({
key: payload.key,
originalName: file.name,
name: file.name,
preview: URL.createObjectURL(file)
});
renderAll();
} catch (e) {
$button.removeClass('is-uploaded has-thumb').text('+').prop('disabled', false).css('background-image', '');
$name.text('');
showError('書類のアップロードに失敗しました。時間をおいて再試行してください。');
} finally {
if (!$button.hasClass('is-uploaded')) {
$button.prop('disabled', false);
}
if (typeof options.onDone === 'function') {
options.onDone(uploadedResults);
}
}
});
$wrap.append($button).append($name).append($input);
$root.append($wrap);
}
async function deleteByIndex(index) {
var item = uploadedResults[index];
if (!item || !item.key || !options.deleteApiUrl || options.enableDelete === false) {
return;
}
try {
var res = await fetch(options.deleteApiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: item.key }),
credentials: 'same-origin'
});
if (!res.ok) {
throw new Error('delete_failed');
}
uploadedResults.splice(index, 1);
renderAll();
} catch (e) {
showError('書類の削除に失敗しました。時間をおいて再試行してください。');
}
}
function makeUploadedSlot(item, index) {
if ($root.find('.ekyc-slot-wrap').length >= options.maxCount) {
return;
}
var preview = item && item.preview ? item.preview : '';
var name = item && (item.originalName || item.name || (item.key ? String(item.key).split('/').pop() : 'uploaded'));
var $wrap = $('');
var $button = $('');
var $name = $('');
$button.css('background-image', preview ? ('url("' + preview + '")') : '');
$button.on('click', function (e) {
e.preventDefault();
openPreview(preview);
});
if (options.enableDelete !== false && options.deleteApiUrl) {
var $delete = $('');
$delete.on('click', function (e) {
e.preventDefault();
e.stopPropagation();
deleteByIndex(index);
});
$wrap.append($delete);
}
$name.text(name || '');
$wrap.append($button).append($name);
$root.append($wrap);
}
function renderAll() {
$root.empty().addClass('ekyc-slot-grid');
uploadedResults.forEach(function (item, idx) {
makeUploadedSlot(item, idx);
});
syncHiddenInputs();
syncSummary();
var target = Math.min(options.maxCount, Math.max(options.initialSlots, uploadedResults.length + (uploadedResults.length < options.maxCount ? 1 : 0)));
for (var i = $root.find('.ekyc-slot-wrap').length; i < target; i++) {
makeSlot();
}
}
renderAll();
return {
getResults: function () { return uploadedResults.slice(); },
render: renderAll
};
}
};
})(jQuery, window, document);