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に登録したデータを受信する〜

でわでわ。。。

Java Socket 〜極小サーバーで通信の基本を学ぶ〜

java.net.ServerSocket

このクラスを使用してサーバーを作成します。
サーバー側のコードは以下の通りになります。
ちなみに通信がまともに繋がるまで、テストした時の残骸が残っております。


ハマったところ

ここで自分が詰まったのはクライアントからのデータを受信するときに「BufferedReader#readLine()]」を使用していたところです。

原因は。。。

データを送信するときに改行がないと受信時にデータの終わりが確認できないので「readLine()」のところで処理が止まってしまうところです。
とりあえずはこちらを参照して実装しました。
実装してみたところ、サーバーとクライアントの送受信で互いに「読み取り」処理の時の区切りが悪かった。。。

int c = -;
while((c = reader.read()) != -1) {
   char ch = (char) c;
   stBuilder.append(ch);
}

こんな感じで実装していたのですが、読み取るデータがいつになっても「-1」にならない事件が起きてしまい。。。
結局は下のようにコードを直しました。

int c = -;
while((c = reader.read()) != -1) {
   char ch = (char) c;
   stBuilder.append(ch);
   if(c == 10 || c == 13) {
       break;
   }
}

c == 10, 13の部分はchar型のint値です。10はLF(改行文字)で13はCR('改行文字)のようです。参考サイト
ソースコードはGitにアップしています。SocketServerBasic.java, ClientSocketBasic.javaをアップしてあります。

とりあえず完了

いやー、read()処理の部分でハマりました(笑)
昔はreadLine()とかで問題なく動いたのですが、ちょっと変わっているようです。今回の実装はJDK1.8での実装になります。
でわでわ。。。



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

イントロダクション

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

設計と思想

今回の実装は、PHPを使用するけどサーバーサイドとクライアントサイドを分断して実装しようと思いましたので以下のようなファイル構成で実装しています。
<クライアント>

  • SampleMap.html
  • SampleMap.js

<サーバー>

  • InsertMap.php
  • GetMapInfo.php

XmlHttpRequest

ここで使用するのはXMLHttpReuqstを使用して実装します。
リクエストのタイプは2つあります。

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

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

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

しかし

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

怪しいところ

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

enctype="multipart/form-data"

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



Google Maps API PHP連携 〜マップ情報をDBに登録する〜

Html -> DB

Google Maps APIを使用して地図を表示する画面の作成を行いました。

こんな感じです。

PHP連携

この画面の下部分にデータを登録するための入力部分があります。ここの入力をサーバー(PHP)へ送信してDBに登録します。
クライアントサイド(HTML)は作成したので次はリクエストを送信したときにデータを受け取りDBに登録する処理(PHP)を作成します。
ちょいメモ
実は画面からリクエストを送信する部分(ボタン)ですがまだ作ってません。。。

参考

参考にするサイトはこちらです。PHPマニュアルです。。。

HTML →PHP

HTML からの入力データをサーバ(PHP)に送信します。今回はHTMLとPHPを分断して実装するのでHTMLで初期表示処理の中で、DB からデータを取得するPHPを呼び出しますが、まずはデータ登録が先なので表示順とはいきませんが、この順序で実装していきます。

  1. 画面にGoogle Mapを表示する
  2. 初期表示処理は空、もしくはマップの初期処理のみを実装
  3. データ入力ボタンを押すと入力フォームが開く
  4. フォームにデータを入力するとサーバのPHPにデータを渡す(POSTリクエスト送信)
  5. PHPで入力(テキスト)とアップロードしたファイルを受け取る
  6. 取得したファイルにエラーがあるかチェック
  7. 同じく、ファイル拡張子のチェック
  8. ファイルをバイナリ(BLOB)でDB に登録

ここまでを実装しようとます。



JS Google Maps API 〜GeoLocation 現在位置の取得〜

Google Maps API

現在位置の取得処理でつまづいたのでメモです。
GoogleMapを作成して、現在位置の取得メソッド(監視メソッド)を使用するときの実装サンプルです。

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
    });
    console.log("test");
}

しかし、これだと以下のようなエラーメッセージが出ました。
this.setValues is not a function
「なんでぢゃ〜」と叫びはしませんでしたが、結構手間取りました。そして[こちらのページ](https://stackoverflow.com/questions/36598503/error-this-setvalues-is-not-a-function-in-js-code-use-google-map-api)を参照すると
newが抜けてるよ」というアドバイスがあり。。。
同じようにして修正したら直しました。
そしてついでなので、このままオプションの変更をしない状態だと、毎度おなじみのオーバーレイが表示されるので、そいつを別のものに変更したいと思い以下のようにオプションを設定してやるとできました。

// マーカーの追加
var current = new google.maps.Marker({
    map: map,
    position: mapLatLng,
    icon: './mapImg/you.png' // ファイルの指定
});

![自作アイコンの追加](https://pbs.twimg.com/media/D_bqqRaU8AIAOit.jpg:large)
でわでわ。。。



PHP Ajax 〜DBに登録したデータを受信する〜

イントロダクション

前回は、PDOを使用してMySQL(DB)に画面から入力したデータを登録しました。
今回は、登録したデータを画面に表示する処理を実装しようと思います。

処理イメージ

<画面>

<サーバー>

こんなイメージです。サーバー側の処理としては登録した地図情報を全部リロード(再描画)する方法と追加した情報のみを描画する方法の2種類ありますが、起動してみた結果で判断するか。。。
ちょっと悩みどころです。

地図をリロードする方法

これは、単純に地図データをリフレッシュするので綺麗に再描画されます。その代わりデータが増えると重くなるかもしれないです。
<対策>
逆に初期表示をちゃんとメモリも考慮した形で実装すれば問題ありません、

追加した情報のみ再描画する方法

これは、更新する部分が少なくパフォーマンスも良いのですが、表示済みの地図情報間でのリンクなど関連する情報があるときに問題が発生します。
<対策>
表示する地図データに関してそれぞれを独立させつつも、連携取るための仕組みをきちんと考える。




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 . '
' . 'img src="data:image/jpg;base64,' . $row['INFO_IMAGE'] . '" alt="写真"' . '/div'; } // コネクションの解放 $pdo = null; } catch(Exception $e) { print($e->getTraceAsString()); } // レスポンスに出力 print($tags);

でわでわ。。。



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());
}

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

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

でわでわ。。。



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でやることを整理〜