ASTRO Camp Day30 - RUBY(8)
RUBY 第八堂課
1、軟刪除
什麼是軟刪除??
假設今天要刪除許願卡,正常要刪除這個卡片,就是直接對這個卡片destroy就好了,不過通常我們在做刪除的時候
都不會讓資料真的消失,那要怎麼辦到呢?要怎麼讓消費者覺得真的有刪掉東西,但是其實是假的,資料庫其實還是有那一筆資料
要怎麼做到呢?
首先我們在在許願卡身上多開一個is_deleted的欄位,此欄位的預設值是false,假設今天許願卡被刪掉了,我們更改的
是這個欄位,把此欄位改成true,之後我們撈資料,要記得撈所有的許願卡,該欄值為false的許願卡,要不然會把已經
被刪除(is_deleted: true)的許願卡也撈出來喔!!!!!!
> def index
> @wishlists = WishList.all -> 撈全部資料的時候,要記得撈is_deleted: false的許願卡
> end
撈資料範例,這樣就可以撈出已經被刪掉的資料(假裝被刪掉)
> def find_wish_list
> @wish_list = current_user.wish_lists.find_by!(id: params[:id], is_deleted: false)
> end
不過我們今天換一種軟刪除的方法,在欄位上面加這個 給一個刪除的時間欄位,預設值給空,這種方式的好處是,可以知道該筆資料啥時被刪除了
> t.datetime "deleted_at", default: nil
1-1、第一步驟:開欄位
那接下來我們就來開欄位了
$ rails g migration add_deleted_at_to_wishlist
migration的內容,增加欄位並且加上index
class AddDeletedAtToWishlist < ActiveRecord::Migration[6.1]
def change
add_column :wish_lists, :deleted_at, :datetime
add_index :wish_lists, :deleted_at
end
end
幫欄位加上index的好處是什麼??
可以幫助加速查找資訊
接下來具現化表單
$ rails db:migrate
為許願卡加上欄位後,要來處理刪除功能了,我們預計要做到的是,點下刪除的按鈕/連結,可以把剛剛新增的欄位,變成true的狀態
1-2、第二步驟:更新deleted_at
因此首先到wish_list的controller更改東西
> 點下刪除的時候,去更新這個欄位(直接更新此欄位的時間 -> 意思是刪除的時候,可以知道是啥時刪除的)
> def destroy
> @wish_list.update(deleted_at: Time.current)
> end
> redirect_to root_path, notice: "資料已刪除"
1-3、第三步驟:index過濾刪掉的東西
並且讓前台顯示的東西,我們去抓deleted表格為nil的值(為啥是這樣抓呢?因為只要這個欄位是有被寫進時間的,代表是被刪掉的)
@wish_lists = current_user.wish_lists.where(deleted_at: nil)
1-4、第四步驟:find_wish_list過濾
find方法也要改,讓show..等功能也可以過濾刪除功能
def find_wish_list
@wish_list = current_user.wish_lists.find_by!(id: params[:id], deleted_at: nil)
end
但是我們用第三、四步驟來做篩選,會有一個問題
假設今天專案不是你一個人做的,是很多人協作的,如果有人看不懂你寫的,不小心把沒有過濾好
不小心把沒過濾的假刪除資料給放出去,那不就出大事了嗎!!?
所以我們現在要來做一些嚴謹的設定 -> 使用scope在model來做設定
1-4-1、寫scope
寫scope有兩個好處
(1) 可以重複使用
(2) 可以賦予意義 -> 這個很重要,設定的好,可以一眼就看出來篩選了哪些欄位
到wishlist的model來設定scope
> wishlist model
> **定義not_deleted此scope,意義是沒刪除的許願卡**
> scope :not_deleted, -> {where(deleted_at: nil)}
接下來到controller改寫,只要套用not_deleted,就可以達成過濾的目的,用find_wish_list舉例
> def find_wish_list
> @wish_list = current_user.wish_lists.not_deleted.find_by!(id: params[:id])
> end
Ps. 在model做限制,就可以讓controller都套用
1-4-2、default_scope
但是,我們雖然寫了一個過濾方法,還是有可能會忘記用,我們來直接用default_scope,來強制使用
> 用default_scope 用這種方法,所有搜尋方法都會經過他(find、find_by、where)
> default_scope -> {where(deleted_at: nil)}
再優化一下wish_list destroy的功能,直接再寫一個新的destroy在model,這樣可以讓controller那邊使用 這個destroy是特別的destroy,他可以更新deleted_at(簡單說就是把目前寫得搬到model) 這樣的好處是,可以讓別人看此action,就像是一般的destroy,實際上是有規則的destroy
> WishList的model
> def destroy
> update(deleted_at: Time.current)
> end
controller正常寫法就可以
> def destroy
> @wish_lists.destroy
> end
1-5、paranoia
11:36 paranoia教學 插件paranoia,可以做到我們軟刪除這件事
1-5-1、首先安裝paranoia
$ bundle paranoia
再來直接到WishList的model的model增加這一段,這樣就可以做到軟刪除
class WishList < ApplicationRecord
acts_as_paranoid
# ...下面忽略
end
可以不一定要取名deleted_at 可以自己創造
2、許願卡留言功能
留言功能製作流程
- 先整理留言功能有哪些欄位
> user_id、wish_list_id、deleted_at、content
- 開Comment的model
> rails g model Comment content:text user:references deleted_at:datetime:index wish_list_id:integer
- 開路徑
> resources :comments, shallow: true, only: [:create, :destroy]
- 建表單(先決定留言要在哪留言、誰可以留言),表單post到comment的controller
- comment的create、destroy製作(有很多細節要注意)
- 許願卡show的action,要補上留言的顯示、刪除功能、scope可以直接避免軟刪除的資料不會被別人抓出來
2-1、第一步驟:整理留言的欄位
- belongs_to user
- user_id
- belongs_to wish_list
- wish_list_id
- content: text
- deleted_at:datetime, index (軟刪除、假刪除 soft delete - 前台看不到、後台看不到,但是資料庫還是有)
2-1-1、留言model
欄位名稱 | 資料型態 | 說明 |
---|---|---|
content | text | 留言內容 |
wish_list_id | integer | 哪一張許願卡的留言 |
user_id | integer | 哪個使用者的留言 |
deleted_at | datetime | 假刪除欄位 |
做每一個model得時候,只要專注自己的model就好,要不然會太雜
2-2、第二步驟:開留言的model
> rails g model Comment content:text deleted_at:datetime:index user:belongs_to wish_list:belongs_to
2-3、第三步驟:開留言功能的路徑
首先我們要來想一下,要把留言功能放在哪邊?
雖然我們還沒有做可以讓多人可以看到同一個許願卡的頁面
因此,就先做只有自己能留言的許願卡吧
Ps. 未來要做到可以讓多人對同一個許願卡留言的功能
2-3-1、在show頁面放留言的form
接著來看頁面,想要在wishlist的show頁面放一個表單(form),這個form可以讓大家留言(此show頁面,就是comment的index頁面)
所有我們到wish_list的show.html.erb,來加上留言功能的表單
> 不過我們很快就遇到問題了!!留言表單的model、url是蝦咪
> <%= form_with model: ??, url: ?? do |form| %>
> <% end %>
首先model應該很好找,就是到wish_list的action,把comment的model新創造的實體傳到html.erb這邊
> @comment = Comment.new
不過重點來了!!!!路徑是啥鬼
> 首先我們來看一下目前show頁面的路徑
> /wish_lists/24 # 後面24是許願卡的id(第24張許願卡)
照之前的經驗,我們直接到routes這樣寫,這樣就可以造出關於留言的路徑了
> resources :comments
但這樣留言不就跟許願卡的連結脫鉤了嗎?照rails慣例,應該要改成下面這樣
> resources :wish_lists do
> resources :comments
> end
> end
這樣是兩個model是有連結了,但是遇到下一個問題,就是路徑太雜亂了!!! 用留言的編輯路徑舉例
> routes的路徑長這樣
> wish_lists/:wish_list_id/comments/:id/edit
> 真實連結
> wish_lists/24/comments/2/edit
> 上面連結這樣寫出來應該蠻好認的,就是第24張許願卡的第2則留言(但你看我要拆解成這樣才看得出來,證明太長了)
2-3-2、routes:shallow介紹
有看rails官方文件,看到shallow的應該很好奇那是什麼東西的縮寫
現在馬上介紹一下根據前面有提到,路徑太長會很醜,並且也不好閱讀
所以是可以把一些路徑拆開的(有些可以跟著許願卡連結,有些可以分開)
舉個例子:
假設我今天想要把一則一則的留言show出來,就不需要許願卡的ID路徑,因為我只在意留言的ID
反之像是new、create、index,我就會需要知道他是跟在誰的ID後面
ex. 今天想要new一個新留言,我不知道許願卡的ID,怎麼知道這則留言是誰的
因此可以把分類拆成下面兩段
> resources :wish_lists do
> resources :comments, only: [:new, :create, :index]
> end
>
> resources :comments, only: [:show, :edit, :update, :destroy]
那shallow到底是什麼呢?其實就是上面拆成兩段的縮寫,像下面那樣寫上shallow,他可以自動幫你拆成上面兩段
> resources :wish_lists do
> resources :comments, shallow: true
> end
2-3-3、Comment的action設定
回到原本的許願卡路徑,既然知道路徑會太長會很醜,我們先來決定留言功能有哪些action是需要用到的,決定好有哪些action後,再來寫上路徑
action | 是否需要 | 說明 |
---|---|---|
index | X | 許願卡show = 留言index |
show | X | 除非你會需要把每則留言點開來看 |
new | X | 現在是建在許願卡show的erb上 |
create | O | 一定要 |
edit | X/O | 看情況,看你會不會允許修改留言 |
update | X/O | 同上 |
destroy | O | 通常可以刪除留言 |
使用shallow把剛剛決定好的留言的action、路徑製作好
> resources :wish_lists do
> resources :comments, shallow: true, only: [:create, :destroy]
> end
2-4、第四步驟:建立留言表單
因此可以來把留言的表單寫完了,要特別注意,因為巢狀結構的關係,create的要抓的該許願卡當下的id,下面的表單會用到的@wish_list,真正抓到的是,目前登入者的id
> @wish_list = current_user.wish_lists.find(params[:id])
把表單model、url寫上
> <%= form_with model: @comment, url: wish_list_comment_path(@wish_list) do |form| %>
> <%= form.label :content %>
> <%= form.text_field :content %>
>
> <%= form.submit %>
>
> <% end %>
表單做好後,預期按鈕點下去,會送到comment的controller,因此來建立
建好後,我們先把create表單送過來的資料,印到畫面上
> class CommentsController < ApplicationController
> def create
> render html: params
> end
> end
2-4-1、接下來在controller補上一些方法
- 一定要登入才能留言
> before_action :authenticate_user!
- 先找到該張許願卡
> find_wish_list -> 要使用current_user來找:wish_list_id -> (想知道為啥wish_list_id,可以用params把create印出來)
- 設定留言的強參數
> comment_params
- 到許願卡的model設定
> has_many :comments
create那一段意思是,從許願卡的角度,產生出一個新的留言,並且要帶過濾後的參數進去
Ps. new新留言的時候,記得要帶wish_list_id、user_id進去,要不然會寫不進去
> class CommentsController < ApplicationController
> def create
> @comment = @wish_list.comments.new(comment_params) # 這一行寫進wish_list_id
> @comment.user = current_user # 這一行寫進user_id
>
> if @comment.save
> redirect_to wish_list_path(@wish_list), notice: "留言成功 # 成功存進留言,要返回許願卡show頁面
> else
>
> # app/views/wish_lists/show.html.erb # 要render許願卡的show介面
> render "wish_lists/show"
> end
> end
> end
2-4-2、merge寫法介紹
不過上面那兩條id帶進進去的方式有點難看,來換一種寫法 使用merge可以直接把一個值,塞進hash裡面,並回傳新的hash,該hash是有塞進新值的
一個新hash
> h = { :name=>123, :age=>18 }
原始把id塞進hash的方法
> h[:user] = 1111
> p h # { :name=>123, :age=>18, :user=>1111 }
新方法,使用merge,他可以跟另一個hash合併,並會回傳一個新hash
> h.merge(cc: 123)
> p h # { :name=>123, :age=>18, :cc=>123 }
因此最後的整留言個controller長這樣
> class CommentsController < ApplicationController
>
> before_action :authenticate_user! # 驗證登入才能留言
> before_action :find_wish_list only: [:create]
>
> def create
> @comment = @wish_list.comments.new(comment_params)
> # @comment.user = current_user
>
> if @comment.save
> redirect_to wish_list_path(@wish_list), notice: "留言成功"
> else
> #render "wish_list/show" # 這一行可以修改成下面那樣
> redirect_to wish_list_path(@wish_list), alert: "請填寫留言"
> end
>
> end
>
>
> private
> def find_wish_list
> @wish_list = current_user.wish_lists.find(params[:wish_list_id])
> end
>
> def comment_params
> params.require(:comment).permit(:content).merge(user: current_user) # 使用merge方法塞進去
> end
>
> end
2-5、第五步驟:增加限制validates
對留言的content欄位增加必填限制
> validates :content, presence: true
2-6、第六步驟:把留言顯示出來
已經可以創造新留言後,該來把留言印出來了,首先我們剛剛決定把留言顯示在許願卡的show頁面,也就是新增留言表單的正下方
那要怎麼做呢?首先當然是到許願卡controller的show,把所有留言用實體傳到view
下面的意思是,該許願卡的id,把所有留言抓出來
> def show
> @comment = Comment.new // 這個是設定給form model的變數
> @comments = @wish_list.comments // 這個是抓出所有留言的變數
> end
再來到show的view頁面,用迴圈印出全部東西
> <ul>
> @comments.each do |comment|
> <li><%= comment.content %></li>
> end
> </ul>
2-6-1、留言顯示反轉
使用ord的功能,在留言抓出來的當下就是反轉的
> def show
> @comment = Comment.new
> @comments = @wish_list.comments.order(id: :desc) -> 這樣就可以讓最新的留言在最上面
> end
2-7、第七步驟:優化redirect_to
我們現在是用redirect_to來進行換頁,雖然有turbolinks的幫助,已經感覺不太到有換頁了,但是還是要等一下
因此我們今天想用一個更快的方式,當使用者留完言後,我們不要用重導的方式回網頁,我們用Ajax的方式
把新增的留言透過JS,寫進留言區的欄位,這樣不就不用redirect_to了嗎!!!!
2-7-1、用create.js.erb設定
要做到這一件事,首先在controller那邊要先動一點手腳,當我們在做create的時候,通常會在後面加上重導或者是render
但是假設今天我沒有給controller這兩樣呢?
如果兩樣都沒有給的話,controller就會去view的地方,找這個檔案 -> create.html.erb
不過今天我們小改一下檔案名稱,把html改成JS,最後名稱就變這樣 -> create.js.erb
這樣我們就可以在這個檔案上面寫JS了
2-7-2、form設定改local: false
不過這樣還不夠喔!
還要處理表單送出那邊的設定,我們要把表單送出的資料,變成是由XML送出去的,因此我們要這樣改
把表單那邊的model,加上這一段 -> local: false,記得要加在model的小括號裡面
> <%= form_with(model: @comment, url: wish_list_comments_path(@wish_list), local: false) do |f| %> -> 加在這邊
> <div>
> <div>留言:</div>
> <%= f.text_area :content %><br />
> <%= f.submit "新增留言" %>
> </div>
> <% end %>
2-7-3、JS設定
接著我們可以開始寫JS了
(1) 先抓到ul的class(先前有幫他加上class了)
(2) 寫一個li,li包住的是留言的內容
(3) 把這個li用insertAD塞進ul裡面
(4) 記得要多寫一個,當使用者留言完,要把該區塊清空
> var comments = document.querySelector(".comments")
> var li = `<li><%= @comment.content %></li>`
>
> comments.insertAdjacentHTML("afterbegin", li)
> document.querySelector("#comment_content").value = ""
2-8、第八步驟:留言功能的軟刪除
接下來把軟刪除掛上comment的model
> acts_as_paranoid # 這個寫在comment的model上
2-8-1、加上刪除的按鈕
掛上去後,可以在show頁面加上一個刪除按鈕,刪除的路徑是comment的destroy
> <% @comments.each do |comment| %>
> <li>
> <%= comment.content %>
> <%= link_to "刪除", comment_path(comment), method: "delete", data: {confirm: "sure????"} %>
> </li>
> <% end %>
寫好link_to,來寫destroy的action,刪除那邊要導回去的路徑,是每一張卡片show頁面,因此後面帶的id,可以用留言、許願卡的關聯去找
因此我們先來寫關聯,每一個user都有很多留言
> User model
> has_many :comments
一則留言是屬於一個許願卡的
> comment model
> belongs_to :wish_list
寫好關聯後,來寫comment的controller
> before_action :find_comment, only: [:destroy]
>
> def destroy
> @comment.destroy
> redirect_to wish_list_path(@comment.wish_list), notice: "留言刪除" # 刪除後,重導回許願卡的show頁面
> end
>
> def find_comment # 先找出這則留言的使用者是誰(寫法上是用使用者來找留言)
> @comment = current_user.comments.find(params[:id])
> end
2-8-2、用render partial來寫留言功能
下面這一段的意思是,把@comments轉出來的所有留言,設定成comment,並把此comment傳給_comment.html.erb
> wishlist show
> <% @comments.each do |comment| %>
> <%= render "comments/comment", comment: comment %>
> <% end %>
> Ps. 這樣寫只是換用選染的方式顯示喔!!還不會讓用JS新增的留言就有刪除功能
rails有一個慣例
假設今天render partial是這樣 <%= render “comments/comment”, comment: comment %>
可以簡寫成這樣 <%= render comment %>
2-8-3、render collection(很重要)
不過這樣寫會有另外一個問題,效能很很爛,rails有說不建議再迴圈裡面使用render partial
Ps. 因為目前是在迴圈裡面render,我們要來改另外一個寫法 - render collection
> <%= render partial: "comments/comment", collection: @comments, as: :comment %>
> 1. 前面是指我要帶進的檔案
> 2. collection是指我有一群東西(迴圈再轉的)
> 3. as是指我要帶進去的變數名稱,通常不用寫(記得要用符號或字串表示)
> 上面是完整型態,可以直接這樣寫就好
> <%= render @comments %>
2-8-4、在create.js.erb寫render
原本寫法var li的寫法是寫一段留言的內容,並塞進留言欄位,之後我們來用render寫法試試
> create.js.erb
> var comments = document.querySelector(".comments")
>
> var li = `<li><%= @comment.content %></li>`
>
> comments.insertAdjacentHTML("afterbegin", li)
> document.querySelector("#comment_content").value = ""
改寫這段(這是原始欄位)
> var li = `<li><%= @comment.content %></li>`
改成這個 - 記得要用`才能執行,這個寫法的意思是
> create.js.erb
> var li = `<%= render "comments/comment", comment: @comment %>`
或者是用一樣用雙引號,但是加上J,這個J是跳脫符號,可以幫你加上反斜線
> var li = "<%= j render "comments/comment", comment: @comment %>"
上面render partial的檔案內容是這個
共同的東西都寫在_comment.html.erb這個資料夾,並且把他那一部分渲染過來,渲染過來的時後,裡面的comment變數是@comment,這樣寫的好處是,不管li寫的在複雜,我都可以把li透過render的方式渲染出來
> _comment.html.erb
> <li>
> <%= comment.content %>
> </li>
2-8-5、ajax處理刪除
前情提要
destroy等等會用到@comment,先把它寫出來怕忘記
> def find_comment
> @comment = current_user.comments.find(params[:id])
> end
2-8-6、link_to掛上remote: true
前面我們是用普通的rails刪除,今天我也想把刪除功能,跟剛剛create一樣,是用xhr刪除,那要怎麼做呢?
首先到刪除的連結那邊,這時候可以幫link_to掛上使用XML送的方式(remote: true)
> <% @comments.each do |comment| %>
> <li>
> <%= comment.content %>
> <%= link_to "刪除", comment_path(comment), method: "delete", data: {confirm: "sure????", remote: true} %>
> </li>
> <% end %>
接著一樣使用把原本的html.erb,改成destroy.js.erb,並在這邊寫刪除li的JS,這樣就可以做到用XHR刪除留言了
Ps. dom_id等等會提到
> var li = document.querySelector("#<%= dom_id(@comment) %>")
> li.remove()
2-8-8、_comment檔案增加刪除
不過還記得我們剛剛新建的_comment資料夾嗎?我們把刪除的連結加進這個資料夾
這樣就可以在新增留言的時候,刪除的按鈕就一起出來了,原因就是這一包檔案裡面的資料在你新增留言時,都會一起渲染出來
> _comment.html.erb
> <li>
> <%= comment.content %>
> <%= link_to "刪除", comment_path(comment), method: "delete", data: {confirm: "sure????"} %>
> </li>
2-8-9、dom_id vs comment_<%= comment.id %>
在介紹dom_id前,先介紹不用dom_id的話,我們要如何為要被刪除的留言,附上各個留言的編號
首先我們先假設,我們每一個刪除的id,他的id是”comment_??”,??是有規律的數字 到時候去抓留言的id,只要抓到此id就可以刪掉了
> _comment.html.erb檔案(這邊的區域變數,是從別的地方送過來的,要記得)
> <li id="comment_<%= comment.id %>"> # 預期是"id = comment_2 之類的"
> <%= comment.content %>
> <%= link_to "刪除", comment_path(comment), method: "delete", data: {confirm: "sure????", remote: true} %>
> </li>
接下來可以到destroy.js.erb,抓住id並刪掉,用@comment.id,來抓到指定要刪除了留言
> document.querySelector("#<%= @comment.id %>").remove()
2-8-10、dom_id
不過這邊有個特別的寫法,叫做dom_id,這個helper會幫我們長出一段id出來,可以實際把它印出來看看,他會自動幫你長出id
> _comment.html.erb檔案(這邊的區域變數,是從別的地方送過來的,要記得)
> <%= dom.id(comment) %> #這邊可以寫區域變數的原因是因為別的地方送變數過來喔
> comment_54
> comment_53
> comment_52
> comment_51 ...... 等等
這樣我們就可以使用dom_id來寫了,用這個寫就可以不用自己串字串 -> ex. comment_id
> _comment.html.erb
> <li id="<%= dom_id(comment) %>">
> <%= comment.content %>
> <%= link_to "刪除", comment_path(comment), method: "delete", data: {confirm: "sure????", remote: true} %>
> </li>
destroy也適用dom_id改寫
> destroy.js.erb
> document.querySelector("#<%= dom_id(@comment)%>").remove()
3、stimulus JS
首先在show頁面,加上一個按鈕
> <button id="btn">
> NO
> </button>
並且在同一個頁面最下面寫script,簡單的JS,點按鈕後,印出hi
> var btn = document.querySelector("#btn")
> btn.addEventListener("click", (e) =>{
> e.preventDefault()
> console.log("hi")
>
> })
不過JS放在同一個資料夾非常的醜,要放到哪呢??rails有一個放JS的地方
JavaScript檔案夾裡的packs -> application.js,我們就把檔案搬過去
不過搬過去後,發生了另外一個問題 -> script先後渲染問題
所以我們要加一段DOMContentLoaded,這樣可以讓事件發生在網頁載入後
> document.addEventListener("DOMContentLoaded", () =>{
> var btn = document.querySelector("#loveBtn")
>
> if (btn) {
> btn.addEventListener("click", (e) => {
> console.log("hi");
> })
> }
>
> })
記得看turbolinks的深度解析
要在rails寫JS,遇到兩個大問題
(1) JS寫哪裡
(2) 怎麼面對Turbolinks的生命週期
Stimulus.js可以解決上面兩件事,這個是一個微框架
這一段是Stimulus.js官網的CODE,來解析一下,這樣寫,之後整段的生命週期都交由hello_controller管理
> <div data-controller="hello"> # 在這個div掛controller,之後要創hello_controller的檔案
> <input data-hello-target="name" type="text">
>
> <button data-action="click->hello#greet"> # 當發生click事件,請你去hello_controller找greet的action
> Greet
> </button>
>
> <span data-hello-target="output"> # data-controller名字-target="span名字"
> </span> # 上面這行取代的是querySelector("#id")
> </div>
另一段
> // hello_controller.js # hello_controller資料夾
> import { Controller } from "stimulus"
>
> export default class extends Controller {
> static targets = [ "name", "output" ] # 上面傳下來的target要寫在這
>
> greet() {
> this.outputTarget.textContent = # outputTarget的由來是,只要你有寫target,他會自動幫你生出一個"名字Target"
> `Hello, ${this.nameTarget.value}!`
> }
> }
3-1、實際練習stimulus
首先要引入stimulus的套件,可以這樣打,rails會印出目前內建的一堆外掛
> rails
接著會看到stimulus指令,只要在最前面加上rails就可以成功啟用
> rails webpacker:install:stimulus
跑完之後會在JS那個資料夾下面多一個controller的資料夾,之後stimulus都寫在這個資料夾裡面
下面的connect比較特別的生命週期,他是一個固定寫法,當這個controller掛載到div身上後,就會做connect下面的事情
> import { Controller } from "stimulus"
>
> export default class extends Controller {
> static targets = [ "output" ]
>
> connect() {
> console.log("hello"); # 掛載controller後,就會自動觸發
> }
> }
如何掛載controller??隨便寫一個地方寫上data-controller就可以
> <section data-controller="hello"></section>
3-1-1、webpacker解決打包過慢的問題
這一段執行會有一段時間是正常的,執行的時候會做一堆事情
因此要來請一個新工具,來幫我們打包,會快很多
有一個webpacker的gem,我們要用裡面的webpack工具
使用此指令,再開一個伺服器,可以幫助你打包東西,這樣會快超級多
> bin/webpack-dev-server
3-1-2、foreman一次執行兩個server
foreman這個套件可以一次執行rails s、bin/webpack-dev-server
先安裝foreman後
> bundle add foreman -> 這樣可以裝這個插件,他可以幫助我們一次執行上面兩個東西
到rails建立一個資料夾 - Procfile,裡面放的code為
> web: bin/rails server -p 3000 # 執行rails s
> webpack: bin/webpack-dev-server # 執行webpack
``
最後再執行,這樣就可以一次執行兩個server了
```md
> foreman start/s
3-1、按鈕加上data-controller
把data-controller加在按鈕上,並新建一個資料夾叫做love_btn_controller.js,這邊加的controller,可以幫助把資料傳到JS那邊,並開始寫JS
> <button style= "padding: 10px 20px", data-controller="love-btn">
> no
> </button>
3-2、按鈕加上data-action
我們來在按鈕那邊多增加幾個屬性 - data-action => 被點擊後,此按鈕會觸發gogo的fc
data-action有幾點要注意
(1) click是指對這個按鈕做的事情(就是addEventListener的click行為)
(2) 後面的love-btn就是對到剛剛的controller
(3) 並且最後面會有一個toggle的action,這個用處就是,當你點擊此按鈕,會發生的事情寫在這邊
> <button style= "padding: 10px 20px", data-controller="love-btn"
> data-action="click->love-btn#toggle">
>
> <span>no</span>
> </button>
> toggle() {
> console.log("good");
> }
3-3、目標加上target
接下來幫按鈕裡面的文字增加target,想要等等點擊下去,就可以更改no 這個target很好用,在這邊設定,就可以直接在JS那邊改他的內容
> <button style= "padding: 10px 20px", data-controller="love-btn"
> data-action="click->love-btn#toggle">
> <span data-love-btn-target="text">no</span> # 幫文字增加target
> </button>
3-4、簡單改變文字
加上target後,我們來簡易的改變一下按鈕中的文字(還沒有用到狀態喔)
> import { Controller } from "stimulus"
>
> export default class extends Controller {
> static targets = [ "icon" ]
>
> connect() {
> // console.log("right now");
> }
>
> change() {>
> this.iconTarget.textContent = "Yes" # 直接改變target的文字
> }
> }
3-5、JS存放狀態的變數
在寫stimulus的時候,可以用this來存放物件的狀態 PS. 在ruby可以用實體變數來存放狀態
> import { Controller } from "stimulus"
>
> export default class extends Controller {
> static targets = [ "icon" ]
>
> initialize() {
> this.btnState = false -> 用this來存放狀態,那個btnState可以隨便亂寫(重點是前面的this)
> }
>
> connect() {
> // console.log("right now");
> }
>
> change() {
> // 如果按鈕狀態是true,那就讓他等等狀態變false,這時候按鈕的文字要變no
> if(this.btnState) {
> this.iconTarget.textContent = "no"
> } else {
> this.iconTarget.textContent = "yes"
> }
> this.btnState = !this.btnState -> 切換狀態
> }
> }
3-6、connect VS initialize
controller掛上去,會觸發connect
connect是指當他掛載到某一個DOM元素身上的時候,會發動事情,connect會做很多次stimulus 掛上去的時候,就會觸發initialize initialize是指他剛被new出來,就發生的事情,而且這個只會做一次,這裡很適合放狀態
懶人包:initialize比connect的發生順序還要早
3-7、form掛controller
> <%= form_with model: @user, url: users_path, class: "form", data: {controller: "reservation-form"} do |form| %>
> <% end %>
3-8、form-field掛action、target、掛兩個action
> <%= form.text_field :name, class: 'input-border-click', data: {reservation_form_target: "nameInput",
> action: "input->reservation-form#input blur->reservation-form#nameFieldBlur"} %>