Jao's Blog

プログラミング備忘録

日記のプレビュー表記を作成する #12

 プレビュー表記とは、カレンダーページから遷移できるページです。

f:id:Kyokuya_jao:20200608143101p:plain
 少し前に撮ったスクショなので、立ち絵が僕のままですが、サンプルとしてはこの画像のような感じです。
 その月に書いた日記一覧をプレビューできるページ、ということです。
 ちなみに今回は、めちゃめちゃ苦戦しました。

日記を取得する

 まず、データベースから日記を取得します。いつものように、URLから$_GET['date']となっている日付のパラメータを取得します。
 パラメータは、例えば「2020-06」「1994-12」のように「年-月」という構成になっています。日記のテーブルの、dateカラムが、この年月から始まっているものを検索し、引っ張り出します。where句のlike文ですね。

しかし、うまくいかず……

 $sql = "select good_1, good_2, good_3 from myDiary where userId = :id and date like ':date%'";
    $stmt = $this->db->prepare($sql);
    $stmt->bindvalue(':date',$date,\PDO::PARAM_STR);

 このように、%のワイルドカードとbindvalue関数を使って検索をかけますが、エラーが出てしまいます。調べれば、原因はすぐにわかりました。

 $sql = "select good_1, good_2, good_3 from myDiary where userId = :id and date like :date ";
    $stmt = $this->db->prepare($sql);
    $stmt->bindvalue(':date',$date . '%',\PDO::PARAM_STR);

 $sqlの文字列の中ではなく、bindvalueの引数の中で%をつけることで、うまくいきました。勉強になった!

空のデータを抹消する

 無事に該当する月の日記を取り出すことに成功しました。しかし、前回にも少し触れましたが、データの中には1文字も書かれていない日記も登録されています。
 取り出したデータを愚直に全て表示してしまうと、空の日記まで表示されることとなってしまいます。

悪戦苦闘

 日記のデータは多次元配列となっているため、配列の消去にかなり苦労しました。

// 多次元配列の子要素が空の場合削除する(親要素は残る)
    foreach($data as $value){
      $res[] = array_filter($value, 'strlen');
    }
    // 子要素が消え去った親要素を消し去る
    foreach($res as $key => $value){
      if(empty($value) ){
        unset($res[$key]);
      }
    }

 まず、多次元配列の子要素が空だった場合、それらを全てarray_filterで抹消します。
 そうすると、中身ががらんどうになった親要素が残ります。
 それらをempty()による条件分岐とunset()によって、完全デリートするのです。

ここでミスに気が付く

 完璧だと思われた作戦は、大敗を喫しました。日記の中身は空でも、その空の日記の日付のデータは残っているという点を失念していたのです。
 つまり、子要素が完全に消え去ることはありません。
 これに対抗すべく、子要素の数が一個の場合、親要素を削除するという処理を攻撃表示で召喚します。子要素が一個ということは、そこに日付のデータしかない=日記が空っぽということだからです。

 この処理の実装にも、悩まされました。
 for($i = 0; $i < count($data) ; $i++)というfor文を回し、該当した要素を削除していたのですが、条件を満たしていても削除される要素とされない要素があるのです。
 原因は、for文を回す回数をcount関数で決定していたことにありました。要素を削除する処理をしているので、要素の数が途中で変わるということをすっかり忘れていたのです。この部分を、あらかじめ設定した定数にすることで解決しました。

取り出したデータを表示する

 苦労して取り出したこれらのデータを、htmlに出力します。  sprintf関数を使い、html文にデータを組み込みます。

f:id:Kyokuya_jao:20200618212252p:plain
 うまくいきました!最新の日記ほど上に表示されるようになっており、スクロールで下の日記を見ることができます。

うまくいったと思ったのも束の間

 さて、ちょっと左の矢印を押して、5月の日記を表示してみましょう。

f:id:Kyokuya_jao:20200618212527p:plain
 心を抉るエラー文です。
 その月の日記を一つも書いていない場合は、データが存在しないので、データベースに検索をかけたときにエラーが出ることが原因です。
 データを一つも取り出せなかった時は、空文字を返すことで万事解決……な、筈はありませんでした。
 これまでに様々な処理を行ってきましたが、それらに渡るデータが空文字なら、エラーが起きてしまいます。故にデータベースから取り出したモノが空文字なら、そもそもそれらの処理を行わず、空文字を返すという処理を行いましょう。

    if(!empty($tempDiaryData)){
      // 空の日記を消す処理
      $tempDiaryList = $this->_eraseEmptyDiary($tempDiaryData);
      // 番号を降り直す
      $diaryList = array_values($tempDiaryList);
      return $diaryList;
    }else{
      return '';
    }

 html部分に表示させるものが空文字であっても、エラーが起きてしまいます。故にここでも、データが空文字なら、そもそも処理を行わないようにしました。emtpy関数サマサマです。

ダメ押しのエラー

 

f:id:Kyokuya_jao:20200618212823p:plain
 三つのいいこと記入欄を全て埋めていないとエラーになることが判明しました。単純に、日記の書かれていない空のデータがエラーを引き起こしているようですね。
 日記を表示させる関数にて、データを直接表示させていましたが、ワンクッション処理を挟みましょう。変数を用意し、データが空なら空文字を、そうでなければデータの文字列を代入します。
f:id:Kyokuya_jao:20200618213255p:plain
 うまくいきました!

表示させる文字数を限定する

 ここまでくれば、あと一歩です。今のままでは、以下の画像のように、日記の文字数が長いとレイアウトが崩れてしまいます。

f:id:Kyokuya_jao:20200622093921p:plain
 mb_strlen関数で文字数を測り、一定以上なら、mb_substr関数でその先を切り捨てるという処理を放ちます。その後、文字列に「……」を連結し、文章が続くことを視覚的に示唆しましょう。  
f:id:Kyokuya_jao:20200622094622p:plain

 完成です!慣れない処理やエラーの連続で、とても大変でした。しかし、それだけにやりがいのある部分だったと思います。

気がついたこと

 面倒な処理の大半が空っぽの日記が含まれることに起因するものでした。
 ということは、つまり……最初から空っぽの日記はデータベースに登録しないという風にすれば、全てが解決したのではないでしょうか……?
 完全に盲点でした。次回までに修正します。

日記を書いた日はカレンダーにマークが付くようにする #11

 タイトルの通りです。

f:id:Kyokuya_jao:20200615074536p:plain
 この画像のカレンダーは初期に作成したダミーですが、このように、日記を書いた日はカレンダーに●マークが記される機能を追加しましょう。

実装していく

 カレンダーを動的に生成する際、URLから年月日のパラメータを取得しています。このパラメータを利用し、データベースから日付とユーザーIDで、適当なデータを取り出します。データを取り出せたのなら、その日に日記を書いている、ということです。<span class="filled">●</span>の文字列を挿入してやりましょう。

f:id:Kyokuya_jao:20200616205313p:plain
 いい感じ……ん? こんなにたくさん日記を書いていたでしょうか。
 ちょっと6月9日を覗いてみます。
f:id:Kyokuya_jao:20200616205448p:plain
 空っぽです。
 新しい日で日記編集画面を開き、何も書かないまま保存をすると、空の日記が登録される仕様になっているのが原因です。空とはいえ、そこに日記が存在するとプログラムは判定しています。

解決していく

 日記の存在を判定する際、日記の中身を一つ一つ確かめる処理を挟みます。怖い先生の持ち物検査みたいですね。
 日記の中身は「いいこと1」「いいこと2」「いいこと3」「その他」の4つで成り立っているので、foreachを回し、一つ一つの中身を確かめます。
 全てが空の場合は、日記が存在しないという判定を下すようにするのです。
 その結果……

f:id:Kyokuya_jao:20200616210114p:plain
 見事、きちんと日記の中身があるときだけ、丸印が付くようになっています。
 これによって、自分がいつどれだけ日記をつけたのか、わかるようになりました。

 それでは、今回はこのあたりで。

日記投稿・編集機能を作成する #10

日記を表示させる

 まずは日記を適切に表示させないといけませんね。MySQLを用い、日記テーブルを作成します。

create table myDiary(
  id int not null auto_increment primary key,
  userId int,
  date varchar(50),
  good_1 varchar(450),
  good_2 varchar(450),
  good_3 varchar(450),
  other text
);

 「日記のID」「紐付けのためのユーザーID」「日付」「良いこと1」「良いこと2」「良いこと3」「その他」というカラムで構成されています。ダミーデータとして、適当にユーザーIDを3とし、2020年6月5日と2020年6月6日の日記を挿入しておきます。
 日記の表示にはshowMydiaryというメソッドを使います。引数にURLの日付パラメータと、ユーザーIDを渡します。それらを条件に、MySQLのSELECT文でデータを取得しましょう。
 「いいこと1」「いいこと2」「いいこと3」「その他」と表記を分けたいので、配列としてデータを取り出し、それらをhtml文に組み込みます。

f:id:Kyokuya_jao:20200615231922p:plainf:id:Kyokuya_jao:20200615232044p:plain
 いい感じです。きちんと、日付に対応した日記が表示されています。
 しかし、ここでバグが。

日記をつけていない日はエラーになる

f:id:Kyokuya_jao:20200615232357p:plain
 日記をつけていない日は、データベースに該当する日付がないため、データを取り出せずエラーが起きます。当然といえば当然か……。
 これに対応すべく、データ取得がうまくいかなければ、返す配列を全て空にするという処理を叩きつけます。

//日付とidで日記を検索し、取得
    $sql = "select good_1, good_2, good_3, other from myDiary where userId = :id and date = :date";
    $stmt = $this->db->prepare($sql);
    $stmt->bindvalue(':date',$date,\PDO::PARAM_STR);
    $stmt->bindvalue(':id',$id,\PDO::PARAM_INT);
    $stmt->execute();

//取得に失敗したら配列を全て空に。成功したらそのまま返す
    $res = $stmt->fetch(\PDO::FETCH_ASSOC);
    if($res === false){
      $res = array_fill(0, 4, '');
    }else{
      return $res;
    }

f:id:Kyokuya_jao:20200615233055p:plain
 これにより見事、からっぽの日記でも表示することができるようになりました。

日記投稿・編集を可能にする

 右上にある羽ペンマークから、日記編集画面にジャンプできます。元のページと同じパラメータを渡してやるだけなので、至極単純です。
 テキストボックスのvalue属性には、日記を表示させた時と同じ処理で、文字列を埋め込みます。これによって、日記をつけていた日は、その内容がそのままテキストボックスに入ります。

f:id:Kyokuya_jao:20200616075104g:plain
 編集画面にて右上の保存ボタンを押せば、テキストボックスの内容が同ページにPOSTされます。POST内容がデータベースに登録された後、日記閲覧画面にリダイレクトします。
f:id:Kyokuya_jao:20200616075507g:plain
 内容をデータベースに登録する際、同じ日付とユーザーIDの日記が既に存在するかをチェックしています。
 存在していた場合はUPDATE処理を、存在しなかった場合はINSERT処理をかけるように分岐させております。


 アプリの要でもある日記編集機能を実装することができました!
 それでは、今回はこの辺りで。

日記表示機能を作成する #09

 カレンダー上で日付をクリックすると、日記が表示されるようにしましょう。

日記閲覧ページに飛ぶ

f:id:Kyokuya_jao:20200615080519p:plain
 カレンダーの日付に、aタグを追加します。
 そのままでは数字しかクリックに反応しません。マス目全体をクリックできるようにしたいので、aタグをブロック要素に変更し、widthとheightを100%に設定します。
 aタグをブロック要素にすると、text-alignが使えなくなるため、文字が中央配置でなくなります。なので、適切なpaddingとmarginを設定します。
f:id:Kyokuya_jao:20200615090928p:plain
 しっかりとa要素がマス全体に広がっていますね。ちょっとはみ出してしまっているので、親要素であるtd要素に「overflow: hidden;」を設定し、はみ出た部分を非表示とします。
 これにて、マス目をクリックすることで日記閲覧ページに飛ぶことが可能になりました。

URLにパラメータを与える

 例えば、2020年の6月1日のマスをクリックしたら、2020年の6月の1日の日記を表示させないといけません。
 それを実現させるために、URLにパラメータを与えます。

$date = $day->format('Y-m-d-D');
      $body .= sprintf('<td><a href="/myDiary.php?date=%s" class="youbi_%d %s">%d</a></td>', $date, $day->format('w'), $todayClass, $day->format('d'));


 aタグのリンク先に日付のデータを渡します。2020年の6月1日(月)なら、「2020-06-01-Mon」という文字列がURLの最後に渡されるはずです。
 実際に、カレンダーで6月1日をクリックしてみましょう。

f:id:Kyokuya_jao:20200615091933p:plain
 いい感じです。日付表記はダミーの6月10日で固定されていますが、URLを見ると、しっかりとパラメータが渡っているのがわかります。

日付を正しく表示する

 URLのパラメーターを、年月日にバラしていきましょう。
 「2020-06-01-Mon」というパラメータがあったとしたら、「2020」と「06」と「01」と「Mon」に分けます。

$year = substr($_GET['date'], 0, 4);
$month = substr($_GET['date'], 5, 2);
$day = substr($_GET['date'], 8, 2);
$youbi = substr($_GET['date'], 11);

var_dump($year);
var_dump($month);
var_dump($day);
var_dump($youbi);


 substrを使えば、適切に切り出すことが可能ですね。

f:id:Kyokuya_jao:20200615224626p:plain
 「06」や「01」がそのまま表示されると見栄えが悪いので、アタマに0がついていた場合はそれを取り除きます。また「Mon」なら「月」、「Tue」なら「火」と、日本語に変換するためのswitch文も追加しましょう。
 ……スクリーンショットの撮影を忘れてしまいましたが、ともあれ、それらのデータをhtml部分に出力すれば、日付表記は完成です。

左右の矢印で日付を切り替えられるようにする

 カレンダーと同様に、左右の矢印で、前日や次の日の日記を表示できるようにします。
 カレンダーでは、URLから取り出したパラメータ(2020-06など)で月単位のDateTimeオブジェクトを作成し、それに-1したものを前の月のリンク、+1したものを次の月のリンクとしていました。
 日記では月単位でなく、日単位のDateTimeオブジェクトを作成し、同じ処理でリンクを作成すればOKです。
 ただし、日記のパラメータには「Mon」といった余計な曜日表記が存在するので、substrでそれらを切り抜く必要があります。また、前や次のリンクを作成する際に、URLに曜日表記を再び連結する処理も忘れてはいけません。

f:id:Kyokuya_jao:20200615230013g:plain
 このように、日記の表示を切り替えられるようになりました!

 次回は、いよいよ日記を書く処理を作っていきましょう。それでは、今回はこのあたりで。

カレンダーを動的に表示する #08

 さて、こちらにおわしますカレンダーですが

f:id:Kyokuya_jao:20200615074536p:plain
 ダミーテキストを表示させているだけなので、実際のカレンダーとは異なります。せめて実際の2020年6月のカレンダーを作れよと思わないこともないです。
 また、それっぽい左右の矢印をクリックしても、何も起きません。なので、各月のカレンダーを動的に表示できるよう、DateTimeクラスを使って改造していきましょう。

完成したものがこちらです

f:id:Kyokuya_jao:20200615213551g:plain
gif変換が上手くいかずチカチカしております
 かの有名な動画教材サイトドットインストールのレッスンそのままなので、カレンダー作成自体に特筆すべき点はありません。
 だがしかし、順風満帆にいかぬのがプログラミングというもの。主に2点の不具合に苦しみました。

不自然な空きスペース

f:id:Kyokuya_jao:20200615074722p:plain
 1日が日曜日な月において、不自然な空きスペースが生まれるという不具合が発生しました。

原因

●空のtr要素が存在する

 カレンダーというものはご存知の如く、1行につき7マス(月〜金)の日付で構成されています。つまり、日曜日で改行しているのです。
 このカレンダーもその例に則り、日曜日になるとtr要素の閉じタグと開始タグ(≒改行)が押し込まれるよう設定されています。

    foreach ($this->_period as $day){
      if($day->format('w') == 0){
        $body .= '</tr><tr>';
      }(以下省略)


 つまり、月の始めが日曜日だと、その直前にtr要素の閉じタグと開始タグが挟まれ、空のtr要素が生じることとなります。その空のtr要素が、不自然な空きスペースを生み出しているのでしょう。

●table要素に高さが設定されている

 table要素の高さは親要素と同等(height: 100%;)となっております。
 試しにtableの高さ要素を消したところ、レイアウトは崩れましたが、空きスペースはなくなりました。

f:id:Kyokuya_jao:20200615074849p:plain
 tr、td要素の高さは、元々設定されておりません。それらの親要素であるtable要素に合わせようとして、空きスペースが生じたということでしょうか。

解決策

●tr要素が空の時、非表示にする

 CSSの:emptyセレクタを用い、trの中身が空だった場合、そのtrを非表示(display: none;)とします。

 しかし、これだけではうまくいきませんでした。:emptyセレクタは、要素の中に空白や改行が存在した時点で無効化されてしまうようです。
 それならばと、jQueryを用い、trの中にtd要素がない場合、空白すらも消し去って完全に空っぽにするようにしましょう。

f:id:Kyokuya_jao:20200615075738p:plain
 この方法により、解決しました。冒頭のtr要素に「firstTr」クラスをつけ、firstTrクラスの中のtd要素の数を計算しています。tdの数が0だった場合、firstTrクラスの中身を跡形もなく消しとばしております。これによって、:emptyセレクタが利くようになりました。

 うまくは行きましたが、少々強引なように思えます。そして、次の方法が最適だと知ることになるのです。

●tr要素に高さを設定する

 原因の項目でも挙げましたが、tableに高さが設定されていることが問題なので
 tableの高さを消し、代わりにtrに高さをつけることで、全てが解決しました。めでたしめでたし。


 さて、カレンダーの動的な生成はこれにて完成しました。

バグはこれだけに非ず……

f:id:Kyokuya_jao:20200615080519p:plain
今日は何日?
 カレンダー上で、当日のマスは薄い黄色で示されるよう設定したのですが  このスクショ撮影日時は2020年6月15日であるにも関わらず、なぜか2020年5月23日になっているのです。
 ローカル環境の問題なのでしょうか……?

 グローバル環境で解決するなら、今の段階では致命的ではないと判断したので、とりあえずは放置しておきます。

 それでは、今回はこのあたりで。

美少女を作成する #07

 さて、いよいよこのWebアプリの要でもある、美少女を作成しましょう。
 美少女というのは、なんといっても「美少女が褒めてくれる日記」という題名の実に25%を占める単語です。対して、一見肝心に思える日記という単語の占有率は約17%。美少女がいかに大切な要素か、定量的に示されてしまいましたね。

 美少女を用意する方法は、様々です。

  • フリー素材を利用する
  • 最もお手軽です。しかし、独自性が失われてしまうので、却下です。
  • イラストレーターに依頼する
  • 独自性があり、かつ高品質な美少女を作れます。しかし、お金がないので却下です。今日の晩ご飯も、パスタです。
  • 自分で描く
  • 品質はともかく、低コストかつ独自性の高い方法です。なにより、楽しい。これでいきましょう。

美少女を描いていく

 勢いで購入したものの、5〜6回使用した後に放置されていたペンタブを引っ張り出し、イラスト作成ソフトを起動します。

f:id:Kyokuya_jao:20200615082637p:plain
 アタリを描きます。サッと描いたかのように言っていますが、実際は3Dデッサン人形アプリ等を使いながら、七転八倒しつつ描きました。

 アタリを元に、手癖でササッと顔をデザインしましょう。
f:id:Kyokuya_jao:20200615083054p:plain
 ややフレッシュすぎる感じがあります。髪の毛はもう少し長くしましょう。目はいい感じなので、このままで。

 髪型を決め、服もそれっぽく決め……
f:id:Kyokuya_jao:20200615083629p:plain
 線画が完成しました。途中過程を省きすぎだろと我ながら思いますが、描くことに夢中になっていたら撮影を忘れてしまっていたので、ご容赦ください。

 あとは色を塗るだけです。出来るだけアプリのデザインに合った配色を考え、バケツツールで塗っていきます。
 適当に影を落として……
f:id:Kyokuya_jao:20200615212223p:plain
 完成です!
 見紛うことなき美少女です。「美少女が褒めてくれる日記」に相応しいキャラクターだと思います。
f:id:Kyokuya_jao:20200615074536p:plain
 実際に画面に落とし込みました。しっかりと画面に調和しているのではないでしょうか。

 この子の名前を決めましょう。PHPフレームワークとして有名な「Laravel」から拝借し、"ララ"という名前で決定しました。
 まぁ、この日記の開発にLaravel使っていないのですけども。

 ついにこの世に産み落とされたララちゃんと共に、今後の開発に尽力していきます。
 それでは、今回はこのあたりで。

その他のページを作る #06

 前回は、カレンダーが表示されているホーム画面を作成しました。

f:id:Kyokuya_jao:20200608110842p:plain
前回出来上がったモノ

 今回は日記閲覧画面日記編集画面マイページを作成していきます。構成はホーム画面と同じものなので、てきぱきと作っていきましょう。

日記閲覧画面

 自分が書いた日記を閲覧することのできるページですね。日記のダミーデータを適当に入れ、表示枠を作ります。

f:id:Kyokuya_jao:20200610212146p:plain
日記を表示
 右上の編集アイコンをクリックすることで、該当日付の編集ページに飛びます。
 複数行の記載も表示させる必要があるので、表示枠の縦幅を可変にしたのですが……
f:id:Kyokuya_jao:20200610212249p:plain
ちょっと長めの日記を書いた場合
 このように、どうしても見切れます。一応、スクロールで全文を確認することは可能です。とはいえ、イマイチ見栄えがよくない気がします。背景色を変えたり、枠線をつけたりしたのですが……結局、素の状態が一番綺麗だと、私は断定しました。

日記編集画面

f:id:Kyokuya_jao:20200610212537p:plain
日記の編集・投稿ができます

 日記閲覧ページから飛ぶ場所です。日記閲覧ページの要素を、input要素に置き換えただけです。正直、楽でした。

マイページ

 様々な情報を確認できるマイページです。

f:id:Kyokuya_jao:20200610212843p:plain
自分に関する設定はここで
 項目をクリックするとそれらを編集できる、というのが当初の設計でした。しかし、実現難易度が高そうなので、右上のボタンから別途編集画面に飛べるようにしました。


 今回は、前回と違ってPC版のレイアウトを載せていません。そのページも似たような構成なので、割愛しました。
f:id:Kyokuya_jao:20200610213608p:plain
どのページも配置が変わるだけ
 相も変わらずに僕の画像が全ページに君臨しているのは、やはり見るに耐えないところがあります。
 各ページのレイアウトはこれにて完結しました! なので、次回はいよいよ、美少女の作成に取り掛かりたいとおもいます。
 それでは、今回はこの辺りで。