一覧画面へのサムネイルの表示

カスタマイズビューを使ってアプリのデータを一覧表示させるのですが、その際、それぞれのレコードに割り当てた画像をサムネイルとして一覧上に表示させることを考えています。

https://cybozudev.zendesk.com/hc/ja/articles/203126440-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%9F%E6%B7%BB%E4%BB%98%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA%E3%83%93%E3%83%A5%E3%83%BC%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86

こちらのチュートリアルを参考に、レコードIDを決め打ちでサムネイル画像の表示をすることはできました。
しかし、決め打ちではなく、ループの処理の中で、一覧を出力し、それぞれのレコードに割り当てた画像をサムネイルで表示させようとすると、エラーとなりうまくいきいません。

デバッグしたところ、
var filekey = record.attachment.value[0].fileKey;
のところで、filekey の取得はできているのですが、その後

Uncaught ReferenceError: fileKey is not defined

とのエラーが返ってきます。
何かトンチンカンなことをやっているようでしたらご指摘いただきたいのですが、よろしくお願いいたします。
以下ソースです。

(function () {
“use strict”;

var test;
// レコード一覧の表示時に発行日が本日であれば文字色、フィールドの背景色を変更する
kintone.events.on(‘app.record.index.show’, function (event) {

var records = event.records;
for (var i = 0; i < records.length; i++) {
var record = records[i];
if (record.attachment.value.length != 0) {
var filekey = record.attachment.value[0].fileKey;
var title = record.title.value;
filedownload(title,fileKey);
}
}

});

//ファイルダウンロード&リンク生成
function filedownload(title,filekey){
var apiurl = ‘/k/v1/file.json?fileKey=’ + filekey;
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, apiurl, true);
xhr.setRequestHeader(‘X-Requested-With’ , ‘XMLHttpRequest’); //これが無いとIE,FFがNG
xhr.responseType = “blob”;
var blob = xhr.responseType;

xhr.onload = function() {
//blobからURL生成
var blob = xhr.response;
var url = window.URL || window.webkitURL;
var image = url.createObjectURL(blob);

//タイトルの要素生成
var youso = document.createElement(“b”);
youso.innerHTML = “タイトル:” + escapeHtml(title);

$(youso).appendTo(“#title”);
$(‘<a><img src="’ + image + ‘" width=“10%” height=“10%” /></a>’).appendTo(“#file”);
};
xhr.send();
}

//エスケープ用関数
function escapeHtml(str) {
str = str.replace(/&/g, ‘&’);
str = str.replace(/</g, ‘<’);
str = str.replace(/>/g, ‘>’);
str = str.replace(/"/g, ‘"’);
str = str.replace(/'/g, ‘'’);
return str;
};

})();

 

tamakey さん

原因は、ファイル取得APIが非同期処理でAPIの終了を待たずにループが進むためです。
またkintone の仕様でAPI同時処理数上限10件というのがあり、非同期で複数同時API処理ですと上限を超えます。
複数のPCからの同時アクセスも考慮しないといけませんので、ファイル取得APIを1件ずつ順番に処理するのが良いと思います

kintone ファイルのダウンロードとアップロード に、Promiseを使った例を載せていますので、参考にしてください。
ファイルダウンロード後に、そのままアップロードする例ですが、ダウンロード部分を使えると思います。
複数ファイル取得は、再帰呼び出しで対応します。

 

rex0220 さん
ありがとうございます。

確かにデバッグしてみると、ループが終わってから
xhr.onload = function()
の中のロジックが実行されており、非同期処理でつまずいている感じがしていました。

ご指摘いただいた通り、Promise を使ったところ、エラーはなくなり各レコードに割り当てた画像のURLの取得ができました。
ありがとうございます!
因みに、
https://www.joyzo.co.jp/blog/2021
こちらで指摘されている通り、イベント
‘app.record.index.show’
で使うとエラーが返されるので、return なしで Promise を使っております。

ただ、すべてのレコードのURLを取得しているようですが、やはりループが終わってから
xhr.onload = function()
の中のロジックが実行されており、その結果、一覧の最後の一レコードだけがサムネイルを表示する、という状況になってしまっております。

kintone の標準の一覧ではサムネイルが各レコードに表示されるので、やろうとしていることが不可能でないことは重々承知なのですが、私のカスタマイズビューではうまくいきません。
なにかまた、おかしなことをしているのではないかと思いますが、お気づきの点がございましたらご指摘いただけると幸いです。

以下、Promise を組み込んだソースです。

(function () {
“use strict”;

kintone.events.on(‘app.record.index.show’, function(event) {
var records = event.records;
var OrderList = document.getElementById(‘OrderListTTbody’);
OrderList.innerHTML = ‘’;
for (var i = 0; i < records.length; i++) {

var record = records[i];
var row = OrderList.insertRow(OrderList.rows.length);
row.id = ‘TrOrderList’;
var TdProductName = row.insertCell(0);
var TdProductImage = row.insertCell(1);

// Product Name
TdProductName.innerHTML = record.ProductName1.value;

// Product Image
var FldProductImage = document.createElement(‘img’);
if (record.ProductImage.value.length != 0) {
new kintone.Promise(function(resolve, reject) {
var filekey = record.ProductImage.value[0].fileKey;
var apiurl = ‘/k/v1/file.json?fileKey=’ + filekey;
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, apiurl, true);
xhr.setRequestHeader(‘X-Requested-With’ , ‘XMLHttpRequest’); //これが無いとIE,FFがNG
xhr.responseType = “blob”;
xhr.onload = function() {
var blob = xhr.response;
var url = window.URL || window.webkitURL;
var image = url.createObjectURL(blob);
FldProductImage.src = url.createObjectURL(blob);
TdProductImage.appendChild(FldProductImage);
};
xhr.send();
});
}
}
});
})();

 

tamakey さん

上記コードは、Promise を宣言していますが、resolve 処理が無いため、意味がありません。

ソースを切り貼りしてみました。こんな感じで順番に処理できると思います。
処理が分かりやすいようにエラー処理なども除いています。
切り貼りしただけですので、動く保証は出来ませんが、考え方はわかると思います。
ファイルをダウンロードしたら、リンク処理して、それから次のレコードを処理するように、再帰呼び出しをしています。
とりあえずお試しください。

 

(function() {
"use strict";

kintone.events.on("app.record.index.show", function(event) {
var records = event.records;
var OrderList = document.getElementById('OrderListTTbody');
OrderList.innerHTML = '';

procImages(0);
return event;

// ファイル取得、表示
function procImages(pno) {

var record = records[pno];
var row = OrderList.insertRow(OrderList.rows.length);
row.id = 'TrOrderList';
var TdProductName = row.insertCell(0);
var TdProductImage = row.insertCell(1);

// Product Name
TdProductName.innerHTML = record.ProductName1.value;

// Product Image
var FldProductImage = document.createElement('img');

if (record.ProductImage.value.length === 0){
if (records.length > (pno + 1))
return procImages(pno + 1);
return true;
}

// file download
var filekey = record.ProductImage.value[0].fileKey;
return fileDownload(filekey).then(function(blob) {

var url = window.URL || window.webkitURL;
// var image = url.createObjectURL(blob);
FldProductImage.src = url.createObjectURL(blob);
TdProductImage.appendChild(FldProductImage);

if (records.length > (pno + 1))
return procImages(pno + 1);
return true;
});

}

});

// File Download
function fileDownload(fileKey) {
return new Promise(function(resolve, reject) {
var url = kintone.api.url('/k/v1/file', true) + '?fileKey=' + fileKey;
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
// successful
resolve(xhr.response);
} else {
// fails
reject(Error('File download error:' + xhr.statusText));
}
};
xhr.onerror = function() {
reject(Error('There was a network error.'));
};
xhr.send();
});
}


})();

rex0220さん

ご返信ありがとうございます。
昨年末よりJSを書き始めたばかりで、Promiseの仕様もよくわからず実装しようとしておりました。
大変お手数をおかけいたしました。
まずは、Promise, resolve, reject, then といったところの一応の理解をしました。
いろいろなサイトを調べてもよくわからなかったのですが、最終的にこちらのサイトが一番わかりやすかったので、私のようなビギナーの方用にリンクを張っておきます。
https://html5experts.jp/takazudo/17107/
https://html5experts.jp/takazudo/17113/

ここに書かれたことを理解したうえで、頂いた上記のソースを読んだところ、仕組みが大体わかりました。
この後、実装してみて、うまくいったらソースをアップします。
行き詰まったらまた質問するかもしれませんが、まずはなによりお礼まで。
お忙しい中本当にありがとうございます。

必ず成功させます。

rex0220さん

頂いたソースを基に、本番で使用する予定のアプリにも実装することができました。

以下、基にしたソースです。これで動作することが確認できました。
頂いたソースから殆どというか全く変わってないかな・・・

(function() {
  "use strict";

    kintone.events.on("app.record.index.show", function(event) {
      var records = event.records;
      var OrderList = document.getElementById('OrderListTTbody');
      OrderList.innerHTML = '';

      ListGenerate(0);
      return event;

      function ListGenerate(pno) {
        var record = records[pno];
        var row = OrderList.insertRow(OrderList.rows.length);
        row.id = 'TrOrderList';
        var TdProductName = row.insertCell(0);
        var TdProductImage = row.insertCell(1);
        // Product Name
        TdProductName.innerHTML = record.ProductName1.value;
        // Product Image
        var FldProductImage = document.createElement('img');
        if (record.ProductImage.value.length === 0){
          if (records.length > (pno + 1))
          return ListGenerate(pno + 1);
          return true;
        }
        var filekey = record.ProductImage.value[0].fileKey;
        return fileDownload(filekey).then(function(blob) {
          var url = window.URL || window.webkitURL;
          // var image = url.createObjectURL(blob);
          FldProductImage.src = url.createObjectURL(blob);
          TdProductImage.appendChild(FldProductImage);
          if (records.length > (pno + 1))
          return ListGenerate(pno + 1);
          return true;
        });
      }
    });

    // File Download
    function fileDownload(fileKey) {
      return new Promise(function(resolve, reject) {
        var url = kintone.api.url('/k/v1/file', true) + '?fileKey=' + fileKey;
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';
        xhr.onload = function() {
          if (xhr.status === 200) {
            // successful
            resolve(xhr.response);
          } else {
            // fails
            reject(Error('File download error:' + xhr.statusText));
          }
        };
        xhr.onerror = function() {
          reject(Error('There was a network error.'));
        };
        xhr.send();
      });
    }
  })();

当初は for loop で一覧を表示しておりましたが、いただいたソースを基に書き換えました。
大変助かりました。ありがとうございました。