JS GoogleMap 〜セレクトボックスの中身をDBから持ってくる〜

Ajaxを使用して、DB(カテゴリマスタ)からデータを取得します。使用する技術はPHPJavaScriptです。

ファイル構成

・画面表示部分(Sample.html)プレーンのHTMLファイルです。
・GetCategoryMst.php HTMLを出力しないプレーンのPHPファイルです。

アプリ全体像

小さなアプリケーションなので、簡単です。
<クライアントサイド(ブラウザ表示側)>
Sample.html: GoogleMapを表示するHTML
Sample.js : GoogleMapやAjax通信を行う処理を実装しています。

<サーバーサイド(DBからデータを取得するなどPHP)>
GetMapInfo.php: 初期表示時にオーバーレイの情報を取得します。
InsertMapInfo.php: HTMLから送信された情報をDBに登録します。
GetCategoryMst.php: これから作成するPHPです、

実装内容

処理の流れとしては、以下のようになります。

  1. HTML画面側でセレクトボックスをクリックする
  2. DBからカテゴリ情報を取得する
  3. 取得したデータをセレクトボックスにセットする

上記のような形で実装します。
まぁちょちょっといじるくらいなので、簡単に行けるであろう。。。というところです。
「〜であろう」というのは、もしかしたら想定外の問題が出てくるかもしれないので、そのように記載しました。

実装開始

既存のソースを眺めます。全部だとえらいことになってしまうので、一部抜粋して記載します。

/** 画面の初期化 */
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;
}

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

抜き出したコードは初期表示時に、画面の設定を行う部分の処理になります。

実装にあたり

今回の改修ポイントは、セレクトボックスを追加したので、以下の処理を行うように修正するというところです。

セレクトボックスをクリック(変更)したときにセレクトボックスに値が入っていなければ、DBからカテゴリ情報を取得する

というわけで、修正ポイントは「セレクトボックスをクリック(値が変わったとき)」になりますので、イベントハンドラの「OnChange」を使用します。

HTML修正

下のコードをselectタグに追加します。
&ltoption value="-" selected="selecred">--未選択--</option>

そしてJSの修正は、以前やったのでそちらを参照してください。

PHP

サーバーサイドのPHPを作成します。
やることは以下の通りです。

  1. DBからカテゴリマスタデータを取得する
  2. データをJSON形式で返却する

実装部分は以下の通り

  1. もともとあったPHPをコピー
  2. SQLの山椒テーブルを変更
  3. 取得する項目名を変更

シンプルなものです。

$driver_options = [
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
];

$selectQuery = "SELECT * FROM CATEGORY_MST";
$tags = "";

$cnt = 0;
try {
    $pdo = new PDO($dns, $username, $password, $driver_options);
    foreach ($pdo->query($selectQuery) as $row) {
        $data .= '{"categoryId" :"' . $row['CATEGORY_NAME   '] . '",';
        $data .= '"name": "' . $row['INFO_CATEGORY'] . '",';
        $json .= $data;
$cnt +=1;
    }
    if ($cnt != 0) {
        // 最後の","を削除する
        $json = substr($json, 0, -1);
        $json .= ']';
    } else {
        $json = "";
   }
    // コネクションの解放
    $pdo = null;
} catch(Exception $e) {
    print($e->getTraceAsString());
}
// レスポンスに出力
//print($cnt);
header("Content-Type: application/json; charset=UTF-8");
echo json_encode($json);

?>

目星はついています。下のコード部分を修正して、Ajax通信により上記のPHPを読んでやれば、おっけ!

修正対象メソッド

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

でわでわ。。。



Ajax + XmlHttpRequest〜画像送信からDB登録して表示〜

イントロダクション

今まで数回にわたり、これに関することを記載しました。以下の記事がそれにあたります。

  1. JS XmlHttpRequest 〜JSでの同期/非同期通信〜
  2. Google Maps API PHP連携 〜マップ情報をDBに登録する〜
  3. PHP Ajax 〜DBに登録したデータを受信する〜

部分的な記載なので詳細部分は上記の記事がわかりやすいと思います。

Ajax + XmlHttpRequest

今回作成しているウェブアプリケーションはGoogle Mapを使用して地域の情報を位置情報を土台にして発信できるものを作成中です。
現状は、まだわけのわからない情報が載っています。こんな感じです(笑)。

PCとスマホなどのモバイルで同じような表示になっています。CSSでのコントロールは行なっていません。→@mediaなどのタグを使用していません。

なぜAjax?

今回の実装では使用しているファイルは以下のようになります。
<クライアント>

  • SampleMap.html
  • SampleMap.js
    <サーバー>
  • InsertMapData.php(だったと思う。。。)
  • GetMapData.php(と思う。。。)

これらの構成で今後はphpファイルが増えていくような形になります。(1機能、1ファイル)

ポイント

HTML, JS(クライアント)とphp(サーバー)を分断して実装したいと思い、このような形になりました。
サーバーとクライアントの通信部分にはAjaxを使用しています。

つまり

「HTML & JS」

  • HTMLの初期表示時にAjaxでphpを呼び出しGoogleMapから地域情報(オーバーレイ)を取得

「PHP(サーバー)」

  • データ入力とデータ送信のボタンで地域情報をサーバーに登録(PHP呼び出し)を行う

このように用途を分割して実装しました。

MVCモデル

余談ですが、自分はJava屋なのでMVCモデルのような実装の仕方が好きです。「あの機能はあのファイル」「この機能はこのファイル」というように処理を分割してやると、修正や追加の実装をするときに「どの部分にコードをついか・修正すれば良いか?」がすぐにわかります。「自分で作ったんならわかるだろ?」という声が聞こえそうですが、それは多分やったことのない人の意見だと思います。
もし、若い人であれば、是非やってみてください多分「んーどこを修正すれば良いのだろう?」となります。そして、この経験をした人でないと「こーやれば良い」というのがわからないと思います。
それなりに経験を積んでいる人であれば、若い子がやって「んー。。。」と声を上げているときに、何か一言声をかけてあげてください。若くても、老人でも「新しきを知る」というのは必要だし。面白いことだと思います。

処理フロー

本当のMVCモデルでは以下のような関係を作りアプリケーションの設計を行います。

  1. モデル(M) = ロジック処理部分(DB登録など)
  2. ビュー(V) = 画面、クライアント側
  3. コントロール(C) = リクエストハンドラ

このような形で設計します。JavaのSpring, Play(リンクはちょっと古い)、JavaEE etc ... などのフレームワークはStrutsという古いフレームワークを基本にしているように見えます。(実際のところは自分の予想です)
それというのはリクエストハンドルを行う中心的なサーブレット(フレームワーク部品)を使用して各URLに対してどのアクション(Control)を起動するのか決定しているからです。

ハンドリング方法

「http(s)://XXXX.com/XXX 」というリクエストが来たときXXXActionというControlクラスのXXXメソッドが起動する、というような形をとっています。

MVCの流れ

画面(V) → コントロール(C) → モデル(M)→ コントロール(C) → 画面(V)と流れていくように設計します。
VからはHTMLでフォームのaction属性を使用する方法、Ajaxで直接処理を呼び出す方法とありますが、そこはアプリケーションの設計次第になります。

同期通信と非同期通信

Ajaxという技術が確立されて久しいですが、昔は「同期通信」、つまり、フォームのaction属性を使用する同期通信にて画面の更新を行なっていました。この場合は画面全体をリロードするので必要な場合のみこのような通信方法を使用したいと思うのが人情だと思います。
しかしAjaxの技術を使用すれば更新したい部分のみを更新できるので、この方法を使用することが多いです。
よく目にするのは「JQuery」を使用した「$.ajax()」です。これはXmlHttpRequestを使用したもので、自分の認識としては単なるラッパー(拡張して使いやすくしたもの)だと思っています。しかし便利になればその分よくない部分も出るので。。。まぁ好みの問題でもありますが。。。とにかく自分はシンプルにXmlHttpRequestを使用した実装を行いました。
具体的には、HTMLでid="jax"とつけた領域のみを更新するなどです。中身としては「非同期通信はレスポンスを待たない」のでリクエストを送信してこのレスポンスが帰って来たときにXXXの処理をするというような形で実装するので「部分的に画面を更新できる」というわけです。
この部分は下の記事を参照願います。
JS XmlHttpRequest 〜JSでの同期/非同期通信〜



今回の実装では

MVCの「C」がない状態です。HTMLとPHPだけで処理を行い、画面の繊維を行わないのでControl部分がいりません。1画面で事を足らします。
というかほとんどをGoogleMapsAPIに依存する形で実装するのでサーバーサイドの処理はDBからのデータの取得、更新のみで良いのです。

MVモデル?

とってつけたようにいろんな言葉が横行していますが、それも時代でしょう。なので「MVモデル」という言葉はテキトーな事を記載しました。。。(冗談)
しかし、実装の方はHTML(V)とDB更新・データ取得(M)のみなのでこのような言い回しになりました。詳細に関しては以下のリンクを参照していただけるとありがたいです。

  1. Google Maps API PHP連携 〜マップ情報をDBに登録する〜
  2. PHP Ajax 〜DBに登録したデータを受信する〜

でわでわ。。。

PHP PDO 〜MySQLにアクセスする〜

イントロダクション

現在、GoogleMapを使用したブラウザアプリの作成中です(2019-07-11)。
実装中につまづいたのでメモがてらに記載します。
<作成プログラム概要>

  • JSでのGoogleMap表示
  • PHPでの入力データをDBに登録

こんな感じです。

PDO to MySql

確認するポイント

  • phpinfo()でPDOの使用が可能か確認

そして確認ができたらMySQLでのPDOのインストール方法(使用可能か確かめる)とDPDO使用方法を参照して使い方を理解する。

<作成したコード>

// DBアクセス
$dns = 'mysql:host=localhost;dbname=test_dbname';
$username = DB_USER;
$password = DB_PASS;
$driver_options = [
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
];
$insertQuery = "INSERT INTO AREA_INFO(AREA_ID, INFO_NAME, INFO_URL, INFO_IMAGE, INFO_LAT, INFO_LNG, OWNER_ID, UPDATE_DATE) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";

try {
    $pdo = new PDO($dns, $username, $password, $driver_options);
echo "tee";
    $pdo->prepare($insertQuery);
    $pdo->bind(1, 1);
    $pdo->bind(2, $name);
    $pdo->bind(3, $url);
    $pdo->bind(4, $lat);
    $pdo->bind(5, $lng);
    $pdo->bind(6, $imgTmp, PDO::PARAM_LOB);
    $pdo->bind(7, 1);

} catch(Exception $e) {
    print($e->getTraceAsString());
}

PreparedStatement

SQLを実行するときによく使うのがプリペアードステートメント(PreparedStatement)です。
早い話が、SQLの値部分(?)にパラメータを渡してやるものです。
Sample

  • INSERT INTO A_TBL(COL_A, COL_B) VALUES(?, ?);

というSQLクエリを実行(execute)しようとしたときに下のようにコードを書きました。

    $pdo->prepare($insertQuery);
    $pdo->bind(1, 1);
    $pdo->bind(2, $name);
    $pdo->bind(3, $url);
    $pdo->bind(4, $lat);
    $pdo->bind(5, $lng);
    $pdo->bind(6, $imgTmp, PDO::PARAM_LOB);
    $pdo->bind(7, 1);

エラリました。。。
マニュアルを見ると「bind」なんてメソッドはありませんでした。。。
なのでマニュアルに習い以下のように書き換えました。

try {
    $pdo = new PDO($dns, $username, $password, $driver_options);
echo "tee";
    $statement = $pdo->prepare($insertQuery);
    $statement->bindParam(":id", $id);
    $statement->bindParam(":name", $name);
    $statement->bindParam(":url", $url);
    $statement->bindParam(":img", $imgTmp, PDO::PARAM_LOB);
    $statement->bindParam(":lat", $lat);
    $statement->bindParam(":lng", $lng);
    $statement->bindParam(":owner", $ownerId);

    $statement->execute();

} catch(Exception $e) {
    print($e->getTraceAsString());
}

これでDBにデータの登録ができるようになりました。作成した画面はこちらです。

ちなみにSELECT文などは「query()」が使える。

try {
    $pdo = new PDO($dns, $username, $password, $driver_options);

    foreach ($pdo->query($selectQuery) as $row) {
        $tags = $tags . '<div visible="hidden" data-name="' . $row['NFO_NAME'] . '" '
                . 'data-url="' . $row['INFO_URL'] . '"'
                . 'data-url="' . $row['INFO_LAT'] . '"'
                . 'data-url="' . $row['INFO_LNG'] . '">'
                . 'img src="data:image/jpg;base64,' . $row['INFO_IMAGE'] . '" alt="写真"'
                . '/div';
    }
    // コネクションの解放
    $pdo = null;
} catch(Exception $e) {
    print($e->getTraceAsString());
}
// レスポンスに出力
print($tags);

でわでわ。。。

WordPress Pluginを作った時の動画です。



PHP PDO 〜invalid data source name〜

invalid data source name

表題のエラーが出てすごく困っていました。
下のようにコードを作成し最後のnew PDOの部分でエラーになるので「おかしい!」と悩んでいました。
参考サイトPHPマニュアル

$dns = 'mysql:host=localhost;dbname=testdb';
$username = 'user';
$password = 'pass';
$driver_options = [
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $username, $password, $driver_options);

解決

スペスミスでした(笑)

$dns = 'mysql:host=localhost;dbname=testdb';
$pdo = new PDO($dsn, $username, $password, $driver_options);

わかりづらいのですが、「$dns」と「$dsn」で間違っていたのでコンストラクタにから文字が渡されていました。。。

解決方法

以下のようにtry〜catchでエラーとレースを出力しました。

} catch(Exception $e) {
    print($e->getTraceAsString());
}

これでエラーの詳細が出力されるので一発で解決できました。

エラー出力、ハンドリングはちゃんとやりましょう(笑)

WordPressのテスト環境

PHPを使用しているのならWordPressを使用している人も多いと思います。PHP + JSってのは結構あるパターンだと思っていますが、テスト環境を作った方が、影響なく、遠慮なくバグを出せるので、気楽にコードの修正ができます。
ここでおすすめなのが、MAMPです、MySQL内臓のサーバーです。早い話が、レンタルサーバーからバックアップ用のXMLをダウンロードして、WordPressをサーバーにセットしてやれば、レンタルサーバーの環境が作れます。
こんな感じです。
MAMP 起動設定 〜WordPressのテスト環境を作る〜

でわでわ。。。

関連ページ(PHP)

  1. PHP PDO 〜MySQLにアクセスする〜
  2. PHP Ajax 〜DBに登録したデータを受信する〜
  3. Google Maps API PHP連携 〜マップ情報をDBに登録する〜
  4. PHP Image File 〜iPhoneやAndroidでの画像送受信の問題〜
  5. AngularJS Routing 〜PHPをWeb APIにする〜
  6. WordPress PHPカスタム〜根本的に見た目を変える〜
  7. WordPress PHPカスタム〜根本的に見た目を変える2〜
  8. Eclipse PHPプラグイン 〜ElipseでWordPress環境を構築〜
  9. WordPress テスト実装 〜heade-test.phpを表示〜
  10. AngularJS + PHP 〜WordPressと連携する〜
  11. AngularJS + PHP 〜AngularJSの実装〜
  12. AngularJS + PHP 〜AngularJSの実装2〜
  13. WordPress 処理解析 ~index.phpを眺める~


PHP Ajax 〜リクエストが取得できない〜

AjaxとPHP

XMLHttpRequestでPHPにリクエストを飛ばしたときにはまりました。
JSONでリクエストを送信したもののPHPでファイルデータが取得できない。。。バイト文字を送信するのも一つですが…

解決

参考サイト
取得するのにFile形式で取得するようだった。。。。

$post_body = file_get_contents('php://input');


ここにたどり着くのに結構かかってしまいました。。。
みなさんお気をつけて。。。

取得はできたけど、データの操作が出来ず…

結局はフォームデータにファイルをセットして送信しました。

注意としては、XmlHttpRquestのヘッダーに何も設定しない状態でリクエストを飛ばす事、これを見つけるのに苦労しました(笑)

次は、PHPで、入力データ(文字列とファイル)をMySqlに登録しようと思います。

既に、試したのですがPDOが使えないサーバがあるので、php Infoで確認した方が安全です。

でわでわ。。。



Ajax PHP 〜Ajax通信エラー No ‘Access-Control-Allow-Origin’ 〜

No 'Access-Control-Allow-Origin'

こんなエラーが出ました。JavaScriptで以下のようなコードを実行したときです。

    xhr = new XMLHttpRequest();
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = recieveResponse
   xhr.open('GET', 'https://zenryokuservice.com/tools/maps/InsertMapInfo.php?param=test', true);
    xhr.send();


SampleMap.html:1 Access to XMLHttpRequest at 'https://zenryokuservice.com/tools/maps/InsertMapInfo.php?param=test' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

答え

異なるドメインから呼び出すと、このようになるようです。
セキュリティの問題なようです。まぁ確かによろしくはないですね。。。参考サイトより

対応

実はこのコードは自分のPCから直接実行しました。運用するときは自分のレンタルサーバー上にJSを配置するのでこの問題は起きません。。。
なのでサーバーにアップして再度実行します。

こんな感じでAjax通信のテストを完了できました。

作成したものはここにあります。

https://zenryokuservice.com/sample/js/SampleMap.html

次は

作成したフォームを送信します。



関連ページ

  1. JS GoogleMaps API 〜オリジナル・データマップを作ろう〜
  2. Github page Github pageでリポジトリの情報を公開しよう〜
  3. 夢を形にする① 目標ブレーク〜じゃんけんゲームの場合〜
  4. プログラム 習得 順序1 概要
  5. Githubページ関連

  6. Github page Github pageでリポジトリの情報を公開しよう〜
  7. Github 使い方〜リポジトリにライセンスを設定する〜
  8. Github 使い方〜Issueでやることを整理〜