使用PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)技术栈构建一个简化版的Instagram Web应用程序
在第 6 部分中,我们添加了主页,在这部分中,我们将研究顶部标题导航菜单中的搜索功能。您可以赶上Instagram 克隆 GitHub Repo。
搜索功能将提供按用户名或全名搜索用户的能力,我们只需要一个包含头像 URL、用户名和全名的地图,让我们添加一个函数来在我们的帐户上下文中获取它。里面lib/instagram_clone/accounts.ex
:
...
def search_users(q) do
User
|> where([u], ilike(u.username, ^"%#{q}%"))
|> or_where([u], ilike(u.full_name, ^"%#{q}%"))
|> select([u], map(u, [:avatar_url, :username, :full_name]))
|> Repo.all()
end
...
我们将处理该事件以在标头导航组件 open 中进行搜索lib/instagram_clone_web/templates/layout/live.html.leex
,然后将 ID 发送到我们的组件以便能够处理该事件:
<%= if @current_user do %>
<%= live_component @socket, InstagramCloneWeb.HeaderNavComponent, id: 1, current_user: @current_user %>
<% else %>
<%= if @live_action !== :root_path do %>
<%= live_component @socket, InstagramCloneWeb.HeaderNavComponent, id: 1, current_user: @current_user %>
<% end %>
<% end %>
<%= live_flash(@flash, :info) %>
<%= live_flash(@flash, :error) %>
<%= @inner_content %>
在里面lib/instagram_clone_web/live/header_nav_component.html.leex
让我们使用 AlpineJs 打开 UL,当输入至少有一个字母时,我们将在其中显示结果,如果里面没有任何内容或单击输入,则不会显示任何内容。让我们使用phx-change
表单事件来运行我们的搜索,我们还将分配给我们的套接字 a@overflow_y_scroll_ul
以在结果大于 6 时显示滚动条。
在我们将显示搜索结果的 UL 中,我们需要 3 个赋值,@searched_users
这将是我们将循环遍历的结果,@while_searching_users?
这将是一个布尔值,用于确定在连接正常的情况下何时显示加载指示器缓慢或需要一段时间,为了用户界面友好的反馈,@users_not_found?
另一个布尔值显示未找到结果消息。
<%= for user <- @searched_users do %>
<%= live_redirect to: Routes.user_profile_path(@socket, :index, user.username) do %>
-
<%= img_tag Avatar.get_thumb(user.avatar_url), class: "w-10 h-10 rounded-full object-cover object-center" %>
<%= user.username %>
<%= user.full_name %>
<% end %>
<% end %>
<%= if @while_searching_users? do %>
-
<% end %>
<%= if @users_not_found? do %>
- No results found.
<% end %>
我们的更新lib/instagram_clone_web/live/header_nav_component.html.leex
应如下所示:
<%= live_redirect to: Routes.page_path(@socket, :index) do %>
#InstagramClone
<% end %>
<%= for user <- @searched_users do %>
<%= live_redirect to: Routes.user_profile_path(@socket, :index, user.username) do %>
-
<%= img_tag Avatar.get_thumb(user.avatar_url), class: "w-10 h-10 rounded-full object-cover object-center" %>
<%= user.username %>
<%= user.full_name %>
<% end %>
<% end %>
<%= if @while_searching_users? do %>
-
<% end %>
<%= if @users_not_found? do %>
- No results found.
<% end %>
里面lib/instagram_clone_web/live/header_nav_component.ex
:
defmodule InstagramCloneWeb.HeaderNavComponent do
use InstagramCloneWeb, :live_component
alias InstagramClone.Uploaders.Avatar
@impl true
def mount(socket) do
{:ok,
socket
|> assign(while_searching_users?: false)
|> assign(users_not_found?: false)
|> assign(overflow_y_scroll_ul: "")
|> assign(searched_users: [])}
end
@impl true
def handle_event("search_users", %{"q" => search}, socket) do
if search == "" do
{:noreply, socket}
else
send(self(), {__MODULE__, :search_users_event, search})
{:noreply,
socket
|> assign(users_not_found?: false)
|> assign(searched_users: [])
|> assign(overflow_y_scroll_ul: "")
|> assign(while_searching_users?: true)}
end
end
end
在我们的处理事件函数中,首先,我们检查参数是否为空字符串,什么都不会发生。当参数不为空时,我们将发送一条带有搜索参数的消息,以在父 LiveView 中运行搜索,这样我们就可以在搜索时显示加载指示器,每次表单更改时,我们都必须重置我们的分配,设置while_searching_users?boolean true
以在搜索时显示加载指示器。
我们必须发送消息,因为如果我们尝试在标头导航组件套接字中执行此操作,则分配首先同时发生,因此如果我们这样做,我们将无法在搜索时以及在组件,我们无法将handle_info
其消息发送到父级并将父级中的分配更新回组件。
标题导航组件在每个页面上都使用,因此我们不必在每个 LiveView 上处理消息,而是为每个 LiveView 处理一次,在第lib/instagram_clone_web.ex
45 行函数内live_view()
添加以下内容:
...
def live_view do
quote do
use Phoenix.LiveView,
layout: {InstagramCloneWeb.LayoutView, "live.html"}
unquote(view_helpers())
import InstagramCloneWeb.LiveHelpers
alias InstagramClone.Accounts.User
alias InstagramClone.Accounts
@impl true
def handle_info(%{event: "logout_user", payload: %{user: %User{id: id}}}, socket) do
with %User{id: ^id} <- socket.assigns.current_user do
{:noreply,
socket
|> redirect(to: "/")
|> put_flash(:info, "Logged out successfully.")}
else
_any -> {:noreply, socket}
end
end
@impl true
def handle_info({InstagramCloneWeb.HeaderNavComponent, :search_users_event, search}, socket) do
case Accounts.search_users(search) do
[] ->
send_update(InstagramCloneWeb.HeaderNavComponent,
id: 1,
searched_users: [],
users_not_found?: true,
while_searching_users?: false
)
{:noreply, socket}
users ->
send_update(InstagramCloneWeb.HeaderNavComponent,
id: 1,
searched_users: users,
users_not_found?: false,
while_searching_users?: false,
overflow_y_scroll_ul: check_search_result(users)
)
{:noreply, socket}
end
end
defp check_search_result(users) do
if length(users) > 6, do: "overflow-y-scroll", else: ""
end
end
end
...
我们使用在帐户上下文中添加的功能创建一个案例,我们send_update/3添加到标题导航组件中,while_searching_users?
在每个案例上设置为 false,以便在搜索完成时不显示加载指示器。
就是这样,现在你有了一个功能齐全的搜索输入,还有很多事情要做,很多可以添加的功能,但我们已经走了很长一段路,我们有一个值得我们自豪的大应用程序,直到下一个时间。