def create(conn, params) do
IO.inspect(params)
end
雖然知道會從 params 拿到表格的內容,但是還是先用 IO.inspect 來看一下他的結構。
在新增完有 IO.inspect(params)
的 create 函式後,我們可以在瀏覽器上送出表格,並且在伺服器的終端機上看到以下的結果:
%{
"_csrf_token" => "FAQfBlEDNBERJz8CAFIuCAwBHgwmAA0nuOY_aeGccKgWU4LOXBmKeoUf",
"note" => %{"content" => "asdfasdf"}
}
在伺服器端的 log 常常會被其他訊息淹沒,可以加上 label 來方便查詢,如: IO.inspect(params, label: "我們要找的 params")
除了用來確保不是偽造請求 csrf token 之外,可以看到我們需要的筆記資料就在 params map 的 "note"
key 裡面。
因此可以這樣使用函式的 pattern matching 取出
def create(conn, %{"note" => note_params}) do
# 獲得 note_params
end
得到 parmas 之後,就可以呼叫先前完成的 Notes.create_note 函式來新增筆記了
def create(conn, %{"note" => note_params}) do
Notes.create_note(note_params)
end
如果在瀏覽器上送出表格卻出現這樣的錯誤
這是因為我們並沒有在提供正確的回傳值給 create 函式,如同錯誤訊息所說 expected action/2 to return a Plug.Conn
通常在 post 之後,我們會希望瀏覽器重新導向到其他頁面,這時候就可以使用 redirect
函式來完成
def create(conn, %{"note" => note_params}) do
Notes.create_note(note_params)
redirect(conn, to: ~p"/notes")
end
雖然這樣就可以新增,而且新增後也會把網頁導向 note 列表,但是會發現有幾個問題:
如果沒有輸入內容就送出,雖然沒有新增空白的筆記,但是也直接導向 note 列表了,如果不知道不能新增空筆記的使用者會很困惑。
如果新增成功,我們並沒有給使用者任何提示,使用者只能從列表中找尋剛剛新增的內容來知道自己是不是有成功的送出。
列表中的筆記順序是按照 id 排序的,而不是按照新增時間排序的,如果列表一長,使用者就很難找到剛剛新增的筆記。
我們的 Notes.create_note
函式會有兩種結果,第一個是成功的 {:ok, note}
,第二個是失敗的 {:error, changeset}
,因此我們可以用 case 來處理這兩種情況。
def create(conn, %{"note" => note_params}) do
case Notes.create_note(note_params) do
{:ok, _note} ->
redirect(conn, to: ~p"/notes")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
在上面的程式碼中,如果我們新增成功,就把頁面導向 note 列表,這與剛剛的步驟一樣。
如果失敗時,因為回傳的是 changeset,所以我們再一次的使用 render
函式,讓表格使用目前的 changeset 來顯示錯誤訊息,並保留了目前輸入的資料。
以我們目前的表格資料很少所以沒有太大的差別,但如果是欄位較多的表格,好不容易送出了卻因為一個欄位的錯誤而被清空,會相當不便。
現在我們試著送出一個空白的表格,會發現瀏覽器會顯示錯誤訊息。
我們可以在 conn 中加入 flash 訊息,讓使用者知道自己成功的新增了筆記。
def create(conn, %{"note" => note_params}) do
case Notes.create_note(note_params) do
{:ok, _note} ->
conn
|> put_flash(:info, "新的感激筆記建立成功")
|> redirect(to: ~p"/notes")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
在 redirect 之前,我們先提供 put_flash 讓 conn 加上 flash 訊息。
conn # 目前的連線
|> put_flash(:info, "成功") # 加上 flash 訊息
|> redirect(to: ~p"/notes") # 導向 notes 列表
在瀏覽器上送出表格,會發現成功新增後,瀏覽器會顯示 flash 訊息。
我們先前在顯示列表時,是按照 id 排序的,這樣的話,如果我們新增了一筆筆記,就會被放在最後面,如果列表很長,就很難找到剛剛新增的筆記。
可以調整 Notes.list_notes
來把順序對調一下
原本是直接使用 Repo.all
找出
def list_notes do
Repo.all(Note)
end
加上排序並調整一下寫法
def list_notes do
Note
|> order_by(desc: :id)
|> Repo.all()
end
再把 Note 傳給 Repo.all
之前,先加上 order_by
並提供使用 id 欄位來倒序(desc)
我們現在多了一頁可以增加筆記的表格,但使用者不會知道 notes/new
這個網址,所以我們要在 notes/index
中加上連結。
修改一下 lib/gratitude_web/controllers/note_html/index.html.heex
的 header 標籤
<header class="my-4 border-b-4 border-amber-600 flex justify-between items-center">
<h1 class="text-xl font-bold text-amber-800">🙏 所有的感激筆記</h1>
<a
href={~p"/notes/new"}
class="text-amber-800 rounded-lg border-amber-700 py-1 px-2 m-2 bg-amber-300"
>
📝 新增感激筆記
</a>
</header>
在 new 頁面中 (新增 note 的表格頁面),我們也要加上返回列表的連結,這樣沒有要新增筆記的使用者才能回到列表頁面。
修改 lib/gratitude_web/controllers/note_html/new.html.heex
的表格
<.form :let={form} for={@changeset} action={~p"/notes"}>
<.input field={form[:content]} />
<div class="flex justify-between items-center mt-4">
<a href={~p"/notes"} class="text-amber-800 rounded-lg border-amber-700 p-2">
返回
</a>
<button class="text-amber-800 rounded-lg border-amber-700 px-2 py-1 bg-amber-300">送出</button>
</div>
</.form>