0
0

画像をあるアクションでアップロードする

Last updated at Posted at 2023-11-01

内容

javascript、今回はkintoneを使用し、画像をあるサーバからアップする方法を記載する。
以下のパターンを検証する。
1.外部urlより取得
2.kintone内の画像より取得

外部urlより取得

主な関数、API

・あるURLから画像を取得したい場合fetchを使用する。

・kintoneでファイルをアップロードする。
https://sample.cybozu.com/k/v1/file.json

kintoneでは以下のようになる。
プロセスのアクションボタンを押下したときに発動させる例。

kintone.events.on('app.record.detail.process.proceed', async function(event) {
    if (event.action.value === '画像アップロード') { // アクションボタンの名前に合わせて変更
        try {
            const response = await fetch('外部サーバの画像URL');
            const blob = await response.blob();

            // kintoneのAPIを使ってblobをアップロード
            let formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); // filenameは実際のファイル名に変更

            const headers = {
                'X-Cybozu-API-Token': 'YOUR_API_TOKEN' // ここに実際のAPIトークンを設定してください
            };

            //画像をアップロード
            const uploadResp = await fetch("/k/v1/file.json", {
                method: "POST",
                headers: headers,
                body: formData,
            });
            
            const uploadData = await uploadResp.json();
            
            if (uploadData.fileKey) {
                record[attachmentFieldCode].value.push({ fileKey: uploadData.fileKey });
            }


        } catch (error) {
            console.error('Error:', error);
        }
    }
    return event;
});

画像の取得元のサーバがCORS(Cross-Origin Resource Sharing)をサポートしている必要がある。CORSの設定がされていないと、ブラウザのセキュリティポリシーにより画像の取得に失敗する可能性あり。
kintoneのAPIの呼び出しに関するレートリミットや、ファイルのサイズ制限なども考慮する必要あり。

上記はasync awaitを使用しているが下記はfetchとthenを使ったPromiseペースを使用した例。

kintone.events.on('app.record.detail.process.proceed', function(event) {
    if (event.action.value === '画像アップロード') { 
        fetch('外部サーバの画像URL')
        .then(response => response.blob())
        .then(blob => {
            var formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); 

            return kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {
                fileKey: formData
            });
        })
        .then(response => {
            if (response.fileKey) {
                event.record['添付ファイルフィールドのAPIコード'].value = [response.fileKey];
            }
        })
        .catch(error => {
            console.error('Error:', error);
        });
    }
    return event;
});


あと上記においてエラーハンドリングは強化しておくべき。

kintone.events.on('app.record.detail.process.proceed', function(event) {
    if (event.action.value === '画像アップロード') {
        fetch('外部サーバの画像URL')
        .then(response => {
            if (!response.ok) {
                throw new Error('外部サーバからの画像の取得に失敗しました。');
            }
            return response.blob();
        })
        .then(blob => {
            var formData = new FormData();
            formData.append('file', blob, 'filename.jpg'); 

            return kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {
                fileKey: formData
            });
        })
        .then(response => {
            if (!response.fileKey) {
                throw new Error('kintoneへの画像のアップロードに失敗しました。');
            }
            event.record['添付ファイルフィールドのAPIコード'].value = [response.fileKey];
        })
        .catch(error => {
            console.error('Error:', error);
            alert(error.message);  // ユーザへのエラーメッセージの表示
            // ここで、エラーログの保存やリトライの機構を実装することも考えられます。
        });
    }
    return event;
});

kitone内の画像で対応

外部urlより取得の場合、CORS問題で取得できない場合がある。
その場合は無理に外部urlで行うよりも、kintone内のアプリより画像を取得してセットとした方が早い。

以下の流れで対応できる。
既存レコードに対してカスタマイズで画像のアップロードが可能
1.JavaScriptカスタマイズでボタン追加
2.カスタマイズボタンクリック処理
3.kintoneのあるアプリ(ファイル管理)より画像を取得
4.取得した画像のアップロード
5.ファイルキーを取得し該当レコードを更新

  1. JavaScriptカスタマイズでボタン追加

    • kintoneのアプリカスタマイズ機能を使用して、アプリの詳細画面にカスタムボタンを追加します。
    • 例: kintone.app.record.getHeaderMenuSpaceElement().appendChild(button);
  2. カスタマイズボタンクリック処理

    • ボタンにクリックイベントを追加し、処理を定義します。
    • 例: button.onclick = function() { // 以下の処理を実行 };
  3. kintoneのあるアプリ(ファイル管理)より画像を取得

    • kintone REST APIを使用して、特定のアプリから画像ファイルを取得します。
    • 例: kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: <ファイル管理アプリID>, id: <レコードID>}, function(resp) { // 画像データの取得 });
  4. 取得した画像のアップロード

    • 取得した画像ファイルを、アップロード用のAPIを使用してkintoneにアップロードします。
    • 例: kintone.api(kintone.api.url('/k/v1/file', true), 'POST', {fileKey: <取得した画像のファイルキー>}, function(resp) { // アップロード完了後の処理 });
  5. ファイルキーを取得し該当レコードを更新

    • アップロードが完了したら、ファイルキーを取得します。
    • 取得したファイルキーを使用して、対象のレコードを更新します。
    • 例: var body = {app: <アプリID>, id: <レコードID>, record: {<画像フィールド名>: {value: [<ファイルキー>]}}}; kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body, function(resp) { // レコード更新完了後の処理 });

これらのステップを組み合わせることで、kintoneのアプリにおいて既存レコードに対して画像のアップロードを実現できます。APIの詳細やパラメータは、kintoneのドキュメントを参照して適宜調整してください。

以下は間違っています!、間違い箇所は保存ボタン押下では、画像をセットできません!
1.画像の取得
2.取得した画像のアップロード
3.アップロードした結果として得られる新しいfileKeyをレコードの添付ファイルフィールドにセット
4.その変更をeventオブジェクトに反映
5.return event;でkintoneの標準の新規登録・更新の動作を続行

async function getAndUploadAttachment(fileKey) {
    const headers = {
        'X-Requested-With': 'XMLHttpRequest',
    };
    try {
        // 画像を取得
        const resp = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
            method: 'GET',
            headers,
        });
        const blob = await resp.blob();
        
        // 新しい添付ファイルとして登録
        const formData = new FormData();
        formData.append("file", blob);
        
        const uploadResp = await fetch("/k/v1/file.json", {
            method: "POST",
            headers,
            body: formData,
        });
        
        const uploadData = await uploadResp.json();
        return uploadData.fileKey;
    } catch (error) {
        console.error("Error:", error);
        throw error;
    }
}

//新規、編集時の保存ボタン押下
kintone.events.on('app.record.create.submit,app.record.edit.submit', async function(event) {
    const record = event.record;
    const attachmentFieldCode = 'YOUR_ATTACHMENT_FIELD_CODE';

    record[attachmentFieldCode].value[0].fileKey = await getAndUploadAttachment(fileKey);

    if (event.type === 'app.record.create.submit') {
        // 新規作成時のみの処理
    } else if (event.type === 'app.record.edit.submit') {
        // 編集時のみの処理
    }

    return event;
});

メモ

もう少し具体的に記載
個別関数のエラーハンドリング

async function getImageFileKey(imageAppId, imageRecordId) {
  try {
    const imageResp = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {
      app: imageAppId,
      id: imageRecordId
    });
    return imageResp.record['添付ファイルフィールドコード'].value[0].fileKey;
  } catch (err) {
    console.error('画像のファイルキー取得でエラーが発生しました:', err);
    throw new Error('画像のファイルキー取得に失敗しました。');
  }
}

async function downloadImage(fileKey) {
  try {
    const fileResp = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
      method: 'GET',
      headers: { 'X-Requested-With': 'XMLHttpRequest' }
    });
    if (!fileResp.ok) {
      throw new Error('ファイルのダウンロードに失敗しました。');
    }
    return await fileResp.blob();
  } catch (err) {
    console.error('画像のダウンロードでエラーが発生しました:', err);
    throw err; // 再スローして呼び出し元でキャッチする
  }
}

async function updateRecordWithImage(currentAppId, currentRecordId, blob) {
  try {
    const formData = new FormData();
    formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
    formData.append('file', blob, 'image.jpg'); // ファイル名は適宜変更可能

    const uploadResp = await kintone.api(kintone.api.url('/k/v1/file', true), 'POST', formData);
    const updateResp = await kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', {
      app: currentAppId,
      id: currentRecordId,
      record: {
        '更新対象の添付ファイルフィールドコード': {
          value: [{ fileKey: uploadResp.fileKey }]
        }
      }
    });
    return updateResp;
  } catch (err) {
    console.error('レコードの更新でエラーが発生しました:', err);
    throw new Error('レコードの更新に失敗しました。');
  }
}

メイン関数のエラーハンドリング

async function updateImageProcess() {
  try {
    // 各ステップの関数を呼び出し
    const fileKey = await getImageFileKey(画像アプリID, 画像レコードID);
    const blob = await downloadImage(fileKey);
    const updateResp = await updateRecordWithImage(kintone.app.getId(), kintone.app.record.getId(), blob);

    console.log('レコード更新完了', updateResp);
  } catch (err) {
    // 呼び出し元でキャッチされたエラーの処理
    console.error('画像更新プロセスでエラーが発生しました:', err);
    alert(err.message);
  }
}

はい、おっしゃる通りです。個別の関数でエラーが発生した場合、throw new Error を使用してエラーをスローし、それをメイン関数のtry...catchブロックでキャッチするというのが一般的なアプローチです。また、catchブロック内でthrowを使用してエラーを再スロー(rethrow)することもできます。これにより、エラーの伝播と適切なハンドリングが可能になります。

エラーの再スロー(Rethrow)

エラーの再スローは、エラーをキャッチして何らかの処理を行った後に、そのエラーを上位のコードに伝播させるために使われます。以下はその一例です:

try {
  // 何らかの処理
} catch (err) {
  // エラー処理
  console.error('エラーが発生しました:', err);

  // エラーを再スロー
  throw err;
}

このパターンでは、catchブロックがエラーをキャッチし、ログ記録などの処理を行った後、同じエラーをthrowを使って再スローします。これにより、エラーはさらに上位のコード(例えば、他のtry...catchブロックや呼び出し元の関数)に伝播され、そこで適切にハンドリングされることになります。

throw new Error と throw err の違い

  • throw new Error('メッセージ'): 新しいエラーオブジェクトを作成してスローします。これは新たなエラーコンテキストを作成し、独自のエラーメッセージを指定する際に使用されます。
  • throw err: 既にキャッチされたエラーオブジェクトをそのまま再スローします。これは、エラーの伝播を維持しながら、追加の処理(ログ記録など)を行う場合に使用されます。

throw err

  • 使用する場合: 既にキャッチされたエラーオブジェクトをそのまま上位のコードに伝播させたい場合。
  • 目的: エラーの原因やスタックトレースを変更せずに、そのままエラーを伝播させます。これにより、エラーが最初に発生した場所の情報が保持されます。

throw new Error

  • 使用する場合: 新しいエラーコンテキストを作成したい、またはエラーメッセージをより明確にしたい場合。
  • 目的: 新しいエラーオブジェクトを作成することで、エラーの説明をカスタマイズできます。ただし、新しいエラーオブジェクトを作成すると、元のエラーのスタックトレースは失われます。

使い分けのポイント

  • エラーの詳細(原因や場所)をそのまま保持する必要がある場合はthrow errを使用します。
  • エラーの説明をカスタマイズしたい、またはエラーの文脈を変更したい場合はthrow new Error('カスタムメッセージ')を使用します。

まとめ

エラーハンドリングの際には、エラーの原因となったコンテキスト、エラーメッセージの明確さ、およびエラー情報の伝播方法を考慮して、throw errthrow new Errorのどちらを使用するかを決定します。これにより、エラーをより適切に処理し、開発者やエンドユーザーに必要な情報を提供できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0