僕のポートフォリオ！Untangled_pasta_treesの仕様や技術について(jqueryでの動的な要素追加について詳しく書きます！)


## はじめに
RUNTEQ19期生のヨシダです！
バリバリの初心者です！
好きな食べ物は地獄ラーメンです！
今日はいつも説明するたびによくわからないと言われる、よくわからないポートフォリオ
「[Untangled_pasta_trees](https://untangled-pasta-trees.herokuapp.com/)」についての説明や仕様、技術その他諸々について語っていこうかなと思います！(ポートフォリオまだ作りかけなので仕様等はコロコロ変わるかもしれません。)
[githubでコード見れますがリファクタリングまだです。処理はほぼ全部 app/views/layers/indexにあります。](https://github.com/yoshidashota0328/Untangled_pasta_trees)

あとは、初心者が動的な要素追加を用いて作成するにあたり、つまずいたことを解説していきます。

# どんなサービスか？
ツリー構造でカテゴリー分けできるマークダウン形式のノートだと思っていただけると理解しやすいと思います！

# なんでこれを作ったの？
マーケットインのポートフォリオを作るときはまず自分が使いたいと思うものを作ればいいと思うよという先人の言葉にならい作りました！
他に似たサービスもあまりなかったので今後の自分の学習に活かせるポートフォリオにすれば一石二鳥だと思ったからです。
実際、今のポートフォリオ開発で必要な情報とかを書きこんで使ってたりします！

# 制作環境
vscode
ruby -v 2.6.6
rails -v 5.2.6
heroku

# how to use ページ
[how_to_use](https://qiita.com/yoshidashota0328/private/dd67cbdb8ca7a7fd1d5f)

# 用語解説
### tree = 1画面に表示されたツリー構造のノート全体を指す言葉。
### treetop(top) = 木構造のroot
### leaf = マークダウン入力ができる１要素。ツリー構造の葉っぱ１枚を指す。(用語の使い分けがめんどうだったのでnodeもleafって呼んでます。)
### leaf_ID = leafの頭についている番号。
### All_trees = 公開されたtreeが一覧表示されるページ。
### parent_ID = leafの親になるleafのID。これを起点に相対座標で子leafが配置される。
### My_page = ログインすると右上に表示されるUserから飛べるページ。自分の作ったtreeが表示される。

# 技術概要

制作にあたり使った技術はRuby(on Rails),javascript(jquery),html,cssです。
主なツリーの処理はほぼ全てjavascript(jquery)を用いています。
ツリー構造を動的に作成するためにappendやafterを使用しました。
絶対座標で配置したrootからボタンクリックで動的にleafを作成し、各要素を繋ぐ枝はborderで書いています。
その他、よくあるweb機能はほぼ全てRuby on Railsを使用しています。

# 以下制作で考えたこと、技術面について

## どうやってdbに保存しようか？

最初に、どうやってツリー構造の親子関係をdbに保存すればいいか分からないという問題がありました。
これに関しては以前pythonを1ヶ月だけ勉強したときに2次元配列を利用して2DのRPGゲームのマップを作成するという勉強をしたことを思い出しました。

将棋やオセロなどを思い浮かべてもらえるとわかりやすいと思いますが、1マスがx座表、y座表で表現でき、それに１マス用の画像を座表指定で貼り付けるといった物です。

これの応用でdbにpositionX,positionYというカラムを追加してそこにcliantX,clientYのように画面上の座標を取得して数値化するものを使い位置関係などを保存しています。
[イベントが発生した時のマウス情報を取得するには？（イベントオブジェクト）](https://www.buildinsider.net/web/jqueryref/021)

自分の場合はあくまで`目に見えるツリーの形`として親子関係を保存している形になります。

## cssの要素をドラッグで移動
[ jQuery 版「要素をマウスで移動できるようにする方法」② ドラッグ移動版](https://javascript-style.hatenablog.com/entry/2019/03/09/100201)

jqueryで動的にleaf要素を追加していけばツリー構造ができそうだなと思っていた僕はどうしても必要な技術が一つありました。

それはツリー自体をドラッグで移動させることでした。
画面右側のウィンドウサイズはツリーが大きくなると自動で広がって行きますが、左側は微動だにしないんですよね。
なので右側にツリーを移動させる必要がありました。

## 動的な要素の追加

次に必要になったのが動的にツリー構造のleafをボタンクリックで生やすことでした。

これはjqueryのappendとafterで実現できます。
[jQueryで要素を追加するappendの使用方法まとめ ～appendToやafterに適したパターンについても解説](https://www.bold.ne.jp/engineer-club/jquery-append)

自分の場合は一番最初の要素(root)をappendで作成してそこからleafをafterで相対座標で配置する作りになってます。
[![Image from Gyazo](https://i.gyazo.com/8f1a0854812d2efb856eeef514f6e646.gif)](https://gyazo.com/8f1a0854812d2efb856eeef514f6e646)

## つまずきポイント1!　動的な要素にクリックイベントを設置する

appendやafterにクリックイベントをつける場合はonclickのような **.on()** を使わないと動かないです！
[clickイベントが効かない？appendで後から追加した要素にクリックイベントを追加する方法](https://begeeek.com/article/972/)
後述するhoverとかも全部基本 **.on()** で対応します！

クリックイベントを付加したい要素にclassやidをつけて

```
 //セーブボタンを押したときにセーブ完了表示
  $(document).on('click', '.save', function(){
    セーブボタンをクリックしたときに走らせたい処理を書く
  });
```
のように指定します。(.saveがclass指定になってます。)

## 動的な要素にhoverアクションを付ける
動的な要素にhoverをつけるには「mouseenter」「mouseleave」を使います！
[onメソッドのイベントにhoverを使う方法：動的に作成した要素にイベント設定〜jQuery
](https://itstudio.co/2015/08/10/4536/)
自分は吹き出しを拡大するときにこれを使ってます。
[![Image from Gyazo](https://i.gyazo.com/7875340b1fbece2cd57d0e47f80fbf31.gif)](https://gyazo.com/7875340b1fbece2cd57d0e47f80fbf31) 

## マークダウンエディタeasyMDEについて
[easy-markdown-editor](https://github.com/Ionaru/easy-markdown-editor)

入力欄をマークダウンにするのがこのポートフォリオを作る最大の難関だったかもしれません！
まずは自分のサイトに合うエディタを探す必要がありました。
自分の使い方だと機能としては入力欄をそのまま出力欄として使える機能が必須。
その機能を満たすものとして自分が使用しているのがeasyMDEです。
自分の場合indexの１画面内に動的に複数のマークダウンエディタを出現させる必要があったので、その仕組みを考える必要がありました。
動的に変数を作成してそれを１つずつエディタに割り振ればいいんじゃないかなと思って、とりあえず動的な変数の作り方を探しました。

## 動的な変数をjavascriptで作るには？
[javascriptで動的に変数を生成する](https://qiita.com/rikua0023/items/1c67bf354ac7038d950f)
まずはeazyMDEの空の連想配列を作りました。

```
var eazy_mde = {}; 
```
そして自分の場合はleaf生成時に

```
eazy_mde['easyMDE' + layer_id] = new EasyMDE({element: document.getElementById("leaf-body" + layer_id + ""), maxHeight: "550px"});
```
layer_id　というindex全体で`「1leafにつきlayer_id++」`で統一して数値を使っている変数で動的に変数名を作って、eazyMDEを出現させるbody入力欄をgetElementByIdで指定しています。

これでとりあえず自分の好きな要素にeazyMDEを配置できました！

## eazyMDEの文字やウィンドウのサイズ変更

ウィンドウサイズの変更はeazyMDEの下にあるtextareaからサイズを取得して変更できました！

hoverに以下の処理を書く、まずは拡大

```
var tooltip_size = document.getElementById(tooltip_id);    
            tooltip_size.style.width = "1280px";
```
　縮小する時は""でサイズ指定するのがデフォに戻す時オススメ！

```
var tooltip_size = document.getElementById(tooltip_id);
            tooltip_size.style.width = "";
```
これでとりあえずwidthを指定して、上の変数作成時にmaxHeight: "550px"でheightを指定しています！

## つまずきポイント2!　eazyMDEの文字サイズ変更！
ここはぶっちゃけ偶然できました！
READMEを見ても確か文字サイズ変えられない感じで色々適当に試してみたんですが、どうもうまく行かない。
そもそもなんで文字サイズが変更したかったかというと。
自分のサービス100%サイズで使うと要素がデカくて使いにくいんですよね！
1回小さく作り直したらレイアウトが崩れてダサくなったので戻しました！
なので今、自分のおすすめとしては画面サイズを50%で使うことを推奨しているんですが、
そうすると固定の文字サイズの時見づらいし書きづらいしすごく使いづらくなってしまい、文字サイズをvh(画面縦幅の比で指定)に変更することにしました。
なので、結局このeazyMDEの文字サイズが変更できないとかなりまずい訳でいつも通り途方に暮れていたんですが、別の作業中に急に文字がデカくなって原因追及してみると、

cssのdivを

```
div{
font_size: 2vh;
}
```
とかにしたらテキスト欄がdivで囲われていたのでデカくなってたことが判明！（親要素を変更すればいいのかも？)

とりあえずラッキーでなんとか乗り切りました！

## つまずきポイント３！jqueryで動的な要素作ったらform_withできない
jqueryで動的に作った要素は純度100%jquery(javascript)な感じなので、form_withが使えません！
完全によしなにやってくれてたところをhtmlでformタグ使って作らなきゃいけなくなりました。

で、いろいろ調べると...
[【Rails】formヘルパーの理解](https://note.com/sakusaku34/n/naeddc0656cc9)
[【Rails勉強ネタ】　HTMLのformは本当にGETとPOSTに制限されている？？](https://qiita.com/xusaku_/items/54479e5744cd7af31f89)
と出てきました。

まず、考えたのがform_withのcreateとupdateをいい感じに判断してくれる部分です。
確か、form_withのmodelに渡した変数が空ならcreate、中身入ってたらupdateとかで処理してたとおもうんですが、そもそもこれ実現しようとしたらコード長くなるな、処理分けるの面倒だなと思いました。

なので、leafを作成したときにajaxでnewしてそのあとに全部updateするという仕組みを考えました。

## ajaxでnewする方法

参考サイトをメモし忘れていたのでとりあえず自分のコードで解説。

```
 $.ajax({url: "layers", type: "POST", data: {layer_id: layer_id, positionX: setX, positionY: setY, parent_id: this_id, user_id: fk_user_id, tree_id: fk_tree_id}});
```
長いですが後ろのdataはparamsです。
url指定してtype指定してnewしてます。

```
  def create
    unless set_layer
      layer = Layer.new(layer_params)
      layer.save
    end
  end
```
leafが空だった時はcreateするためにunless set_layerしてます。

```
def set_layer
    @layer = Layer.find_by(layer_id: params[:layer_id], user_id: params[:user_id], tree_id: params[:tree_id])
  end
```

## つまずきポイント４！url指定は相対指定がオススメ！
もともとは

```
url: "http://localhost:3000/layers"
```
とかやってたんですが、herokuにデプロイするときにurl変わるので書き直す必要が出てきます。

```
url: "layers"
```
みたいにしましょう！

## urlの最後の/ってなんなんですか？
[知らないとまずい？ホームページアドレスの最後の「/」っているの？いらないの？](https://pr.toriaez.jp/navi/column/404.html)

url指定で付けるのと付けないので処理が変わる場合があるみたいです。

## 画面遷移前のurlを取得して色々やる
[[Rails]遷移元のURLを取得してリダイレクトする方法](https://qiita.com/sew_sou19/items/21974ec512c0a9b329aa)
[Railsで遷移元のURLを取得する方法(if文で使える)](https://qiita.com/takazi/items/9b1c82d8fcc602df8a1a)

```
redirect_to request.referer
```
とすると遷移前の画面にリダイレクトできます。

これを応用すると

```
  def update
    if @tree.update(set_tree_params)
      unless request.referer&.include?("layers")
        redirect_to request.referer, success: 'Update successful.'
      end
    end
  end
```
include?で部分一致したurl以外でリダイレクトみたいな感じで分岐できます。


## formでupdateする方法
上にも載せましたが
[【Rails勉強ネタ】　HTMLのformは本当にGETとPOSTに制限されている？？](https://qiita.com/xusaku_/items/54479e5744cd7af31f89)
`HTMLのフォームで指定できるメソッドがGETとPOSTだけという制限に起因する`らしいです。
なので
隠しパラメータを入れてやる必要があります。

```
<input type="hidden" name="_method" value="patch" />
```
こんな感じでupdateで使うpatch(put)を入れることでupdateアクションを呼び出せます！

```
<form action="layers/' + treetop_i + '" method="post"><input type="hidden" name="_method" value="patch" />ここに入力欄<input type="submit" value="save" class="save"></form>
```
こうすると入力欄の内容がupdateアクションに送られます！
## ajaxでupdateする方法

```
$.ajax({url: "/trees/" + fk_tree_id +"", type: "POST", data: { tree: { id: fk_tree_id, leaf_count: layer_id }, "_method": "PATCH"}});
```
"_method": "PATCH"としてこっちもpatch(put)を送ってあげます。

# 注意："_method": "PATCH"はdataの{}の中に書いてparamsとして送りましょう！

## バブリング
[[jQuery]イベントのバブリングって何？](https://taneppa.net/jquery-bubbling/)
まあ簡単にいうと、　子要素クリックすると親要素も連鎖的にクリックされたことになるよって話です。
そんな時は適切にstopPropagation()を付けましょう！

## バブリング対策したのに変な動きする時
[buttonでsubmitさせない方法](https://qiita.com/kazu56/items/b2e9c15942e04a6ffda9)
自分の場合勝手にupdateアクション発生してどこでバブリングしてるんかな？と思ったらボタンの方でバグってたことがありました。
formの中でtype="button"としないとデフォがsubmitなのでsubmit扱いになるらしいです！

## 画面リロードしないとjavascriptが動かない時
[【Rails】リロードしないとJavaScriptが動かない！【簡単に解決】](https://qiita.com/shibata0406/items/861df68467f3bee22a94)
上記の通りです。
下で書いてるtreeの表示非表示機能で使ってます。


## controllerからscript内（jquery側）にどうやって変数渡そう？
rails側からdbのデータをviewに渡してそれをjqueryに渡すのってちょっと面倒ですよね？
[Ruby On Rails Gemまとめ -gon-](https://qiita.com/yk-212/items/8d648b83e24a43af17ba)
gonってやつがあります！すごく便利です！
ただ変数名にgon.って付けて呼び出すだけ！

## setTimeout使ってたけど処理がずれる時
処理をタイミングずらして順番に動かしたくて安易にsetTimeout使ってたんですがどうも処理がズレるっぽい。
[async/await、promise・・これが最後の「JavaScriptの非同期処理完全に理解した」](https://qiita.com/_takeshi_24/items/1403727efb3fd86f0bcd)
とりあえずこれ読んで完全に理解すればいいと思います。
僕はもう完全に理解しました。
自分の場合は

```
 const ajax_new = async function(){
        ajaxでnewするよ
      }
      const create_leaf = async function(){
        leaf作るよ
      }
      const set_params = async function(){
        params入れるよ
      }
      const processAll = async function() {
        await ajax_new()
        await create_leaf()
        await set_params()
      }
      processAll()
```
のようにajaxでnewして、そのレコードのupdate用にleaf作って、作ったleafの入力欄に計算された数値を入力みたいな流れを順番にやってます！

## さあleafができたら枝で要素繋ごうか?
で、ここでまた頭かかえます。
もともと使おうと思ってた[Ruby | Gviz gem の内部 DSL の基本](https://qiita.com/tbpgr/items/9037fb05745dfac930ed)で多分使ってる、[html5 でグラフを描く ( canvas, SVG )](https://kurage.ready.jp/jhp_g/html5/canvas.html)というものがあります。
これが使えるとleaf間を枝でつないでツリー構造ができる訳ですが、残念ながらうまく動かせませんでした！
たしか、絶対座標で描画領域を配置できなくて、描画領域自体もめっちゃでかいけど限界あるよって感じでちょっと実装難しいなとなりました。

なので、divのborderで愚直に線を引きました。

# 以下、技術から仕様の話になります。大したことは書いてません。

## 以下仕様解説

というわけで続きです。

サイトの軽快な動作を目指すに当たって乗り越えなければいけない問題がありました。

`ツリー動かしたら全部のleafの座標一気に送られてめっちゃ処理重くなるんじゃないか？という問題。`ですが、

`treetopから全部のleaf相対座標にする`ことで解決しました。

上のdivのborderで枝作ったやつもこれを応用している訳です。

これによりツリーを一気に動かしてもtreetopのx,y座標が送られるだけなので処理が重くなる心配がなくなった訳です。

## leafのつなぎ変えできる方法に変更

ただこのままだとleafを別のleafの子要素にしたいなと思ってもできないんですよね。

なので、

`hoverでleafのID取得して子要素のleafに記憶させてそれぞれを親子付けする方法`に変更しました。

```
 //マウスオーバーでid取得
  $(function(){
    $(document).on({
      'mouseenter' : function() {
        this_id = $(this).attr('id');
        console.log("this_id" + this_id);
      }
    }, '.leaf');
  })  
```
hoverで取得したthis_idを子leafを作るときにparent_idというカラムに保存させます。

再表示の際、親leafの座標をclientX、Yで取得して相対座標で配置します。

これで画面が再表示されたときに親leafに対して相対的な表示がされてleafの付け替えが可能になった訳です！

## そもそもどうやって再表示しているか？

まずindexがこうなってます。

```
　　def index
    tree = Tree.find(params[:tree_id]) 
    # 公開ツリー または ユーザがauthor
    if tree.state == "public_tree" || current_user&.id == tree.user_id
      gon.layers = tree.layers.all.order(:layer_id)
      gon.current_user = current_user&.id
      @author = User.find(tree.user_id)
    else
      redirect_back(fallback_location: trees_path)
    end
  end
```

ここでgon.layersに入れた値をindexで

```
$.each(gon.layers, function(index, elem){...
```
としてeachでleafを作り上げていく訳ですが

今の作りのままではleafは自分よりも先に画面上にあるleaf(頭についてる番号が小さいやつ）しか親にできません。(全てのleafが親座標の相対座標で配置されるので)

これはこれで、親子関係の順番をある程度正しい順序に制限することができるのでよしとします。

## leafの削除機能

まず、treetop以外のleafにDELETEボタンをつけました。

しかし、子leafをもっているleafを削除してしまうと削除したleaf以下が全非表示になってしまいます。(仕様です。)

なので、子leafがあるかどうかでDELETEボタンを付けたり付けなかったりする必要がありました。

動的にDELETEボタンを追加するよりも動的に削除する方が良さそうだった為。
[jQueryでappendした要素を削除する方法を解説！](https://qumeru.com/magazine/438)

もともとあったマウスオーバーの処理に`子leafがあればDELETEボタンを削除`する処理を追加。

remove()が.on()のイベント起点で発火する必要があった為、何かしらのイベントで動かす必要がありました。

```
//マウスオーバーでid取得
  $(function(){
    $(document).on({
      'mouseenter' : function(evt) {
        this_id = $(this).attr('id');
        console.log("this_id" + this_id);
        // 子leafがあればDELETEボタンを削除
        let i = this_id;
        outside:{
          while(i<=layer_id){
            let p = $("#parent" + i + "").val()
            if(p == this_id){
              $("#del-id" + this_id + "").remove();
              break outside;
            }
            i++;
          }
        }
      }
    }, '.leaf');
  }) 
```

現在の仕様ではleafは自分より大きな番号だけが子になるのでマウスオーバーしてる要素のIDをthis_idで取得して、一番最後に作られたleafの番号がlayer_idとして残っているのを利用し、whileで回して画面上にhiddenで保存されているparent_idに自分のIDがあれば子leafがあるということでDELETEボタンを削除します。

次に、deleteボタンを押した時の処理を追加

```
//DELETEボタンを押したときにleafを削除
  $(document).on('click', '.del', function(evt){
    if(window.confirm("Are you sure you want to delete this leaf?")){
      $.ajax({url: "layers/" + layer_id + "", type: "POST", data: {layer_id: this_id, user_id: fk_user_id, tree_id: fk_tree_id, "_method": "DELETE"}})
    }
    stopPropagation()
  });
```
if文に[window.confirm](https://developer.mozilla.org/ja/docs/Web/API/Window/confirm)を使って分岐させてます。
trueならajaxでdestroyさせてます。

これでうまくいった訳ですが、1leaf削除ごとに次のconfirmが++していく謎のバグがありました。

## confirmが++される謎のバグ
これを止めているのがたぶんstopPropagation()です。
最初はfunctionの引数でevt.stopPropagation()にしてたんですが、謎バグが止まりませんでした。
stopPropagation()にしたら止まったんですが、evtつけて止まってる別の処理との区別がまだつきません。

勉強しておきます。

## ツリーの公開非公開機能
ツリーの公開非公開機能を追加したのでご紹介です。

tree.index(All_trees)からよびだした_tree.html.erbに`<% if tree.state == "public_tree" %>`で公開するツリーを選ばせます。

```
<% if tree.state == "public_tree" %>
  <div class="card">
    <div class="caption"><%= link_to tree.title, tree_layers_path(tree), data: {"turbolinks" => false}, :style => "color: black;" %></div>
    <div>Description:<%= tree.description %></div>
    <div>Author:<%= User.find(tree.user_id).user_name %></div>
    <div>Leaf:<%= tree.leaf_count %></div>
    <div>Updated_at:<%= l tree.updated_at, format: :long %></div>
  </div>
<% end %>
```
それとは別にマイページ用のshared/_treeを用意

```
    上と同じ表示部分に以下を追加
  <% if current_user.id == tree.user_id %>
    <%= link_to "edit", edit_tree_path(tree), class: "card float-left" %>
    <% if tree.state == "public_tree" %>
      <%= link_to "public_tree", tree_path(tree, params: { tree: { state: "private_tree" }}), method: :patch, class: "card" %>
    <% elsif tree.state == "private_tree" %>
      <%= link_to "private_tree", tree_path(tree, params: { tree: { state: "public_tree"}}), method: :patch, class: "card" %>
    <% end %>
    <%= link_to "delete", tree_path(tree), class: "card float-right", method: :delete, data: { confirm: 'Are you sure you want to delete this tree?' } %>
  <% end %>
</div>

```
if文で分岐させたlink_toでupdateを呼んでprivateとpublicを切り替えてます。

## urlの直打ち禁止
ツリーの公開非公開機能を追加したことでurlの直打ちを防ぐ仕組みが必要になりました.

```
  def index
    tree = Tree.find(params[:tree_id]) 
    # 公開ツリー または ユーザーがauthor
    if tree.state == "public_tree" || current_user&.id == tree.user_id
      gon.layers = tree.layers.all.order(:layer_id)
      gon.current_user = current_user&.id
      @author = User.find(tree.user_id)
    else
      redirect_back(fallback_location: trees_path)
    end
  end
```
公開ツリー または ユーザーがauthorの処理を追加しました。
それ以外をAll_treesに戻すようにしています。

## leafのparent_id入力ミスると非表示になる問題が解決した件について
leafが親leafの相対座標で配置される&自分より先に画面上に配置されたleafしか親にできない仕様の関係で、解決しないといけない問題がありました。
parent_idを自分より大きな数値にすると非表示になったり、画面左上に表示される問題です。

で、解決しました。

```
 //parent_idが修正された時に数値をチェックする
  $(document).on('change', '.parent', function(){
    var p = $("#parent" + this_id + "").val();
    if(Number(p) >= this_id){
      $("#parent" + this_id + "").val('');
    }
  });
```
.on('change')で入力欄が変更されたタイミングを取得。
parent_id入力欄に自分のIDと同じ&より大きい数値を入力すると入力欄が空になるように変更。
placeholderに『"Enter smaller than ' + layer_id + '"』と入力したメッセージが見えるようにしました。

validates :parent_id, presence: trueで空の入力を弾いて変更を防ぎます。

## 検索機能について
All_treesについてはgem ransackを使って普通にtitle、descriptionで絞り込む機能をつけました。

で、問題だったのがleaf側の検索機能ですが、例に漏れずこちらも仕様の問題でうまくransackで検索機能をつけることができなかったのでオリジナルです。

leafのbodyにlike検索をかけて、hitしたleafのbackground-colorを変更して自分の探したい記述がすぐに見つかるようにしています。

```
def index
    tree = Tree.find(params[:tree_id]) 
    # 公開ツリー または ユーザがauthor
    if tree.state == "public_tree" || current_user&.id == tree.user_id
      gon.layers = tree.layers.all.order(:layer_id)
      gon.current_user = current_user&.id
      @author = User.find(tree.user_id)
      gon.search = tree.layers.where('body like ?', "%#{params[:search]}%")
    else
      redirect_back(fallback_location: trees_path)
    end
  end
```
indexに

```
gon.search = tree.layers.where('body like ?', "%#{params[:search]}%")
```
を付けます。

```
  <div class="center">
    <%= form_with url: tree_layers_path, method: :get, local: true do |f| %>
      <%= f.search_field :search, placeholder: 'body search', class: "search" %>
      <%= f.submit value: 'search', class: "button" %>
    <% end %>
  </div>
```
検索エリアを作成。
methodがPOSTで送られてしまうので、GETを指定。

```
//読み込み完了時にleaf_countを送信
  window.onload = function () {
    if(gon.current_user){
      var leaf_count = $('.leaf').length;
      $.ajax({url: "/trees/" + fk_tree_id +"", type: "POST", data: { tree: { id: fk_tree_id, leaf_count: leaf_count }, "_method": "PATCH"}});
    }
    //検索にヒットしたleafを色変更
    $.each(gon.search, function(index, elem){
      var leaf = document.getElementById("" + elem.layer_id + "");
      $(leaf).css('background-color','#dcedc1');
    })
  }
```
window.onloadに`検索にヒットしたleafを色変更`を追加。

で、とりあえず探したい記事はどのleafに書いてあるのか探せるようになりました。

## ページネーション
kaminariを使ってページネーションを作りました！

All_treesとMy_pageに作ったんですが、All_treesの非表示のツリーがカウントされてしまい、1ページごとの表示tree数にばらつきが出る問題が発生。

```
  def index
    @q = Tree.ransack(params[:q])
    @trees = @q.result.order(:id).page(params[:page]).where(state: "public_tree")
  end
```
indexにwhereでpublic_treeだけを取得する方法に変更しました。
find_byではエラーが出るのでwhereを使うのがポイントです。

## とりあえずタイトルがカッコよくなった話
タイトルが回転してカッコよくなりました。(下記リンク参考)
[文字を円形状に自動で並べ3D回転する（水平・垂直・斜め）方法 CSSとjQueryで実現](https://b-moon.net/characters-circle-rotated-3d/)
[![Image from Gyazo](https://i.gyazo.com/821a284a2e87bcd02684f0cd515ccaf0.gif)](https://gyazo.com/821a284a2e87bcd02684f0cd515ccaf0)

コピペで動かそうとして1日かかってしまったので共有します。

まずhtml、css、jsを自分の環境に合わせて感謝の気持ちを込めながらコピペしました。

で、最初につまずいたのがjsがうまく動かない問題でした。
完結にいうとfunction(){}などを使ってイベントの発火タイミングを作る必要がありました。
onloadでもhoverでも自分のイメージ通りに動くような処理を追加すればいいと思います。

その次に`spanタグ`が処理に使われている関係でページネーションがバグりました。

これは`spanタグ`の部分を`<div class="move">`に書き換えることで対応しました。

よし、うまくいったなと思ったら
# 入力欄のエレクトリカルパレードや！
[![Image from Gyazo](https://i.gyazo.com/4948300eba210b04fb15d71dc8757019.gif)](https://gyazo.com/4948300eba210b04fb15d71dc8757019)

となっていたので、js、cssを全て個別に読み込む必要が出てきました。

## ページごとにjs、cssを読み込む方法
[【Ruby on Rails】ページごとに、読み込まれるCSSとJavaScriptを変えるには？(Rails5まで)](http://www.code-magagine.com/?p=4609)
[【Ruby on Rails】アセット(CSS,JavaScript)やマニフェストファイルの仕組み(Rails5まで）](http://www.code-magagine.com/?p=4602)
require tree .に頼っていた部分を全部個別にrequireすることで解決できました。

## herokuにpushできなくなった件について
タイトルをカッコよくして見える問題も全部解決したので、herokuにpushしたらエラーが出ました。

```
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
```
プリコンパイルで問題が発生している模様。
[【Rails】ES6が原因でHerokuへのデプロイ失敗 Uglifier::Error: Unexpected token:~](https://qiita.com/terufumi1122/items/27bf288414569e13e050)
　上記サイトと同じエラー文を発見したので

```
  config.assets.js_compressor = Uglifier.new(harmony: true)
```
と変更したが同じエラー文が表示される。

```
Uglifier::Error: Unexpected token: keyword (const). To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).
remote:        --
remote:          1 $(function () {
remote:         =>   const before = $('.text');
remote:          3   const text = before.text(); // 文字をspanタグで囲む前のテキストを取得
remote:          4   const textArray = text.split('');  // 取得したテキストを1文字ずつに分割し配列にして変数に格納
remote:          5     
remote:          6   let after = '';
remote:          7   $.each(textArray,function(index,val){ // 配列の各文字をspanタグで囲み、繋げていく
remote:          8     after += "<h1>" + val + "</h1>";
remote:          9   });  
remote:         10     
remote:        ==
```
[heroku run rails db:migrateの時にpg is not part of the bundle](https://qiita.com/jpmogul/items/b651d2664d6055cc6bf9)
他にも思いつく限り手を尽くしてみたがうまく解決できなかったので大人しく他のページのクラス名を変更して対処してます。

## パスワードリセット機能が動かない問題。
15日公開のこの記事のために14日までになんとか動かしておこうと思ったパスワードリセット機能が動いていません。
なので走り書きです。

まずは普通にRUNTEQのカリキュラム通りにパスワードリセット機能をつけました、メーラーとか使って。
その後、herokuにpushして動くか確認しようと思ったらまたプリコンパイルエラーがでました。
[[Rails] herokuデプロイエラー　Precompiling assets failed.](https://qiita.com/koda_7932/items/63061c197aabf1dafb0e)
この記事を参考にコメントアウトしたらとりあえずpushできました！

で、肝心のメールが送られるかどうか確認したところ`Completed 500 Internal Server Error`というものがでます。

最初にそもそもproduction.rbの設定を忘れていたので見よう見まねで設定し、その後heroku addonsの`sendgrid`というものが必要だというところにたどり着きます。

[RailsでHerokuのSendGridアドオンを使ってみる](https://sprink.hatenablog.com/entry/2018/02/28/211223)
[Twilio SendGrid](https://devcenter.heroku.com/articles/sendgrid#provisioning-the-add-on)
この辺を頼りにとりあえずsendgridを導入しました。

でproduction.rbに記述する内容がいまいち分からなくてつまずいてます。(`Completed 500 Internal Server Error`いまだ健在です。)

たぶん、あと2日くらい調べればいける気がするのでそれまでパスワードリセット機能は使えません。（すいません）

## 　パスワードリセットが動いたので手順書き残し
とりあえず一通りのパスワードリセットが動作確認出来たので書きます。
[Rails Heroku で運用しているサイトで SendGrid を2段階認証にし API KEY を利用して SMTP 方式で Mail を送信する](https://qiita.com/1024xx4/items/04ee9b9028615a5d0693)

まずproduction.rb

```
config.action_mailer.default_url_options = { host: 'untangled-pasta-trees.herokuapp.com' }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
  :user_name => 'apikey',
  :password => ENV['SENDGRID_API_KEY'],
  :domain => 'herokuapp.com',
  :address => 'smtp.sendgrid.net',
  :port => 587,
  :authentication => :plain,
  :enable_starttls_auto => true
  }
```
間違えていた部分がuser_name,passwordの部分です。

apiでログインする場合は:user_name => 'apikey'の`apikey`の文字列は固定です！
で、パスワードは

```
$ heroku run config:set SENDGRID_API_KEY=*************************************
```
のようにherokuの環境変数にセットして:password => ENV['SENDGRID_API_KEY']で呼び出して使います。

```
$ heroku config
```
でherokuの環境変数を確認出来ます。

その次にusermailerのdefault from:のところをsendgridに送信者登録したメールアドレスにします。
これをやらないとエラーで送信できません。

## 雪のように文字を降らせる方法
topページに新しく動きのあるデザインを追加したので覚書です。
[![Image from Gyazo](https://i.gyazo.com/ad701f352fae6ce621e852e3d02c45c4.gif)](https://gyazo.com/ad701f352fae6ce621e852e3d02c45c4)

script

```
//画面サイズを取得して代入
var availWidth = window.screen.width;
// 2つの値の間のランダムな整数を得る
  function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
  }
  // アルファベットをランダムに１文字変数に代入する
  var particle = function particle() {
    // 生成する文字列の長さ
    var l = 1;

    // 生成する文字列に含める文字セット
    var c = "abcdefghijklmnopqrstuvwxyzABΓΔEZHΘIKΛMNΞOΠPΣTΥΦXΨΩ1234567890";

    var cl = c.length;
    var r = "";
    for(var i=0; i<l; i++){
      r += c[Math.floor(Math.random()*cl)];
    }
    var x = getRandomInt(0, availWidth)
    var size = getRandomInt(10, 30)
    $('body').append('<div class="particle text-blur" style="font-size: ' + size + 'px; left: ' + x + 'px;">' + r + '</div>')
  }
  var i = 1;
  var particleLoop = setInterval(function() {
    particle()
    i++;
    if(i > 200) clearInterval(particleLoop);  
  }, 100);
```
css

```
div.particle{
  animation: particle 20s linear 0s infinite normal ;
  z-index: 3;
  position: absolute;
  top: 0px;
  pointer-events: none;
}

@keyframes particle {
  0%{
    transform: translateY(0px);
    opacity: 0;
  }
  5%{
    opacity: 0.2;
  }
  95%{
    opacity: 0.2;
  }
  100%{
    transform: translateY(90vh);
    opacity: 0;
  }
}
```

まず、var availWidth = window.screen.width;で画面幅を取得。(avail使って無いですが、変数名そのままです。そのうち変えます。)

取得した画面幅をランダム関数に入れてy座標をランダムに指定させます。

その後、文字をランダムに１文字取得してbodyにappendします。

これで画面上にランダムな文字を出現させます。

setIntervalを使って0.1秒に１文字出現させます。

で、重くなりすぎないように200文字出現させたところでループを止めます。

出現した文字をアニメーションで動かせば完成です！

absoluteで指定した要素は100%のような書き方で動かなかったので、

px表示やvh,vwなどの要素で移動距離を指定します。

100vhとすると縦幅100%と同じように機能します。（たぶん）

## 半角全角で文字数制限を変更するvalidator
レイアウトを崩さないようにtreeのtitle、descriptionに10文字までの文字数制限をつけましたが、英語で10文字は少ない！半角だし20文字までにしたい！と思い検索しました。

[全角1文字を半角2文字としてカウントするvalidator](https://nkmrshn.hatenablog.com/entry/20110704/1309775411)
[【決定版】Railsで個別の属性を検証するカスタムバリデーションを作る](https://qiita.com/youcune/items/74f7c0ab0784519124c5)

上記サイトを参考に追加しました。

ずっとUnknow validationみたいなエラー出ててカスタムバリデーションの読み込み方間違ってるんかなと愚直に素直にエラー追ってたらmaximu`m`のスペルがmaximu`n`になっててエラーメッセージ全然関係ないやんってなりました。

たまにはエラーメッセージを疑うことも必要だなって思いました。


## 最後に
ここまでお付き合いいただきありがとうございました！
初心者だからこそつまずく些細なポイントに焦点をあてて書いてみました！
ふわふわした文章だったと思いますが、とりあえず良いアウトプットをさせていただきました！
これからポートフォリオを作る方は色々不安もあると思いますが、自分が心がけていたことは
『大まかな計画を立てたら、すぐ実験する。』
ということです。
机上の空論にならないように疑問を持ったらすぐにコード書いて実験する方がいいです！
自分もまだまだポートフォリオ作りかけなので頑張ってたくさんコード書こうと思います！