JS XmlHttpRequest 〜JSでの同期/非同期通信〜

イントロダクション

ブラウザアプリを作成しています。
GoogleMapへのデータ登録と表示を行うシンプルなものです。

設計と思想

今回の実装は、PHPを使用するけどサーバーサイドとクライアントサイドを分断して実装しようと思いましたので以下のようなファイル構成で実装しています。2023-06-16現在では、プログラムのサンプルを起動できない状態になっています。
<クライアント>

  • SampleMap.html
  • SampleMap.js

クライアントというのはつまるところブラウザ上で動く処理のことを指しています。処理内容としては、GoogleMapを表示してクリックした場所にどんなものがあるか登録する入力フォームを表示、サーバーサイドに送信するプログラムを実装しました。

<サーバー>

  • InsertMap.php
  • GetMapInfo.php

こちらはサーバーサイドプログラムです。クライアントから送信されたフォームデータを受け取りMySQLのデータベースに情報を登録します。登録された情報はページを開いたときに表示できるようにします。

XmlHttpRequest

クライアントからサーバーへフォームを送信するのに使用するクラスです。JavaScriptのクラスです。
ここで使用するのはXMLHttpReuqstを使用して実装します。
リクエストのタイプは2つあります。

  1. データを送信しないGET
    XMLHttpReuqst.send(); // 引数なし
  2. データを送信するPOST
    XMLHttpReuqst.send(data); // 引数あり

これらは用途により使い分けます。世間巷でよくある$ajaxなどはこれを使用しているのでテクノロジー的には大差ありません。

とりあえずはこんな形で実装します。

しかし

実装したのだけれど、ファイルの送信がうまくいかず。。。以前イメージファイルの送信に以下のようなコードでリクエストを飛ばし、DBへのデータ登録まで確認したのだけれど、またうまくいかなくなりました。

怪しいところ

HTML側のFormに余計な属性をつけた。

enctype="multipart/form-data"

しかし、これは関係ないようでした。どこなのだろうか? 絶賛戦闘中です(笑)

実装結果

/**************************************************
 *          GeoLocationAPIスクリプト          *
 **************************************************/
// グローバル変数
var id, target;

function getCurrentPos() {
    if (navigator.geolocation == false) {
        console.log("GEO Locaion is unable.");
        return;
    }
    // オプション・オブジェクト
    var optionObj = {
        "enableHighAccuracy": false ,
        "timeout": 5000 ,
        "maximumAge": 0 ,
    };
    target = {
        latitude: 0,
        longitufe: 0
    };
    // Navigator.geolocation
    id = navigator.geolocation.watchPosition(
        successCurrentPos // 成功時のコールバック
        , errorCurrentPos // 失敗時のコールバック
        , optionObj); // オプション設定

}

/** GeoLocationの成功時のコールバック */
function successCurrentPos(position) {
    var crd  = position.coords;
    // マップの位置情報オブジェクト
    var mapLatLng = new google.maps.LatLng(crd.latitude, crd.longitude);

    if (target.latitude === crd.latitude && target.longitude === crd.longitude) {
      console.log('Congratulations, you reached the target');
      navigator.geolocation.clearWatch(id);
    }
    // マーカーの追加
    var current = new google.maps.Marker({
        map: map,
        position: mapLatLng,
        icon: './mapImg/you.png'
    });
    console.log("test");
}
/** GeoLocationのエラー時のコールバック */
function errorCurrentPos(error) {
    // エラーコード(error.code)の番号
    // 0:UNKNOWN_ERROR              原因不明のエラー
    // 1:PERMISSION_DENIED          利用者が位置情報の取得を許可しなかった
    // 2:POSITION_UNAVAILABLE       電波状況などで位置情報が取得できなかった
    // 3:TIMEOUT                    位置情報の取得に時間がかかり過ぎた…

    // エラー番号に対応したメッセージ
    var errorInfo = [
        "原因不明のエラーが発生しました…。" ,
        "位置情報の取得が許可されませんでした…。" ,
        "電波状況などで位置情報が取得できませんでした…。" ,
        "位置情報の取得に時間がかかり過ぎてタイムアウトしました…。"
    ];

    alert(errorInfo[error.code]);
    id  = null;
}
/**************************************************
 *          ライブハウス照会機能実装スクリプト          *
 **************************************************/
/** 登録時に選択する位置情報のマーカー */
 var selectedMarker;
/** 入力フォーム */
var sideWin;
/** XMLHttpRequest */
var xhr;
/** オーバーレイの数 */
var oblNo = 0;
/** マーカーリスト */
var markerList = [];

/** 画面の初期化 */
function loadView() {
    // GeoLocation API
    getCurrentPos();
    // XMLHttpRequestの生成
    xhr = createXmlHttpRequest();

    sideWin = document.getElementById('sideWin');
    sideWin.hidden = true;

    // Load Overlay
    var areaData = dstMapData();

}

/** 画面の開閉 */
function sideWinHandle() {
    console.log("change");
    if (sideWin.style.display === 'block') {
        sideWin.style.display = "none";
    } else {
        sideWin.style.display = "block";
    }
    sideWin.hidden = !sideWin.hidden;
}

/**
1. PHPでマップデータ(オーバーレイ)の取得
2. 取得したデータをHTMLのDIVタグに出力する
 */
function dstMapData() {
 // 初期表示用データの取得
 xhr.open('GET', 'https://zenryokuservice.com/tools/maps/GetMapInfo.php', true);
 xhr. responseType = 'json';
 xhr.onreadystatechange = dstMap;
 try {
     xhr.send();
 } catch(e) {
    console.log(e);
 }
}

/** 初期データ取得処理のコールバック関数 */
function dstMap(response) {
    var json;
    if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
            createMapData(xhr.response);
            // カテゴリマスタ取得
            getCategories();

        } else {
            alert('リクエストに問題が発生しました');
        }
    }
}

/** 取得データ(JSON)からオーバーレイ作成 */
function createMapData(jsonData) {
   if (jsonData === "") {
       return;
   }
    var jsonObj = JSON.parse(jsonData);
    jsonObj.forEach(function(data) {
        createOverLay(data);
    });
    return jsonObj;
}

/** マーカーとWindowInfoの作成とmapへの登録 */
function createOverLay(json) {
    // 位置情報がからの時は飛ばす
    if (json.lat == "0" || json.lng == "0") {
        return;
    }
    // マーカー作成
    var marker = new google.maps.Marker({
        position: {lat: parseFloat(json.lat), lng: parseFloat(json.lng)},
        icon: getCategoryImg(json.categoryId),
        map: map,
        category: json.categoryId,
        title: json.name,
    });
    // WindowInfo作成
    var infoWindow = new google.maps.InfoWindow({ maxWidth: 300 });
    marker.addListener('click', function() {
        infoWindow.setContent(createWindowHTML(json));
        infoWindow.open(map, marker);
    });

    // リストに追加
    markerList.push(marker);
}

/** 表示する吹き出しのHTMLを作成する */
function createWindowHTML(json) {
    var html = document.createElement("div");
    html.setAttribute("width", "300px");
    html.innerHTML = '<h4><a href="' + json.url + '">' + json.name + '</a></h4>';
    html.innerHTML += '<div width="300px" height="200px"><iframe width="300" height="200" src="https://www.youtube.com/embed/' + json.image + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" /></div>';
    oblNo++;
    // Youtube Player api

    return html;
}

/** 位置情報の取得と選択位置表示(Overlay) */
function viewLatLng(mapEvent, map) {
    if (selectedMarker != undefined || selectedMarker != null) {
        selectedMarker.setMap(null);
    }
    // 表示部分に設定
    document.getElementById('latText').textContent = mapEvent.latLng.lat();
    document.getElementById('lngText').textContent = mapEvent.latLng.lng();
    // 送信部分に設定
    document.getElementById('latValue').value = mapEvent.latLng.lat();
    document.getElementById('lngValue').value = mapEvent.latLng.lng();

    selectedMarker = new google.maps.Marker(selectedOverlayOpt(mapEvent));
    selectedMarker.setMap(map);
}

/** 選択用のオーバーレイOption */
function selectedOverlayOpt(mapEvent) {
    return {
        position: mapEvent.latLng,
        animation: google.maps.Animation.DROP,
        icon: {
            url: "mapImg/selected.png",
            scaledSize: new google.maps.Size(40, 40)
        },
    };
}

/** セットしたデータをPHPに送信する */
function postData() {
    ajaxPost();
}

/** XMLHttpRequest作成 */
function createXmlHttpRequest() {
    console.log("init httpRequest");
    xhr = new XMLHttpRequest();
    return xhr;
}

/** レスポンスを取得するときの実装 */
function recieveResponse() {
    if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
            alert(xhr.response);
        } else {
            alert('リクエストに問題が発生しました');
        }
    }
}
/** Ajax送信処理 */
function ajaxPost() {
    xhr.open('POST', 'https://zenryokuservice.com/tools/maps/InsertMapInfo.php', true);
    // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;  charset=UTF-8"');
    xhr. responseType = 'text';
    xhr.onreadystatechange = recieveResponse;
    // POSTデータを設定する
    var data = createJSON();
    console.log(data);
    try {
        xhr.send(data);
    } catch(e) {
        console.log(e);
    }
}

// application/x-www-form-urlencoded
// application/json
/** Ajax送信 */
function ajaxGet() {
    xhr.open('GET', 'https://zenryokuservice.com/tools/maps/InsertMapInfo.php?param=test', true);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.onreadystatechange = recieveResponse;

    xhr.send();
}

/** POST送信するデータ(JSON)を作成する */
function createJSON() {
    var data = new FormData(document.getElementById("mapData"));
    // data.append("name", form.name.value);
    // data.append("url", form.url.value);
    // data.append("aFile", form.imageFile.files[0]);
    // data.append("lat", form.lat.value);
    // data.append("lng", form.lng.value);
    return data;
}

/** フォーム部分を開く */
function openLoginForm() {
    var input = prompt("Login Form");
    if ('PGBox' === input) {
        var loggedInButton = document.getElementsByName("loggedIn");
        loggedInButton.forEach(but => {
            but.style = "display: visible";
        });
    }
    // カテゴリマスタ取得
    getCategories();

}

/** カテゴリの変更 */
function changeCategory(selectBox) {
    markerList.forEach(marker => {
        if (selectBox.value === marker.category) {
            marker.setVisible(true);
        } else {
            marker.setVisible(false);
        }
    });
}

/** Ajax送信 */
function getCategories() {
    xhr.open('GET', 'https://zenryokuservice.com/tools/maps/GetCategoryInfo.php', true);
//    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr. responseType = 'json';
    xhr.onreadystatechange = responseMst;

    xhr.send();
}
/** マスタ情報取得処理 */
function responseMst() {
    if (xhr.readyState === XMLHttpRequest.DONE) {
        if (xhr.status === 200) {
            var res = JSON.parse(xhr.response);
           // セレクトボックス
           var selectBox = document.getElementById("categories");
           var selectBox1= document.getElementById("categoriesSide");
           var arr = [];
           for( var i=0; i < res.length; i++) {
               var jsn = res[i];
               arr[jsn.categoryId] = jsn.name;
           }
           // Opitonセット
           for( var i=0; i < arr.length; i++) {
               selectBox.options.add(new Option(arr[i], i));
           }
           for( var i=0; i < arr.length; i++) {
               selectBox1.options.add(new Option(arr[i], i));
           }
        } else {
            alert('リクエストに問題が発生しました');
        }
    }
}

/**
1. カテゴリのイメージファイルパスを取得
2. 想定外のものは""を返す
*/
function getCategoryImg(categoryId) {
    var imagePath = "";
    if ("0" === categoryId) {
        // 観光資源(その他)
        imagePath = "";
    } else if ("1" === categoryId) {
        // ライブハウス
        imagePath = "./mapImg/LiveHouse.png";
    } else if ("2" === categoryId) {
        // 音楽スタジオ
        imagePath = "./mapImg/musicStudio.png";
    } else if ("3" === categoryId) {
        // 音楽系カフェ
        imagePath = "";
    }
    return imagePath;
}

投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す