From 38d2dbfde433f647dbd741ab215f438767401b35 Mon Sep 17 00:00:00 2001 From: Jonathan Gao Date: Tue, 15 Nov 2022 17:37:03 +0800 Subject: [PATCH] fix: Fix linke style. --- .../lib/phoenix_webcomponent.ex | 2 + .../lib/phoenix_webcomponent/appbar.ex | 6 +- .../lib/phoenix_webcomponent/link.ex | 189 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 apps/phoenix_webcomponent/lib/phoenix_webcomponent/link.ex diff --git a/apps/phoenix_webcomponent/lib/phoenix_webcomponent.ex b/apps/phoenix_webcomponent/lib/phoenix_webcomponent.ex index 3e3c8270..f890b7d9 100644 --- a/apps/phoenix_webcomponent/lib/phoenix_webcomponent.ex +++ b/apps/phoenix_webcomponent/lib/phoenix_webcomponent.ex @@ -46,6 +46,7 @@ defmodule Phoenix.WebComponent do def alias do quote do + alias Phoenix.WebComponent.Link alias Phoenix.WebComponent.Actionbar alias Phoenix.WebComponent.Appbar alias Phoenix.WebComponent.Card @@ -68,6 +69,7 @@ defmodule Phoenix.WebComponent do defp components do quote do + import Phoenix.WebComponent.Link import Phoenix.WebComponent.Actionbar import Phoenix.WebComponent.Appbar import Phoenix.WebComponent.Card diff --git a/apps/phoenix_webcomponent/lib/phoenix_webcomponent/appbar.ex b/apps/phoenix_webcomponent/lib/phoenix_webcomponent/appbar.ex index 10718e96..3e5c0f7e 100644 --- a/apps/phoenix_webcomponent/lib/phoenix_webcomponent/appbar.ex +++ b/apps/phoenix_webcomponent/lib/phoenix_webcomponent/appbar.ex @@ -5,6 +5,8 @@ defmodule Phoenix.WebComponent.Appbar do """ use Phoenix.WebComponent, :html + import Phoenix.WebComponent.Link + @doc """ Generates a html customElement appbar. @@ -37,12 +39,12 @@ defmodule Phoenix.WebComponent.Appbar do <%= render_slot(@logo) %> <%= for menu <- @menus do %> - <.link + <.wc_link navigate={menu.to} slot="nav" > <%= menu.label %> - + <% end %> <%= render_slot(@user_profile) %> diff --git a/apps/phoenix_webcomponent/lib/phoenix_webcomponent/link.ex b/apps/phoenix_webcomponent/lib/phoenix_webcomponent/link.ex new file mode 100644 index 00000000..0511b36d --- /dev/null +++ b/apps/phoenix_webcomponent/lib/phoenix_webcomponent/link.ex @@ -0,0 +1,189 @@ +defmodule Phoenix.WebComponent.Link do + @moduledoc """ + render appbar + + """ + use Phoenix.WebComponent, :html + + @doc """ + Generates a link for live and href navigation. + [INSERT LVATTRDOCS] + ## Examples + ```heex + <.wc_link href="/">Regular anchor link + ``` + ```heex + <.wc_link navigate={Routes.page_path(@socket, :index)} class="underline">home + ``` + ```heex + <.wc_link navigate={Routes.live_path(@socket, MyLive, dir: :asc)} replace={false}> + Sort By Price + + ``` + ```heex + <.wc_link patch={Routes.page_path(@socket, :index, :details)}>view details + ``` + ```heex + <.wc_link href={URI.parse("https://elixir-lang.org")}>hello + ``` + ```heex + <.wc_link href="/the_world" method={:delete} data-confirm="Really?">delete + ``` + ## JavaScript dependency + In order to support links where `:method` is not `:get` or use the above data attributes, + `Phoenix.HTML` relies on JavaScript. You can load `priv/static/phoenix_html.js` into your + build tool. + ### Data attributes + Data attributes are added as a keyword list passed to the `data` key. The following data + attributes are supported: + * `data-confirm` - shows a confirmation prompt before generating and submitting the form when + `:method` is not `:get`. + ### Overriding the default confirm behaviour + `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the clicked DOM element + when a click happened. This allows you to intercept the event on its way bubbling up + to `window` and do your own custom logic to enhance or replace how the `data-confirm` + attribute is handled. You could for example replace the browsers `confirm()` behavior with + a custom javascript implementation: + ```javascript + // listen on document.body, so it's executed before the default of + // phoenix_html, which is listening on the window object + document.body.addEventListener('phoenix.link.click', function (e) { + // Prevent default implementation + e.stopPropagation(); + // Introduce alternative implementation + var message = e.target.getAttribute("data-confirm"); + if(!message){ return true; } + vex.dialog.confirm({ + message: message, + callback: function (value) { + if (value == false) { e.preventDefault(); } + } + }) + }, false); + ``` + Or you could attach your own custom behavior. + ```javascript + window.addEventListener('phoenix.link.click', function (e) { + // Introduce custom behaviour + var message = e.target.getAttribute("data-prompt"); + var answer = e.target.getAttribute("data-prompt-answer"); + if(message && answer && (answer != window.prompt(message))) { + e.preventDefault(); + } + }, false); + ``` + The latter could also be bound to any `click` event, but this way you can be sure your custom + code is only executed when the code of `phoenix_html.js` is run. + ## CSRF Protection + By default, CSRF tokens are generated through `Plug.CSRFProtection`. + """ + @doc type: :component + attr(:navigate, :string, + doc: """ + Navigates from a LiveView to a new LiveView. + The browser page is kept, but a new LiveView process is mounted and its content on the page + is reloaded. It is only possible to navigate between LiveViews declared under the same router + `Phoenix.LiveView.Router.live_session/3`. Otherwise, a full browser redirect is used. + """ + ) + + attr(:patch, :string, + doc: """ + Patches the current LiveView. + The `handle_params` callback of the current LiveView will be invoked and the minimum content + will be sent over the wire, as any other LiveView diff. + """ + ) + + attr(:href, :any, + doc: """ + Uses traditional browser navigation to the new location. + This means the whole page is reloaded on the browser. + """ + ) + + attr(:replace, :boolean, + default: false, + doc: """ + When using `:patch` or `:navigate`, + should the browser's history be replaced with `pushState`? + """ + ) + + attr(:method, :string, + default: "get", + doc: """ + The HTTP method to use with the link. + In case the method is not `get`, the link is generated inside the form which sets the proper + information. In order to submit the form, JavaScript must be enabled in the browser. + """ + ) + + attr(:csrf_token, :any, + default: true, + doc: """ + A boolean or custom token to use for links with an HTTP method other than `get`. + """ + ) + + attr(:rest, :global, + include: ~w(download hreflang referrerpolicy rel target type), + doc: """ + Additional HTML attributes added to the `a` tag. + """ + ) + + slot(:inner_block, + required: true, + doc: """ + The content rendered inside of the `a` tag. + """ + ) + + def wc_link(%{navigate: to} = assigns) when is_binary(to) do + ~H""" + <%= render_slot(@inner_block) %> + """ + end + + def wc_link(%{patch: to} = assigns) when is_binary(to) do + ~H""" + <%= render_slot(@inner_block) %> + """ + end + + def wc_link(%{href: href} = assigns) when href != "#" and not is_nil(href) do + href = Phoenix.LiveView.Utils.valid_destination!(href, "<.wc_link>") + assigns = assign(assigns, :href, href) + + ~H""" + <%= render_slot(@inner_block) %> + """ + end + + def wc_link(%{} = assigns) do + ~H""" + <%= render_slot(@inner_block) %> + """ + end + + defp csrf_token(true, href), do: Plug.CSRFProtection.get_csrf_token_for(href) + defp csrf_token(false, _href), do: nil + defp csrf_token(csrf, _href) when is_binary(csrf), do: csrf +end