yunyun_engineer
@yunyun_engineer (yunyun)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

getElementsByClassNameかquerySelectorAllを使い日付の要素を取得したいです!

解決したいこと

RailsでWebアプリをつくっています。
購入日と賞味期限をDB上に登録し、その2つのデータの差分で残り日数を表示させましたが、下記の画像の通り最初のコンテンツしか残り日数が表示されません。

追記
getElementByIdメソッドでは最初の要素しか取得されず、getElementsByClassNameメソッドを使いましたが、今度は取得自体ができなくなってしまいました。コンソールで見るとinnerHTMLが使えないみたいです。
HTMLcollectionというものでdiv要素は取得できていますが、使えるメソッドが少ないみたいで、日付が取得できるか不明です。

発生している問題・エラー

3bcb942aa8e69af68ada8796c013df41.png

該当するソースコード

window.addEventListener('load', () => {
  const purchaseInput = document.getElementsByClassName("purchase-date");
  const expirationInput = document.getElementsByClassName("expiration-date");
  const purchaseDate = new Date(purchaseInput.innerHTML);
  const expirationDate = new Date(expirationInput.innerHTML);
  const daysLeft = document.getElementsByClassName("days-left");
  daysLeft.innerHTML = (Math.floor( (expirationDate.getTime() - purchaseDate.getTime()) / ( 1000 * 60 * 60 * 24 )));
});
<main class="main">
  <div class="inner">
    <div class="card__wrapper">
      <% @items.each do |item| %>
        <div class="content">
          <div class="image-content">
            <%= link_to image_tag(item.image.variant(resize: '200x200'), class: :card__img ), item_path(item) %>
          </div>  
          <div class="sub-content">
            <span>購入日</span>
            <div class="purchase-date"><%= item.purchase_date %></div>
          </div>
          <div class="sub-content">
            <span>賞味期限</span>
            <div class="expiration-date"><%= item.expiration_date %></div>
          </div>
          <div class="sub-content">
              <span>残り<span class='days-left'></span>日</span>
          </div>
          <div class="nickname-content">
          <%= link_to "by #{item.user.nickname}", root_path, class: :card__user %>
          </div>
        </div>
      <% end %>
    </div>
  </div>
</main>
class ItemsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_item, only: [:show, :edit, :update, :destroy]
  before_action :move_to_index, only: [:edit, :update, :destroy]

  def index
    @items = Item.where(user_id: current_user.id).order("created_at DESC")
  end

  def new
    @item = Item.new
  end

  def create
    @item = Item.create(item_params)
    if @item.save
       redirect_to root_path
    else
       render :new  
    end
  end

  def show
  end

  def edit
  end

  def update
    if @item.update(item_params)
      redirect_to item_path(@item)
    else
      render :edit
    end
  end

  def destroy
    if @item.destroy
      redirect_to root_path
    else
      redirect_to root_path
    end
  end

  private
  def item_params
    params.require(:item).permit(:name, :quantity, :purchase_date, :expiration_date, :memo, :image).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])
  end

  def move_to_index
    unless current_user == @item.user
      redirect_to root_path
    end
  end
end
0

2Answer

JavaScriptのDateクラスは機能が貧弱な上、日付のフォーマットによっては予測がしづらい動作をします。
せっかっくRubyのコードが利用できるのですからわざわざここだけJavaScriptで計算する意味は薄いように思います。
どうしてもブラウザ側でで算出したいのであれば、ブラウザの開発者ツールを利用するなどしてJavaScriptのコードを1行単位で実際に実行してみると良いでしょう。
どこでエラーが出ているかが分かりやすくなります。

const purchaseDate = document.getElementById("purchase-date");

これで取得できるのはタグ(DOM要素)ですから、 new Date() に渡すことはできません。

const pur = new Date(purchaseDate); // Invalid Date

テキストを取り出して渡す、つまり

const pur = new Date(purchaseDate.innerText);

なら意味が通ります。

また getDate() は日(2021/4/19なら19)を返す関数なので、同じ月同士でなければ結果が意図しない値になるでしょう(例: 4/19と5/18なら?)
日付の差を取得するには、両者の getTime() を取って引き算をして86,400,000(24時間をミリ秒で示した値)で割るという手があります。

1Like

Comments

  1. 回答ありがとうございます!大変勉強になり嬉しいです。
    ご指摘のinner.Textとget.Time()を使い、残り日数の表示はうまくいきました。といいたいのですが、最初のコンテンツしか表示されません。
    やはりご指摘の通りJavascriptの機能の貧弱性からくるものでしょうか?

こんな感じでしょうか。

window.addEventListener('DOMContentLoaded', function() {
    const cardWrapper = document.querySelector('.card__wrapper');
    const contents = cardWrapper.querySelectorAll('.content');
    for(let content in contents) {
        if(typeof contents[content] !== 'object') continue;
        const c = contents[content];
        const date = [];
        const purchaseDate = c.querySelector('.purchase-date');
        if(purchaseDate !== null) date[0] = purchaseDate.textContent.match(/\w+/g);
        const expirationDate = c.querySelector('.expiration-date');
        if(expirationDate !== null) date[1] = expirationDate.textContent.match(/\w+/g);

        const daysLeft = c.querySelector('.days-left');

        if( date[0] !== undefined && date[0].length === 3 &&
            date[1] !== undefined && date[1].length === 3 &&
            daysLeft !== null)
        {
            daysLeft.textContent = daysLeftCalc(date);
        }
    }

    function daysLeftCalc(d) {
        const date0 = new Date(+d[0][0], +d[0][1] + 1, +d[0][2]);
        const date1 = new Date(+d[1][0], +d[1][1] + 1, +d[1][2]);
        const leftDay = date1.getTime() - date0.getTime();
        return leftDay > 0 ? Math.floor(leftDay / 864e5) : 0;
    }
});

IEでも動くよう組んでいますので冗長な書き方な部分もあります。

1Like

Comments

  1. 一旦解決しました。回答大変ありがとうございました。勉強になります!

Your answer might help someone💌