地域情報ページ作成方法の解説

地域情報ページ作成方法

地域情報ページの作成方法に関して、解説したいと思います。
このページは、以下の二つのファイルで構成されています。

  1. index.html: HTMLファイル。

  2. Gotochi.js: JavaScriptファイル(JSプログラムを書きます)。

  3. BootStrapのサンプルをダウンロード

作成方法に関しては以下の通りです。

  1. HTMLファイルは、BootStrap exampleからダウンロードしてきたファイルを修正する形で作成しました。

  2. JSファイルは一からすべて作成しました。

HTMLファイルに関しては、BootStrapを使用するため次の設定をしています。

  • ダウンロードしたBootStrapサンプルの読み込みの設定(linkタグ)。
  • 外部のJS(Masonry)の読み込みの削除(linkタグ)。
  • ツイッターカードの設定。

BootStrap以外の設定を変更しました。以下の手順を見ていけばご理解いただけると思います。

作成手順

  1. BootStrap exampleからサンプルをダウンロードしてきます。

  2. 解凍して「masonry」フォルダのindex.htmlファイルをコピーして
    適当な場所に配置します。※マイドキュメントフォルダなど。

  3. mansonryの読み込み部分を削除する。

  4. 中身のコンテンツboddyタグ内部を修正し、自分の意図する形に修正。
    ※この部分に関しては、BootStrapとHTMLの学習が必要になります。

  5. JSを起動するためのファンクションをbodyタグのonload属性に設定する。
    ※Gotochi.jsファイルを読み込むためのlinkタグを追加する

  6. JSプログラムからアクセスしたいタグにIDを付ける。

  7. 選択したときに取得したい情報(hokkaido)などをdata属性に設定する。

  8. 「5」で設定したファンクションの実装を行う。

  9. JSプログラムで読み込むデータ配列の作成。※後ほど説明します。

  10. 適当な都道府県を表示してみる。

  11. 複数の都道府県表示を行うため、データの追加作成を行う。

JSの実装に関して。

JSプログラム、JavaScriptプログラムに関して解説します。
JavaScript、略してJSは主にウェブブラウザでHTMLタグを取得し操作する事
を行います。しかし、これ以外にもNode.js, Vue.jsなどいろいろな
ライブラリがあり、それぞれに使い方があるのでそれを調べて学習する必要
がありますが、それは必要になったときに学習するということでよいと
思います。

どういうことか?といいますと、Node.jsにしろVue.jsにしろJavaScriptで
あることには変わりないので、JavaScriptがわかれば学習にかかる時間は
少なくて済むということです。

余談。

筆者は、それなりに年を取っているので、JavaScriptの基本がわかるレベル
にあります。なので、Node.jsなどもある程度、作成するものによりますが
よほどでない限り、ドキュメントを見ながらプログラムを作成することが
できます。ある程度熟達したプログラマーはほとんどこの領域にあるので
気軽に質問してよいと思います。

むしろ積極的に質問してほしいです。なぜなら「知っていることをしゃべる
のは楽しいから」です。

JSプログラムの内容に関して

今回作成したプログラムに関して解説いたします。プログラムの仕様として
以下のものがあります。※自分で決めた仕様になります。

  1. 都道府県を選択したときに、それに紐づく市町村を表示する。
  2. 市町村が選択されたときに、次の項目を表示する。
    ・各市町村に関するYoutube動画の表示。
    ・対象の市町村がどの地域・地方に存在するか表示。
    ・各市町村のホームページ。
    ・各市町村の情報があるツイッター。
    ・楽天APIを使用し「XX市町村のふるさと納税」で取得できる商品一覧。

仕様の実現方法。

「1.都道府県を選択したときに、それに紐づく市町村を表示する」に関して。

各都道府県を選択するためのセレクトボックスを作成し、47都道府県を
OPTIONタグでセットする。具体的にはOPTIONタグの表示部分に都道府県名、
data属性に各都道府県のキー(hokkaidoなど)をセットして各項目を選択し
たときに、キーをJSプログラムで取得できるようにする。
そして、キーは、データ配列の大文字(UpperCase)で以下のように定義する。
「大文字の各都道府県キー = [市町村名, 市町村のホームページURL
, ユーチューブ埋め込みURL, Twitter埋め込みHTMLコード, facebookURL
, 地域のイメージファイルURL, 対象になる地域名(よみ)];」

プログラムの内容に関して。

・HTMLファイルに読み込むファンクションを設定。
bodyタグのonload属性で「selectLv1()」というファンクションを起動する
ように設定する。
これにより、HTMLファイルが表示されたときに「selectLv1()」が起動する。

・JSプログラム。
都道府県セレクトボックスと市町村セレクトボックスにイベントリスナー(
ファンクション)を登録する、これによりセレクトボックスの値を選択した
ときに起動する処理を定義することができる。

具体的なコード

画面表示時にonloadで実行するファンクション「selectLv1()」で処理を行っています。
行っていることは、都道府県、市町村のセレクトボックスの変更時に起動するファンクションを登録しているだけです。
まずは、コード全体をみてから、細かい部分を解説します。

コード全体

function selectLv1() {
    // 市町村のイベントリスナー登録
    let citySelectBox = document.getElementById('shityoson');
    citySelectBox.addEventListener('change', (event) => {
      // データ取得
      let cityObj = event.srcElement[event.srcElement.selectedIndex];
      let cityUrl = cityObj.value;
      let cityName = cityObj.text;
      // 表示する市町村の値セット
      document.getElementById('targetCity').innerHTML = cityName; // 表示する市町村
      let cityPage = document.getElementById('targetCityHomePage');
      cityPage.src = '';
      cityPage.innerHTML = cityName + ' <a href="' + cityUrl + '"  target="_blank">ホームページを開く</a> :  <span> ';// ホームページリンク
      // まちのよみがな
      document.getElementById('yomi').innerText = cityObj.dataset.yomi;
      // 地方のイメージ
      document.getElementById('tiho').src = cityObj.dataset.tiho;
      // 地方の読み仮名
      document.getElementById('tihoYomi').innerHTML = '<a href="' + cityObj.dataset.tiho + '"><b>' + cityObj.dataset.tihoYomi + ' ちほう</b></a>※リンク先に大きい地図';

      // FaceBookのリンク
      cityPage.innerHTML += cityObj.dataset.facebook == '' ? "<font size='3'>FaceBookリンク</font>" : "<a href='" + cityObj.dataset.facebook + "'><font size=\'3'>FaceBookへのリンク</font></a></span>";
      // ふるさと納税対象市町村
      document.getElementById('hurusatoNozei').innerText = cityName + "のふるさと納税";
      let hurusato = document.getElementById('hurusatoNozeiPage');
      hurusato.src = '';
      hurusato.src =  "https://zenryokuservice.com/project/rakuten/php/rakutenCatalog.php?category=" + cityName + "ふるさと納税";
      // 画面のリロード処理が走ってくれるようだ。。。
      hurusato.onload = function() {
//        let hurusatoDoc = this.contentWindow;
//        hurusatoDoc.getElementById('category').value = cityName + "ふるさと納税"
//        hurusatoDoc.getElementById('reloadButton').onClick();
      };

      // Youtubeの表示
      document.querySelector('#targetUrl').childNodes[0].src = cityUrl;// iframeで表示するURL
      document.getElementById('videoComment').innerText = cityObj.dataset.you == '' ? "動画の選別中です" : cityName + "の動画です";
      document.getElementById('youtubeVideo').src = cityObj.dataset.you == '' ? "https://www.youtube.com/embed/dZMAphYDiYo" : cityObj.dataset.you;

      // Tweetの埋め込み
      let tweetTitle = document.getElementById('tweetTitle').innerHTML = cityObj.dataset.tweet == '' ? "Twitter表示の工事中です" : cityObj.dataset.tweet;
      let tweetUrl = document.createElement('script');
      tweetUrl.src = 'https://platform.twitter.com/widgets.js';
      tweetUrl.onload = function(e) { e.target.src };
      let tweetEle = document.getElementById('tweetEmbbed');
      tweetEle.appendChild(tweetUrl);
    });

    // 都道府県のSELECTボックスの取得
    let selectbox = document.getElementById("todohukenList");

    // 初回はイベントリスナーを起動するのでイベント生成
    const eve = new Event('change');

    // 都道府県のイベントリスナーの登録
    selectbox.addEventListener('change', (event) => {
      // 選択した都道府県をタイトルにセット
      let todohuObj = event.srcElement[event.srcElement.selectedIndex];
      let todohuIdx = todohuObj.dataset.todohu;
      let totohuTItle = document.getElementById("targetTodohu");
      // シンボルイメージセット
      let img = document.getElementById('todohuFlag');
      img.src = KENKI[todohuIdx];
      // 都道府県名のセット
      totohuTItle.innerHTML = todohuObj.text;

      // セレクトボックスを初期化する
      let shityoson = document.getElementById("shityoson");
      if (shityoson.hasChildNodes()) {
        while(shityoson.childNodes.length > 0) {
            shityoson.removeChild(shityoson.firstChild);
        }
      }
      // 指定都道府県の市町村をセットする(YoutubeUrlも取得)
      let targetSityosons = TODOHU[todohuIdx];
      for (let i = 0; i < targetSityosons.length; i++) {
        let option = document.createElement("option");
        option.text = targetSityosons[i][0];
        option.value = targetSityosons[i][1];
        // YoutubeUrlを格納
        option.dataset.you = targetSityosons[i][2] == '' ? '' : targetSityosons[i][2];
        // ツイッター埋め込み
        option.dataset.tweet = targetSityosons[i][3] == '' ? "ツイートの検索中" : targetSityosons[i][3];
        // FacebookURLの埋め込み
        option.dataset.facebook = targetSityosons[i][4] == '' ? '' : targetSityosons[i][4];
        // まちのよみがな
        option.dataset.yomi= targetSityosons[i][5] == '' ? '' : targetSityosons[i][5];
        // 地方の画像
        option.dataset.tiho = targetSityosons[i][6] == '' ? '' : targetSityosons[i][6];
        // 地方の読み
        option.dataset.tihoYomi = targetSityosons[i][7] == '' ? '' : targetSityosons[i][7];

        // セレクトボックスに追加
        shityoson.appendChild(option);
      }
      // 市町村の初回イベント処理
      const even = new Event('change');
      citySelectBox.dispatchEvent(even);
    });

    // 初回のイベント処理実行
    selectbox.dispatchEvent(eve);
}

市町村のイベント処理ファンクション

  1. 市町村のセレクトボックスを取得する

    let citySelectBox = document.getElementById('shityoson');
  2. 市町村セレクトボックスにイベント処理を登録する、変更時のイベント「change」を指定して登録する

    citySelectBox.addEventListener('change', (event) => {
    });
  3. 市町村の変更時の処理
    セレクトボックスの選択した項目、OPTIONタグを取得する

      // データ取得
      let cityObj = event.srcElement[event.srcElement.selectedIndex];

    OPTIONタグから設定している値を取得する(URL, data属性の値)

      let cityUrl = cityObj.value;
      let cityName = cityObj.text;

    HTMLでID「targetCity」としているタグに市町村名をセット

      // 表示する市町村の値セット
      document.getElementById('targetCity').innerHTML = cityName; // 表示する市町村

    「targetCityHomePage」としているタグを取得する

      let cityPage = document.getElementById('targetCityHomePage');

    このタグにリンクを設定、同様に読み仮名などを設定する、ふるさと納税に関しては依然作成したPHPファイルにパラメータを渡して起動する

      cityPage.src = '';
      cityPage.innerHTML = cityName + ' <a href="' + cityUrl + '"  target="_blank">ホームページを開く</a> :  <span> ';// ホームページリンク
      // まちのよみがな
      document.getElementById('yomi').innerText = cityObj.dataset.yomi;
      // 地方のイメージ
      document.getElementById('tiho').src = cityObj.dataset.tiho;
      // 地方の読み仮名
      document.getElementById('tihoYomi').innerHTML = '<a href="' + cityObj.dataset.tiho + '"><b>' + cityObj.dataset.tihoYomi + ' ちほう</b></a>※リンク先に大きい地図';
      // FaceBookのリンク
      cityPage.innerHTML += cityObj.dataset.facebook == '' ? "<font size='3'>FaceBookリンク</font>" : "<a href='" + cityObj.dataset.facebook + "'><font size=\'3'>FaceBookへのリンク</font></a></span>";
      // ふるさと納税対象市町村
      document.getElementById('hurusatoNozei').innerText = cityName + "のふるさと納税";
      let hurusato = document.getElementById('hurusatoNozeiPage');
      hurusato.src = '';
      hurusato.src =  "https://zenryokuservice.com/project/rakuten/php/rakutenCatalog.php?category=" + cityName + "ふるさと納税";
      // 画面のリロード処理が走ってくれるようだ。。。
      hurusato.onload = function() {
    //        let hurusatoDoc = this.contentWindow;
    //        hurusatoDoc.getElementById('category').value = cityName + "ふるさと納税"
    //        hurusatoDoc.getElementById('reloadButton').onClick();
      };
    
      // Youtubeの表示
      document.querySelector('#targetUrl').childNodes[0].src = cityUrl;// iframeで表示するURL
      document.getElementById('videoComment').innerText = cityObj.dataset.you == '' ? "動画の選別中です" : cityName + "の動画です";
      document.getElementById('youtubeVideo').src = cityObj.dataset.you == '' ? "https://www.youtube.com/embed/dZMAphYDiYo" : cityObj.dataset.you;
    
      // Tweetの埋め込み
      let tweetTitle = document.getElementById('tweetTitle').innerHTML = cityObj.dataset.tweet == '' ? "Twitter表示の工事中です" : cityObj.dataset.tweet;
      let tweetUrl = document.createElement('script');
      tweetUrl.src = 'https://platform.twitter.com/widgets.js';
      tweetUrl.onload = function(e) { e.target.src };
      let tweetEle = document.getElementById('tweetEmbbed');
      tweetEle.appendChild(tweetUrl);

    都道府県のイベント処理ファンクション

    市町村のイベントとほぼ変わらないが、都道府県が選択された後に、市町村のセレクトボックスの値をセットするようにしている。

    // 都道府県のイベントリスナーの登録
    selectbox.addEventListener('change', (event) => {
      // 選択した都道府県をタイトルにセット
      let todohuObj = event.srcElement[event.srcElement.selectedIndex];
      let todohuIdx = todohuObj.dataset.todohu;
      let totohuTItle = document.getElementById("targetTodohu");
      // シンボルイメージセット
      let img = document.getElementById('todohuFlag');
      img.src = KENKI[todohuIdx];
      // 都道府県名のセット
      totohuTItle.innerHTML = todohuObj.text;
    
      // セレクトボックスを初期化する
      let shityoson = document.getElementById("shityoson");
      if (shityoson.hasChildNodes()) {
        while(shityoson.childNodes.length > 0) {
            shityoson.removeChild(shityoson.firstChild);
        }
      }
      // 指定都道府県の市町村をセットする(YoutubeUrlも取得)
      let targetSityosons = TODOHU[todohuIdx];
      for (let i = 0; i < targetSityosons.length; i++) {
        let option = document.createElement("option");
        option.text = targetSityosons[i][0];
        option.value = targetSityosons[i][1];
        // YoutubeUrlを格納
        option.dataset.you = targetSityosons[i][2] == '' ? '' : targetSityosons[i][2];
        // ツイッター埋め込み
        option.dataset.tweet = targetSityosons[i][3] == '' ? "ツイートの検索中" : targetSityosons[i][3];
        // FacebookURLの埋め込み
        option.dataset.facebook = targetSityosons[i][4] == '' ? '' : targetSityosons[i][4];
        // まちのよみがな
        option.dataset.yomi= targetSityosons[i][5] == '' ? '' : targetSityosons[i][5];
        // 地方の画像
        option.dataset.tiho = targetSityosons[i][6] == '' ? '' : targetSityosons[i][6];
        // 地方の読み
        option.dataset.tihoYomi = targetSityosons[i][7] == '' ? '' : targetSityosons[i][7];
    
        // セレクトボックスに追加
        shityoson.appendChild(option);
      }
      // 市町村の初回イベント処理
      const even = new Event('change');
      citySelectBox.dispatchEvent(even);
    });

Javaでも学習したが、3項演算子が使っていますので、補足です。
<書式>

論理式 ? TRUEの場合 : FALSEの場合;

サンプル

let ans = 1 < 10 ? "aaa" : "bbb";

上記の変数「ans」には"aaa"が入る。

JS はてなブログ 〜サイドバーに動的処理を入れる〜

イントロダクション

現在(2019-08-31)自分は「はてなブログ」では、「計画やプログラムの理論部分など」どちらかというと高レベルな部分(人間に近い部分)を記載しています。リンク先は「テキストRPGの仕様作成」について記載しています。

しかし、基本はプログラムのことを記載しているので、やはり何かしらのプログラムを入れたいと思っているのですが、問題が。。。。

JSをどこに書く?

やりたいことは、はてなブログの、サイドバーに、IFRAMEで他のページを読み込んで表示しています。(PHPでの楽天APIの実行結果)実際左のリンクにパラメータを与えて表示内容を切り替えています。。。

しかし、現状はパラメータが固定なので(ハードコーディングなので)折角のAPIが意味なし状態になっています。

なので、今回はJSで動的に切り替えられるように改修を加えようと考えています。

問題1

初めのこの問題で全て解決しそうですが、JSをどこに書き起動させるか?
これを解決する必要があります。

先ほど、はてなブログにJSを記載してみたのですが、window.onloadのイベントハンドラで処理を行うと、サイドバーを読み込む前に処理が走ってしまうので初期値を設定したいDOM(HTMLタグ(今回はIFRAME)))を取得できません。

解決

はてなブログで、「onload」イベントを使用すると、コンテンツ(ブログのサイドバーなどの記事内容)が読み込まれる前に処理が走るので、DOM(HTMLタグ)の取得ができません。

なので、イベント処理を行うタイミングを変更します。起動するタイミングは「」です。コードに書くと下のようになります。

document.addEventListener("DOMContentLoaded", function(event) {
    loadIframe();
});

function loadIframe() {
   var data = ["pc", "guitar", "健康", "java", "占い", "github", "blender"];

   var selected = Math.floor( Math.random()*(6-0)+0);

   var iframeDom = document.getElementById("rakutenAPI");
   iframeDom.src= "https://zenryokuservice.com/project/rakuten/php/rakutenCatalog.php?category=" + data[selected];
   iframeDom.style="height: 1200px; margin: auto;";
}

初めに、iframe.srcに渡すURLパラメータ用の配列を用意します。(var data)
次に、乱数で0-6までの整数を取得します。
Math.floor()は整数を取得するのに使用しています。Math.random()は小数点付きの値を返却するので、整数になるようにしています。
以下省略。。。

記載する部分は、詳細設定のheadタグ部分に記載しました。以下のような手順でやりました。

  1. サイドバーの編集で使用するタグにIDをつけて定義(HTMLで記述)
  2. headタグの部分にJSを記載し「DOMContentLoaded」のイベントハンドラを設定してやります。

実行結果はこちらのページを見てくれれば。。。

でわでわ。。。



JavaScriptでユーザー認証 〜シンプルな認証処理〜

イントロダクション

最近作成している、GoogleMapを使用したページがある程度見れるものになってきたので、そろそろと公開できるような形にしたいと思い、ちょいとギミックを追加します。

パスワード認証

皆さんご存知「パスワード認証」を実装します。
これを実装すれば、「<入力>」「<送信>」などのボタン押下で、不特定多数の人に地図データを登録される心配がなくなります。

パスワード認証の実装

シンプルにボタンをクリックしたらパスワード入力ダイアログボックスが表示され、入力→認証という流れで処理を行います。
パスワードは見つけられないように、サーバーサイド(PHP)で持つことにします。つまり実装としては下のような感じになります。

認証準備

まずは、入力とデータの送信が出来ないようにボタンを非表示にします。
これはシンプルにHTMLでstyle="display: none"を設定してやります。具体的には以下のようなHTMLコードに変更します。
<Before>

        <button class="butt1" onclick="sideWinHandle(sideWin)"><入力>   </button>
        <button class="butt2" onclick="postData()"><送信>  </button>

<After>

        <button name="loggedIn" class="butt1" onclick="sideWinHandle(sideWin)" style="display: none;"><入力></button>
        <button name="loggedIn" class="butt2" onclick="postData()" style="display: none;"><送信></button>
        <button onclick="openLoginForm()"><Login> </button>

そして、ボタン押下時に走らせる処理onclick="openLoginForm()"を設定します。
これが出来たら、あとはJSの実装のみです。

ズバリ下のような実装を追加します。ちょっと特殊なのはprompt()です。

function openLoginForm() {
var input = prompt("Login Form");
    if ('パスワード' === input) {
        var loggedInButton = document.getElementsByName("loggedIn");
        loggedInButton.forEach(but => {
            but.style = "display: visible";
        });
    }
}

これで下のように動きます。

  1. 「Login」ボタンを押下
  2. プロンプトが開き、入力を求める
  3. 入力した値がJSのinput変数に代入される(上のコード参照)

シンプルに実装できていると思います。

問題が1つ

この実装だと、パスワードがバレバレで、セキュリティ的にアウトです。しかし、今回の実装はあくまでサンプルなのでまぁよしとします。

実際の使用方法

実際は入力して判定するときにクライアント側ではなく、サーバー側(PHPなど)で判定を行います。
今の自分の実装だと、入力した値をサーバーに送信(Ajax通信)して判定結果をもとに画面の表示・非表示を切り替えるなどを行うのが、安全なやり方だと思います。

ぢつは。。。

今の実装をしていて気がつきました。「ハナっからこーすればいいよな?」と思っております。初めの初期表示時に処理を行うことに執着していて気がつかなかった次第です。



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

D3.js ダイヤグラムを表示 〜サンプルを実行する〜

イントロダクション

前回までは「Treant.js」を使用してダイヤグラムを作成していました。駄菓子菓子、イベントハンドルができない(クリックしたときの処理を追加できない)ので諦めてD3.jsを使用することにしました。ブラウザで起動するアプリにしたいのでJSからは逃げられないのでした。。。

D3.js

上記のリンクにあるサンプルを参考にD3.jsを使用します。そして、クリックイベントにも対応している様なのでそちらの心配もしなくて良いです。(ここが肝心)

使用したことのないライブラリを使用するときは意図する実装ができるかどうかを確認してから使用すると自分の様に無駄な時間を使わなくて良いと思います。

ちなみに、初心者は大いに無駄な時間も使ってください(笑)。JSとはいえプログラムであることには変わりないので、処理(コード)をどんどん深掘りして読んだり、実行したりしていくと「これならいける!」とか「ダメだこりゃ。。。」などの様な「結論」にたどり着きます。これを何度か経験しておくと後々役にたつと思います。

上記のサンプルを実行すると下の様に表示されました。コピペです。

サンプルの画像と多少違うけど。。。まぁ放っておきます。

そして、サンプルコードを見てみます。

今回はJSとHTMLを分けてやりたいのでコピッたソースを下の様に分けました。

<HTML>

<body onload="treeMain()"> ... </body>

<JS> Gitにアップしてあります

こぴったコードをtreeMain()メソッドで囲みました。変数宣言のみの部分はメソッドの外にあります。

これをカスタムして、目標ブレークツリーを作成しようと思います。

https://zenryokuservice.github.io/GoalAchievement/

でわでわ。。。