質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.64%

JavaScript/jQueryでローカルにあるjsonファイルの中身を配列として取得できない

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,136

kisuke-09

score 13

 前提・実現したいこと

お世話になっております。
現在、HTML/JavaScript/CSSを用いて、4択式の問題アプリを作成しております。
参考として以下のサイトのソースコードを使用しています。
参考サイト
上記参考サイトではconst myQuestions = [];の中にハードコーディングで
問題文を記述しておりますが、これをJSON形式で別だしにしたいと考えています。
JavaScriptは現在勉強中であり、皆様の知恵をお借りしたく、ここに投稿させていただきます。
何卒よろしくお願い申し上げます。

 発生している問題・エラーメッセージ

現在、以下のエラーメッセージが出ています。

Uncaught TypeError: Cannot read property 'classList' of undefined

 該当のソースコード

(function() {
  var myQuestions = [];
  function jsonLoader(){
    $.ajax({
      type: 'GET',
      url: 'ローカルにあるjsonファイルのフルパス',
      dataType: 'json',
    }).done(function(json, textStatus, jqXHR) {
        jsonArray = JSON.parse(json);
        for(var i = 0; i < jsonArray.length; i++){
          myQuestions.push({
            'question': jsonArray[i].question,
            'answers': jsonArray[i].answers,
            'correctAnswer': jsonArray[i].correctAnswer
          });
        };
      console.log(myQuestions);
     }).fail(function(jqXHR, textStatus, errorThrown){
        alert(errorThrown);
     });
  }


  function buildQuiz() {
    jsonLoader()
    const output = [];
    console.log(myQuestions);
    // for each question...
    myQuestions.forEach((currentQuestion, questionNumber) => {
      // we'll want to store the list of answer choices
      const answers = [];

      // and for each available answer...
      for (letter in currentQuestion.answers) {
        // ...add an HTML radio button
        answers.push(
          `<label>
             <input type="radio" name="question${questionNumber}" value="${letter}">
              ${letter} :
              ${currentQuestion.answers[letter]}
           </label>`
        );
      }

      // add this question and its answers to the output
      output.push(
        `<div class="slide">
           <div class="question"> ${currentQuestion.question} </div>
           <div class="answers"> ${answers.join("")} </div>
         </div>`
      );
    });

    // finally combine our output list into one string of HTML and put it on the page
    quizContainer.innerHTML = output.join("");
  }

  function showResults() {
    // gather answer containers from our quiz
    const answerContainers = quizContainer.querySelectorAll(".answers");

    // keep track of user's answers
    let numCorrect = 0;

    // for each question...
    myQuestions.forEach((currentQuestion, questionNumber) => {
      // find selected answer
      const answerContainer = answerContainers[questionNumber];
      const selector = `input[name=question${questionNumber}]:checked`;
      const userAnswer = (answerContainer.querySelector(selector) || {}).value;

      // if answer is correct
      if (userAnswer === currentQuestion.correctAnswer) {
        // add to the number of correct answers
        numCorrect++;

        // color the answers green
        answerContainers[questionNumber].style.color = "lightgreen";
      } else {
        // if answer is wrong or blank
        // color the answers red
        answerContainers[questionNumber].style.color = "red";
      }
    });

    // show number of correct answers out of total
    resultsContainer.innerHTML = `${numCorrect} out of ${myQuestions.length}`;
  }

  function showSlide(n) {
    slides[currentSlide].classList.remove("active-slide");
    slides[n].classList.add("active-slide");
    currentSlide = n;

    if (currentSlide === 0) {
      previousButton.style.display = "none";
    } else {
      previousButton.style.display = "inline-block";
    }

    if (currentSlide === slides.length - 1) {
      nextButton.style.display = "none";
      submitButton.style.display = "inline-block";
    } else {
      nextButton.style.display = "inline-block";
      submitButton.style.display = "none";
    }
  }

  function showNextSlide() {
    showSlide(currentSlide + 1);
  }

  function showPreviousSlide() {
    showSlide(currentSlide - 1);
  }

  const quizContainer = document.getElementById("quiz");
  const resultsContainer = document.getElementById("results");
  const submitButton = document.getElementById("submit");

  // display quiz right away
  buildQuiz();

  const previousButton = document.getElementById("previous");
  const nextButton = document.getElementById("next");
  const slides = document.querySelectorAll(".slide");
  let currentSlide = 0;

  showSlide(0);

  // on submit, show results
  submitButton.addEventListener("click", showResults);
  previousButton.addEventListener("click", showPreviousSlide);
  nextButton.addEventListener("click", showNextSlide);
})();
[
    {
        "question": "Who is the strongest?",
        "answers": {
            "a": "Superman",
            "b": "The Terminator",
            "c": "Waluigi, obviously"
        },
        "correctAnswer": "c"
    },
    {
        "question": "What is the best site ever created?",
        "answers": {
            "a": "SitePoint",
            "b": "Simple Steps Code",
            "c": "Trick question; they're both the best"
        },
        "correctAnswer": "c"
    },
    {
        "question": "Where is Waldo really?",
        "answers": {
            "a": "Antarctica",
            "b": "Exploring the Pacific Ocean",
            "c": "Sitting in a tree",
            "d": "Minding his own business, so stop asking"
        },
        "correctAnswer": "d"
    }
]
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" charset="UTF-8">
<link rel="stylesheet" href="css/teraQuizApp.css">
<script src="../script/jquery-3.3.1.min.js"></script>
<title>QuizApp</title>
</head>
<body>
<h1>Quiz</h1>
<div class="quiz-container">
  <div id="quiz"></div>
</div>
<button id="previous">前の問題</button>
<button id="next">次の問題</button>
<button id="submit">解答を送信</button>
<div id="results"></div>
</body>
<script src="../script/quizScript.js"></script>
</html>
@import url(https://fonts.googleapis.com/css?family=Work+Sans:300,600);

body{
    font-size: 20px;
    font-family: 'Work Sans', sans-serif;
    color: #333;
  font-weight: 300;
  text-align: center;
  background-color: #f8f6f0;
}
h1{
  font-weight: 300;
  margin: 0px;
  padding: 10px;
  font-size: 20px;
  background-color: #444;
  color: #fff;
}
.question{
  font-size: 30px;
  margin-bottom: 10px;
}
.answers {
  margin-bottom: 20px;
  text-align: left;
  display: inline-block;
}
.answers label{
  display: block;
  margin-bottom: 10px;
}
button.start{
  padding: 20px;
}
button{
  font-family: 'Work Sans', sans-serif;
    font-size: 22px;
    background-color: #279;
    color: #fff;
    border: 0px;
    border-radius: 3px;
    padding: 20px;
    cursor: pointer;
    margin-bottom: 20px;
}
button:hover{
    background-color: #38a;
}
.slide{
  position: absolute;
  left: 0px;
  top: 0px;
  width: 100%;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.5s;
}
.active-slide{
  opacity: 1;
  z-index: 2;
}
.quiz-container{
  position: relative;
  height: 200px;
  margin-top: 40px;
}

 試したこと

Chromeのデバッカーを用いて、ソースを追ってみました。
結果根本原因はmyQuestionsの中にデータがうまく入っておらず、
エラーが出ているということはわかりました。
JSコードのbuildQuiz()内でjsonLoader()は正常に呼び出されており、
jsonLoader()内で取得後、ループを回してpushし、
抜けた後のconsole.log(myQuestions);ではコンソール内に
(3) [{…}, {…}, {…}]0: {question: "Who is the strongest?", answers: {…}, correctAnswer: "c"}1: {question: "What is the best site ever created?", answers: {…}, correctAnswer: "c"}2: {question: "Where is Waldo really?", answers: {…}, correctAnswer: "d"}length: 3proto: Array(0)
というように値は入っているように思われますが、jsonLoader()実行後に
console.log(myQuestions);をすると以下のようになってしまいます。
[]
length: 0
proto: Array(0)

 補足情報(FW/ツールのバージョンなど)

HTML5/jQuery3.3.1/Chrome 70.0.3538.77
Chromeは--allow-file-access-from-filesパラメータ付きで起動しています。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

JSON形式で別だしにしたい
url: 'ローカルにあるjsonファイルのフルパス'

単純に別ファイルのjsにしてロードするのではなく、
各クライアントPCのローカルに保存されているものを読み込むという認識であってますか?

下記のようにファイル選択ダイアログで選択させるのであればできました。
https://jsfiddle.net/8ruLeoxw/

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/05 09:13 編集

    ご回答ありがとうございます。
    また、実際の例まで提示してくださったにもかかわらず、
    こちらの言葉足らずで申し訳ありません。
    ローカルにある問題のデータ(ファイルは1つのみ)を
    読み込むというものです。
    1つのみなので選択してロードではなく、jsコード上で指定したパスにあるファイルをロードという形にしたいと考えています。

    キャンセル

  • 2018/11/05 10:29 編集

    普通、セキュリティ的にファイル選択ダイアログなどのユーザ操作を経由しないと、ローカルファイルにアクセスすることはできません。
    (他にも例えばフルスクリーン操作などもユーザがボタンを押すなどしないと、ロード時に自動で行うのは無理)
    ただ、上の回答でありますが、chromeで`--allow-file-access-from-files`で起動すればajaxで出来るようですね。

    それでいいのであれば、x_xさんがおっしゃられているとおり、スライドの作成処理をajaxの完了後に行うか、ajaxを非同期ではなく同期的に実行してください。
    前者は具体的には以下のような感じですね。
    https://jsfiddle.net/ypd6csqn/

    キャンセル

  • 2018/11/05 11:01

    追加のご回答と具体例の提示ありがとうございます。
    ただ真似るだけではなく、こちらで動作とかを調べながら実装したところ
    無事に動作いたしました!
    ご指導感謝いたします。

    キャンセル

+1

Chromeでローカルファイルに対してAjaxを行うには、--allow-file-access-from-filesパラメーター付きでChromeを起動する事が必要となります(Qiita)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/02 17:28

    maisumakun様
    ご回答ありがとうございます。
    --allow-file-access-from-filesパラメータ付きで起動に関しましては
    すでに実行しております。
    こちら側で記載が漏れており、大変申し訳ございません。

    キャンセル

+1

Ajaxで非同期に取得してくるのであれば、buildQuiz()直後にはまだmyQuestionsができていません。
slidesもゼロのはずです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/02 18:29

    x_x様
    ご回答ありがとうございます。

    $.ajax以外での取得となりますとgetJSONとかでしょうか?
    一応先ほど試してみたところ結果変わらずのようです。。。
    初心者故、誤っていた場合はご指摘いただければ幸いです。

    キャンセル

  • 2018/11/05 09:27

    使わないようにするのではなく、取得後に処理するようにするのです。
    取得後にdone()に渡した関数が実行されます。
    https://api.jquery.com/jQuery.ajax/

    キャンセル

  • 2018/11/05 11:03

    追加のご回答と具体例の提示ありがとうございます。
    ただ真似るだけではなく、こちらで動作とかを調べながら実装したところ
    無事に動作いたしました!
    また、非同期ではなく、async:falseとして実装してもうまくいきました。
    ご指導感謝いたします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る