対象となる開発形態
- intra-mart Accel Platform
- IM-FormaDesigner
やりたいこと
- IM-FormaDesignerのファイルアップロードアイテムで、「ファイルを開く」ダイアログに表示する拡張子を制限する。
- 指定した拡張子以外が選択された場合にエラーを表示し、ファイルアップロードさせない。
サンプルコード
スクリプトアイテムを画面上に配置するか、アクション設定-初期表示イベントでカスタムスクリプトを追加し、以下のスクリプトを貼ります。
ファイルアップロードアイテムが1つ
// MutationObserverオブジェクトを生成
let observer = new MutationObserver(function(r, o) {
// コールバック関数
// 追加されたノードが#file-regist-formの場合
if ($('form#file-regist-form').length) {
// fileにaccept属性を追加
$('input.imui-fileupload-input[type="file"]').attr('accept', 'text/html');
// fileのchangeイベントを追加
$('input.imui-fileupload-input[type="file"]').on('change', function() {
// ファイルリストを取得
let files = $('span.upload-file-name');
for (i = 0; i < files.length; i++) {
let splitName = $(files[i]).data('file_name')
.split('.');
// 拡張子判定
if (splitName[splitName.length - 1].toLowerCase() !== 'html'
&& splitName[splitName.length - 1].toLowerCase() !== 'htm') {
imuiAlert('HTML文書以外は添付できません。', '拡張子エラー', true);
// エラーの場合、直近のtrタグごと消去
$(files[i]).closest('tr.template-upload')
.remove();
}
}
});
}
});
// 監視を開始
observer.observe($('body')[0], {childList: true});
解説
MutationObserver
ファイルアップロードダイアログはimuiPageDialog
で作られており、「+」ボタンが押されるまでDOM要素が構成されません。
そのため、MutationObserver
で<form id="file-regist-form">
が生成されるのを監視しています。
ダイアログはbody
要素直下に生成されるため、MutationObserver
はchildList
指定でbody
要素のノード変更を全て監視することになります。
このため、コールバック関数内でノード変更内容をチェックして、生成されたノードが<form id="file-regist-form">
かどうかを判断する必要があります。
<input type="file">のaccept属性
ノード変更時にform#file-regist-form
が存在したら、子要素の<input type="file>
要素に対してaccept
属性を付与します。
これにより、「ファイルを開く」ダイアログを表示した時に表示するファイルタイプを指定しています。
ただし、これだけではファイルタイプを変更された場合に指定拡張子以外でも選択できてしまうため、ファイルが追加された時に別途拡張子をチェックする処理が必要となります。
追加ファイル拡張子チェック
<input type="file">
は、ファイルが追加/削除された時にchange
イベントが発生するため、これをトリガーとして追加されたファイルの拡張子チェックを実行します。
通常<input type="file">
のファイルリストは$('input.imui-fileupload-input[type="file"]')[0].files
で取得できますが、ファイルアップロードアイテムでは独自のファイルリストを自動的に作成するだけでfiles
属性には追加がされない模様(ファイルを追加してもlength
が0になっている)。
独自ファイルリストは<span class="upload-file-name">
のdata属性data-file_name
に格納されており、実際のファイル追加時もこちらを判断しているようです。
そのため、ファイルリストとしてこちらを取得します。
後は、ファイル名から拡張子を取得・判定し、エラーとする場合はアラートを表示した後で直近の<tr class="template-upload">
ごとremoveしてやればOK。
ファイルアップロードアイテムが2つ以上
// ファイルアップロードアイテムの制限内容
let checkFiles = [];
checkFiles = [{
uploadItemId: 'item1',
fileExt: ['html', 'htm'],
mimeType: 'text/html',
fileType: 'HTML文書'
},
{
uploadItemId: 'item2',
fileExt: ['css'],
mimeType: 'text/css',
fileType: 'CSSファイル'
}]
// ファイルアップロードフォーム
let checkForms = [];
// MutationObserverオブジェクトを生成
let observer = new MutationObserver(function(r, o) {
// コールバック関数
for (let i = 0; i < checkFiles.length; i++) {
// uploadItemIdが一致するform要素
checkForms[i] = $('form#file-regist-form').has('input#upload-item-id[value="' + checkFiles[i].uploadItemId + '"');
// 追加されたノードが#file-regist-formの場合
if (checkForms[i].length) {
// fileにaccept属性を追加
checkForms[i].find('input.imui-fileupload-input[type="file"]')
.attr('accept', checkFiles[i].mimeType);
// for文にループインデックスiを引き渡す
(function(i) {
// fileのchangeイベントを追加
checkForms[i].find('input.imui-fileupload-input[type="file"]')
.on('change', function() {
// ファイルリストを取得
let files = checkForms[i].find('span.upload-file-name');
for (let j = 0; j < files.length; j++) {
let splitName = $(files[j]).data('file_name')
.split('.');
// 拡張子判定
if ($.inArray(splitName[splitName.length - 1].toLowerCase(), checkFiles[i].fileExt) === -1) {
imuiAlert(checkFiles[i].fileType + '以外は添付できません。', '拡張子エラー', true);
// エラーの場合、直近のtrタグごと消去
$(files[j]).closest('tr.template-upload')
.remove();
}
}
});
})(i);
}
}
});
// 監視を開始
observer.observe($('body')[0], {childList: true});
解説
基本的には1つの時とやることは変わらず、どうやって複数のファイルアップロードアイテムの処理を回すかだけです。
今回は、先にファイルアップロードアイテム毎のチェック内容をobjectに設定し、for文で設定数分ループを回す方式を採りました。
ポイントは、予めupload-item-id
が一致するform要素を発見し、配列checkForms
に格納している点。
以降、<input type="file">
や<span class="upload-file-name">
を取得する場合は、checkForms[i]
を基準にfind()
で子ノードを取得することで、upload-item-id
単位の処理を実現しています(実際にはファイルアップロードダイアログ自体が同時に複数存在することがないため、不要かも)。
また、複数の拡張子判定(jpg/jpeg
など)に対応するため、拡張子指定を配列とし、チェック時にはjQuery関数$.inArray()
で判定しています。
ファイルアップロードアイテムが1つの場合はAND条件で直接指定してやればいいので、この対応は不要です。
最後に注意点として、forループ内でchange
イベントのコールバック関数を定義しているため、(function(i) {})(i);
を用いてcallback関数にループインデックスi
を引き継いでいます。
これをやらないと、コールバック関数内ではi
がスコープ外になってしまうため参照できません。