> For the complete documentation index, see [llms.txt](https://help.genser.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.genser.ai/ja/developers/script-guide/widget-design.md).

# ウィジェット・デザイン

## **1. 概要** <a href="#id-1" id="id-1"></a>

<figure><img src="/files/FQPN0ixK2zygMa2Q0QMr" alt=""><figcaption></figcaption></figure>

* ウィジェット・デザインは、取引先サイトにコードを1行追加するだけで自動的に表示される連携方式です。
* ウィジェット・デザイン内でユーザーが商品をクリックして詳細ページへ遷移しようとする際、その動作を取引先が希望する方法(独自のページ遷移ロジックなど)で直接連携できるよう、「イベント発生時に自動的に実行される関数(コールバック)」を提供します。

***

## **2. 連携方法** <a href="#id-2" id="id-2"></a>

### 2-1. クイックスタート <a href="#id-2-1" id="id-2-1"></a>

* 以下のコード断片(スニペット)をページに貼り付けると、このコードが実際のウィジェットプログラム(メインスクリプト)を自動的に読み込み(ローダー)、初期化まで行います。

```html
<script>
  (function (w, d, s, l) {
    w[l] = w[l] || { q: [] };
    if (!w[l].init) {
      w[l].init = function (c) {
        w[l].q.push({ c: c });
      };
    }
    var f = d.getElementsByTagName(s)[0];
    var j = d.createElement(s);
    j.async = true;
    j.setAttribute('data-vendor-id', 'YOUR_VENDOR_ID');
    j.src = 'https://cdn.genser.ai/dist/genser-discovery-loader-jp.min.js';
    f.parentNode.insertBefore(j, f);
  })(window, document, 'script', 'genserDiscovery');

  window.genserDiscovery.init({
    vendorId: 'YOUR_VENDOR_ID',
    environment: 'web',
    onUrlOpen: function (payload, context) {
      var url = payload.pcUrl || payload.mobileUrl || payload.fallbackUrl;
      if (!url) return { ok: false, reason: 'url_not_found' };
      window.location.href = url;
      return { ok: true, url: url };
    },
  });
</script>
```

### 2-2. init(config) <a href="#id-2-2" id="id-2-2"></a>

* 役割: この関数を呼び出すことで、ウィジェットが実際に画面に表示され、動作を開始します。
* 関数名: `window.genserDiscovery.init`
* 戻り値: 実行結果を非同期で通知する値(`Promise`)
* パラメーター

| オプション       | 型                  | 必須 | 説明                            |
| ----------- | ------------------ | -- | ----------------------------- |
| vendorId    | string             | 必須 | 取引先識別ID                       |
| onUrlOpen   | function           | 推奨 | 商品詳細/外部遷移イベント処理コールバック(3章参照)   |
| environment | 'web' \| 'webview' | -  | 実行環境。未指定の場合は自動検出              |
| onReady     | function           | -  | UIレンダリング完了後に1回呼び出し            |
| onError     | function           | -  | 初期化・読み込み失敗時にエラーオブジェクトとともに呼び出し |

### 2-3. コールバック名(エイリアス) <a href="#id-2-3" id="id-2-3"></a>

遷移処理コールバックは以下のいずれかの名前で指定でき、複数指定した場合は上から優先的に適用されます。

```
onOpen > openCallback > onUrlOpen > onProductOpenDetail > onProductDetail > productDetailCallback
```

`onUrlOpen`を標準として使用することを推奨します。

### 2-4. environment自動検出 <a href="#id-2-4" id="id-2-4"></a>

`environment`を指定しない場合、SDKが実行環境を自動的に検出します。ネイティブWebView(ブリッジが存在する)環境では`webview`、それ以外では`web`として動作します。通常のWeb連携では`environment: 'web'`を明示することを推奨します。

### 2-5. ウィジェットタイプ(デフォルト / カスタム) <a href="#id-2-5" id="id-2-5"></a>

ウィジェットタイプは管理画面で設定し、2つの方式があります。

* **デフォルト**: 追加のスクリプト作業なしで、SDKが提供するオーバーレイのトリガーをそのまま使用します。
* **カスタム**: SDKが提供するデフォルトウィジェット要素の代わりに、取引先が独自に作成した要素(ボタンなど)をトリガーとして使用します。この場合、該当要素のクリックイベントに`open()`を、閉じる動作が必要な箇所には`close()`を直接連携する必要があります。

```js
document.querySelector('#my-custom-trigger').addEventListener('click', function () {
  window.genserDiscovery.open();
});

document.querySelector('#my-custom-close-button').addEventListener('click', function () {
  window.genserDiscovery.close();
});
```

{% hint style="info" %}
ウィジェットタイプをカスタムに設定した場合、`open()`/`close()`を連携しないとオーバーレイが開閉しません。`open()`/`close()`の詳細仕様は[**\[5. APIリファレンス\]**](#id-5)を参照してください。
{% endhint %}

***

## **3. イベント処理方法** <a href="#id-3" id="id-3"></a>

ユーザーがウィジェット内で商品をクリックした際、そのクリックを自社サイトが検知して反応できるようにする方法は2つあります: **コールバック方式**(イベント発生時に自動実行される関数を`init()`にあらかじめ登録する`onUrlOpen`)と**購読方式**(必要なイベントだけを個別に登録して受け取る`on`/`off`)。

{% hint style="info" %}
`product.openDetail`イベントがポップアップ(モーダル)を経由して発生するか、クリックと同時に発生するかは、管理画面の[**\[その他表示設定 > 商品カード > PDP表示方法\]**](https://help.genser.ai/~/changes/356/design/additional-options#id-2-2.-pdp)の設定によって異なります。
{% endhint %}

| イベント名              | 説明               |
| ------------------ | ---------------- |
| product.openDetail | 商品詳細ページへの遷移リクエスト |

### 3-1. onUrlOpen(コールバック方式) <a href="#id-3-1" id="id-3-1"></a>

```
onUrlOpen(payload, context)
```

{% hint style="warning" %}
**コールバックの引数はpayload、contextの2つです。**\
イベントタイプが必要な場合は`context.message.type`で確認します。
{% endhint %}

| 引数      | 説明                                                                          |
| ------- | --------------------------------------------------------------------------- |
| payload | クリックされた商品の情報と遷移先URLが含まれるデータ                                                 |
| context | このイベントに応答(返信)できる経路となるオブジェクト(元のメッセージは`context.message`、応答関数は`context.reply`) |

**payloadフィールド**

| フィールド       | 型              | 説明                        |
| ----------- | -------------- | ------------------------- |
| productSku  | string         | 商品SKU                     |
| pcUrl       | string         | PC遷移URL                   |
| mobileUrl   | string \| null | モバイル遷移URL                 |
| fallbackUrl | string         | 既定のfallback URL           |
| source      | string         | イベント発生箇所(例: product-card) |
| utmParams   | object         | (任意)URLクエリにマージするUTMパラメーター |

**例**

```json
{
  "productSku": "3370616",
  "pcUrl": "https://shop.example.jp/product/3370616",
  "mobileUrl": null,
  "fallbackUrl": "https://shop.example.jp/product/3370616",
  "source": "product-card"
}
```

**URL優先順位**

遷移先URLは以下の順序で選択することを推奨します。(SDKの既定処理も同じ順序を使用します。)

| 優先順位 | フィールド               |
| ---- | ------------------- |
| 1    | payload.pcUrl       |
| 2    | payload.mobileUrl   |
| 3    | payload.fallbackUrl |

**推奨例**

```js
function handleUrlOpen(payload, context) {
  var url = payload.pcUrl || payload.mobileUrl || payload.fallbackUrl;
  if (!url) {
    return { ok: false, reason: 'url_not_found' };
  }
  window.location.href = url;
  return { ok: true, url: url };
}

window.genserDiscovery.init({
  vendorId: 'YOUR_VENDOR_ID',
  environment: 'web',
  onUrlOpen: handleUrlOpen,
});
```

### 3-2. on / off(購読方式) <a href="#id-3-2" id="id-3-2"></a>

`init()`にコールバックを登録する代わりに、必要なイベントが発生するたびに実行する処理を個別にあらかじめ登録(購読)しておくこともできます。

```js
window.genserDiscovery.on('product.openDetail', function (payload, context) {
  var url = payload.pcUrl || payload.mobileUrl || payload.fallbackUrl;
  if (!url) {
    context.reply('product.openDetail.result', { ok: false, reason: 'url_not_found' });
    return;
  }
  window.location.href = url;
  context.reply('product.openDetail.result', { ok: true, url: url });
});
```

{% hint style="warning" %}
**on()は戻り値が自動的に返信されません。**

`on()`で購読したハンドラーは戻り値が自動的に返信されないため、必ず`context.reply('<イベント名>.result', { … })`で直接結果を返信する必要があります。(一方、`init`の`onUrlOpen`は戻り値が自動的に返信されます。(**3-1参照**))
{% endhint %}

**購読解除**

```js
window.genserDiscovery.off('product.openDetail', handler); // 特定のハンドラーのみ解除
window.genserDiscovery.off('product.openDetail');          // 該当タイプを全て解除
```

***

## **4. 環境別の注意事項** <a href="#id-4" id="id-4"></a>

### 4-1. SPA <a href="#id-4-1" id="id-4-1"></a>

SPA(ページ遷移時にリロードせず画面の一部だけが切り替わる方式)では、アプリ初回起動時に1回だけスニペットを読み込み、`init()`を呼び出します。以降のルーティング(ページ遷移)時に`init()`を繰り返し呼び出さないでください。

### 4-2. JSP <a href="#id-4-2" id="id-4-2"></a>

JSP(ページ遷移のたびに画面全体がリロードされる方式)はページ遷移のたびに画面全体がリロードされるため、ページテンプレートにスニペットと`init()`を一緒に組み込みます。

```html
<script>
  window.genserDiscovery.init({
    vendorId: '<%= vendorId %>',
    environment: 'web',
    onUrlOpen: handleUrlOpen,
  });
</script>
```

***

## **5. APIリファレンス** <a href="#id-5" id="id-5"></a>

`window.genserDiscovery`が提供する主なメソッドです。

<table data-search="false"><thead><tr><th>メソッド</th><th>戻り値</th><th>説明</th></tr></thead><tbody><tr><td>init(config)</td><td>Promise</td><td>初期化。オプションは2章参照</td></tr><tr><td>on(type, handler)</td><td>-</td><td>イベント購読(3-2参照)</td></tr><tr><td>off(type, handler?)</td><td>-</td><td>イベント購読解除</td></tr><tr><td>open()</td><td>-</td><td>ミニオーバーレイを開く。active状態でのみ動作し、開いている状態で再度呼び出しても動作しません</td></tr><tr><td>close()</td><td>-</td><td>ミニオーバーレイを閉じる。閉じている状態で再度呼び出しても動作しません</td></tr><tr><td>toggle()</td><td>-</td><td>オーバーレイの開閉トグル</td></tr><tr><td>changeLanguage(lang)</td><td>boolean</td><td>iframeを再読み込みせずにアプリの言語を変更。対応するlang値(ko, en, ja)</td></tr></tbody></table>

`toggle`、`changeLanguage`などは、メインスクリプトの読み込みおよびレンダリング完了後に動作します。`open()`/`close()`にはパラメーターと戻り値がなく、ウィジェットタイプ(デフォルト/カスタム)にかかわらず使用できます。

***

## **6. 記述例** <a href="#id-6" id="id-6"></a>

コードの記述例を示します。

```html
<script>
  (function (w, d, s, l) {
    w[l] = w[l] || { q: [] };
    if (!w[l].init) {
      w[l].init = function (c) {
        w[l].q.push({ c: c });
      };
    }
    var f = d.getElementsByTagName(s)[0];
    var j = d.createElement(s);
    j.async = true;
    j.setAttribute('data-vendor-id', 'YOUR_VENDOR_ID');
    j.src = 'https://cdn.genser.ai/dist/genser-discovery-loader-jp.min.js';
    f.parentNode.insertBefore(j, f);
  })(window, document, 'script', 'genserDiscovery');

  function handleUrlOpen(payload, context) {
    var url = payload.pcUrl || payload.mobileUrl || payload.fallbackUrl;
    if (!url) {
      return { ok: false, reason: 'url_not_found' };
    }
    window.location.href = url;
    return { ok: true, url: url };
  }

  window.genserDiscovery.init({
    vendorId: 'YOUR_VENDOR_ID',
    environment: 'web',
    onUrlOpen: handleUrlOpen,
    onReady: function () {
      console.log('genser discovery mini ready');
    },
    onError: function (err) {
      console.error('genser discovery mini init failed', err);
    },
  });
</script>
```

***

## **7. 注意事項** <a href="#id-7" id="id-7"></a>

1. ローダーファイル名は`genser-discovery-loader-jp.min.js`を使用してください。
2. `vendorId`は取引先識別値であり、任意に変更するとデータ集計に影響を与える可能性があります。
3. SPA環境では`init()`の重複呼び出しを避けてください。
4. `on()`購読方式を使用する場合は、必ず`context.reply(...)`で結果を返信してください。

   返信しない場合、応答待ち状態のまま残ることがあります。
