顧客訪問リストを地図にピン表示する

目次

warning
注意

2020年8月改訂のセキュアコーディング ガイドライン に抵触する内容が含まれています。
認証情報が漏洩した場合の影響を考慮して慎重に検討してください。
該当箇所:JavaScriptプログラムの13行目

概要

アプリのレコード一覧表示時、顧客の住所をもとに地図上でピンを表示します。
詳細画面での地図表示も行います。

information

サンプルでは、Google Maps PlatformのMaps JavaScript APIを使用しています。
ご利用方法によっては有償ライセンスの購入が必要になります。
Googleのライセンスを確認してください。

完成形

下準備

サンプルプログラム

プログラム

後述のサンプルプログラムについて、以下2点の変更をしてください。

  • 13行目のYour Google API keyの部分を取得したMaps JavaScript APIキーに書き換えてください。
  • 203行目の「// Google Maps JavaScript APIの読み込み」を、次のコードに書き換えてください。
1
2
  (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
    ({key: api_key , v: "beta"});

以下にサンプルプログラムを示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
 * 一覧画面に地図を表示するサンプルプログラム
 * Copyright (c) 2024 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */

(() => {

  'use strict';
  // APIキー
  const api_key = 'Your Google API key';

  // 緯度、経度を空にします
  const emptyLatLng = (event) => {
    // eventよりレコード情報を取得します
    const rec = event.record;

    // 保存の際に緯度、経度を空にします
    rec.lat.value = '';
    rec.lng.value = '';
    return event;
  };

  // 地図をスペースフィールドに表示します。
  // 緯度、経度フィールドを住所を基に算出した値で更新し、
  // 画面をリロードします
  const setLatLng = async (rec) => {
    // Google Geocoderを定義します
    const {Geocoder} = await google.maps.importLibrary('geocoding');
    const gc = new Geocoder();

    // Geocoding APIを実行します
    gc.geocode({
      address: rec['住所'].value,
      language: 'ja',
      country: 'JP'
    }, (results, status) => {

      // 住所が検索できた場合、開いているレコードに対して
      // 緯度・経度を埋め込んで更新します
      if (status === google.maps.GeocoderStatus.OK) {

        // 更新するデータのObjectを作成します
        const objParam = {
          app: kintone.app.getId(), // アプリ番号
          id: kintone.app.record.getId(), // レコードID
          record: {
            lat: {value: results[0].geometry.location.lat()}, // 緯度
            lng: {value: results[0].geometry.location.lng()} // 経度
          }
        };

        // レコードを更新します
        kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', objParam, (resp) => {
          // 成功時は画面をリロードします
          location.reload(true);
        }, (resp) => {
          // エラー時はメッセージを表示して、処理を中断します
          alert('error->' + resp);
        });
      }
    });
  };

  // 地図を「住所」フィールドの下に表示します
  // 緯度・経度がない場合は、住所をもとに緯度・経度を算出し、
  // フィールドに値を入れた後、レコードを更新します
  const setLocationDetail = async (event) => {
    const {AdvancedMarkerView} = await google.maps.importLibrary('marker');

    // レコード情報を取得します
    const rec = event.record;

    // 住所が入力されていなければ、ここで処理を終了します
    if (!rec['住所'].value) {
      return;
    }

    // 緯度・経度が入力されていなければ、住所から緯度・経度を算出して、
    // フィールドに追加し、レコードを更新する
    if (!rec.lat.value || !rec.lng.value) {
      await setLatLng(rec);
      return;
    }

    // 地図を表示するdiv要素を作成します
    const mapEl_address = document.createElement('div');
    mapEl_address.setAttribute('id', 'map');
    mapEl_address.setAttribute('name', 'map');
    mapEl_address.setAttribute('style', 'width: 300px; height: 250px');

    // 「Map」スペース内にmapEl_addressで設定した要素を追加します
    const elMap = kintone.app.record.getSpaceElement('Map');
    elMap.appendChild(mapEl_address);

    // 「Map」スペースの親要素のサイズを変更します
    const elMapParent = elMap.parentNode;
    elMapParent.setAttribute('style', 'width: 300px; height: 250px');

    // ポイントする座標を指定します
    const point = new google.maps.LatLng(rec.lat.value, rec.lng.value);

    // 地図の表示の設定(中心の位置、ズームサイズ等)を設定します
    const opts = {
      zoom: 15,
      center: point,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      scaleControl: true,
      mapId: 'DEMO_MAP_ID'
    };

    // 地図を表示する要素を呼び出します
    const map_address = new google.maps.Map(document.getElementById('map'), opts);

    // マーカーを設定します
    const marker = new google.maps.marker.AdvancedMarkerElement({
      position: point,
      map: map_address,
      title: rec['住所'].value
    });
  };

  // 地図を一覧画面のメニュー下のスペースに表示します
  const setLocationIndex = async (event) => {
    const locations = [];

    // レコード情報を取得します
    const recs = event.records;

    // 一覧に表示されているすべてのレコードの緯度・経度とレコードIDを配列に格納します
    recs.forEach(rec => {
      if (rec.lat.value && rec.lng.value) {
        locations.push({
          lat: parseFloat(rec.lat.value), // 緯度
          lng: parseFloat(rec.lng.value), // 経度
          recno: rec.$id.value // レコードID
        });
      }
    });
    if (locations.length === 0) {
      return;
    }
    // 一覧の上部部分にあるスペース部分を定義します
    const elAction = kintone.app.getHeaderSpaceElement();

    // すでに地図要素が存在する場合は、削除します
    // ※ ページ切り替えや一覧のソート順を変更した時などが該当します
    const existingMap = document.getElementById('map');
    if (existingMap) {
      elAction.removeChild(existingMap);
    }
    // 地図を表示する要素を定義し、スペース部分の要素に追加します
    const mapEl = document.createElement('div');
    mapEl.setAttribute('id', 'map');
    mapEl.setAttribute('name', 'map');
    mapEl.setAttribute('style', 'width: auto; height: 300px; margin: 30px; border: solid 2px #c4b097');
    elAction.appendChild(mapEl);

    const {Map} = await google.maps.importLibrary('maps');
    const {AdvancedMarkerView} = await google.maps.importLibrary('marker');

    // マップの中心を指定
    const position = {lat: locations[0].lat, lng: locations[0].lng};
    const map = new Map(document.getElementById('map'), {
      zoom: 12,
      center: position,
      mapId: 'DEMO_MAP_ID'
    });

    const marker = [];
    const m_latlng = [];

    // 緯度・経度をもとに、地図にポインタを打ち込みます
    locations.forEach((location) => {
      if (!isNaN(location.lat) && !isNaN(location.lng)) {
        const pinView = new google.maps.marker.PinView({
          background: '#FBBC04',
          borderColor: '#111111',
          scale: 1.5,
          glyph: `${location.recno}`
        });

        new AdvancedMarkerView({
          map: map,
          position: {lat: location.lat, lng: location.lng},
          content: pinView.element,
        });
      }
    });
  };

  const isGoogleMapsLoaded = () => {
    return typeof google === 'object' && typeof google.maps === 'object';
  };

  const initMap = async () => {
    if (isGoogleMapsLoaded()) {
      return Promise.resolve();
    }
    // ライブラリの読み込み
    // Google Maps JavaScript APIの読み込み
    return Promise.resolve();
  };

  // 一覧画面で編集モードになった時に実行されます
  const indexEditShow = (event) => {
    const record = event.record;
    // 住所フィールドを使用不可にします
    record['住所'].disabled = true;
    return event;
  };

  // 登録・更新イベント(新規レコード、編集レコード、一覧上の編集レコード)
  kintone.events.on(['app.record.create.submit',
    'app.record.edit.submit',
    'app.record.index.edit.submit'], emptyLatLng);

  // 詳細画面が開いた時のイベント
  kintone.events.on('app.record.detail.show', async (event)=>{
    await initMap();
    setLocationDetail(event);
  });

  // 一覧画面が開いた時のイベント
  kintone.events.on('app.record.index.show', async (event)=> {
    await initMap();
    setLocationIndex(event);
  });

  // 一覧画面で編集モードにした時のイベント
  kintone.events.on('app.record.index.edit.show', indexEditShow);
})();
  1. エディターにサンプルプログラムをコピーし、ファイル名をsample.js 、文字コードをUTF-8にして保存します。
    ファイル名は任意です。
  2. 準備したアプリの設定画面で、保存したファイルを読み込みます。
  3. アプリの設定を完了します。

レコードの詳細画面を開いたとき、住所をもとに緯度、経度を算出してフィールドが更新されます。
レコードの一覧画面を開いたとき、リスト内に緯度、経度の含まれるレコードがある場合、自動的に地図が表示されます。

使用したAPI

変更履歴

  • 2018/07/18
    • Maps JavaScript APIキーを使用したコードに修正
  • 2024/08/06
    • google.maps.Markerサポート終了に伴い、google.maps.marker.AdvancedMarkerElementに変更
information

このTipsは、2024年6月版kintoneで動作を確認しています。