Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support default title attribute #3491

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions assets/js/phoenix_live_view/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,12 @@ let DOM = {
putTitle(str){
let titleEl = document.querySelector("title")
if(titleEl){
let {prefix, suffix} = titleEl.dataset
document.title = `${prefix || ""}${str}${suffix || ""}`
let {prefix, suffix, default: defaultTitle} = titleEl.dataset
let isEmpty = typeof(str) !== "string" || str.trim() === ""
if(isEmpty && typeof(defaultTitle) !== "string"){ return }

let inner = isEmpty ? defaultTitle : str
document.title = `${prefix || ""}${inner || ""}${suffix || ""}`
} else {
document.title = str
}
Expand Down
2 changes: 1 addition & 1 deletion assets/js/phoenix_live_view/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default class View {
this.log(type, () => ["", clone(rawDiff)])
let {diff, reply, events, title} = Rendered.extract(rawDiff)
callback({diff, reply, events})
if(typeof title === "string"){ window.requestAnimationFrame(() => DOM.putTitle(title)) }
if(typeof title === "string" || type == "mount"){ window.requestAnimationFrame(() => DOM.putTitle(title)) }
}

onJoin(resp){
Expand Down
12 changes: 12 additions & 0 deletions assets/test/dom_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ describe("DOM", () => {
DOM.putTitle("My Title")
expect(document.title).toBe("PRE My Title POST")
})

test("with default", () => {
appendTitle({default: "DEFAULT", prefix: "PRE ", suffix: " POST"})
DOM.putTitle(null)
expect(document.title).toBe("PRE DEFAULT POST")

DOM.putTitle(undefined)
expect(document.title).toBe("PRE DEFAULT POST")

DOM.putTitle("")
expect(document.title).toBe("PRE DEFAULT POST")
})
})

describe("findExistingParentCIDs", () => {
Expand Down
13 changes: 9 additions & 4 deletions assets/test/test_helpers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import View from "phoenix_live_view/view"
import {version as liveview_version} from "../package.json"

export let appendTitle = opts => {
export let appendTitle = (opts, innerHTML) => {
Array.from(document.head.querySelectorAll("title")).forEach(el => el.remove())
let title = document.createElement("title")
let {prefix, suffix} = opts
let {prefix, suffix, default: defaultTitle} = opts
if(prefix){ title.setAttribute("data-prefix", prefix) }
if(suffix){ title.setAttribute("data-suffix", suffix) }
if(defaultTitle){
title.setAttribute("data-default", defaultTitle)
} else {
title.removeAttribute("data-default")
}
if(innerHTML){ title.innerHTML = innerHTML }
document.head.appendChild(title)
}

Expand Down Expand Up @@ -78,5 +85,3 @@ export function liveViewDOM(content){
document.body.appendChild(div)
return div
}


15 changes: 10 additions & 5 deletions assets/test/view_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
PHX_HAS_FOCUSED
} from "phoenix_live_view/constants"

import {tag, simulateJoinedView, stubChannel, rootContainer, liveViewDOM, simulateVisibility} from "./test_helpers"
import {tag, simulateJoinedView, stubChannel, rootContainer, liveViewDOM, simulateVisibility, appendTitle} from "./test_helpers"

let simulateUsedInput = (input) => {
DOM.putPrivate(input, PHX_HAS_FOCUSED, true)
Expand Down Expand Up @@ -44,9 +44,10 @@ describe("View + DOM", function(){
expect(view.rendered.get()).toEqual(updateDiff)
})

test("applyDiff can set title to falsy values", async () => {
document.title = "Foo"
test("applyDiff with empty title uses default if present", async () => {
appendTitle({}, "Foo")

let titleEl = document.querySelector("title")
let liveSocket = new LiveSocket("/live", Socket)
let el = liveViewDOM()
let updateDiff = {
Expand All @@ -56,13 +57,17 @@ describe("View + DOM", function(){
}

let view = simulateJoinedView(el, liveSocket)
view.applyDiff("update", updateDiff, ({diff, events}) => view.update(diff, events))
view.applyDiff("mount", updateDiff, ({diff, events}) => view.update(diff, events))

expect(view.el.firstChild.tagName).toBe("H2")
expect(view.rendered.get()).toEqual(updateDiff)

await new Promise(requestAnimationFrame)
expect(document.title).toBe("")
expect(document.title).toBe("Foo")
titleEl.setAttribute("data-default", "DEFAULT")
view.applyDiff("mount", updateDiff, ({diff, events}) => view.update(diff, events))
await new Promise(requestAnimationFrame)
expect(document.title).toBe("DEFAULT")
})

test("pushWithReply", function(){
Expand Down
28 changes: 23 additions & 5 deletions lib/phoenix_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2084,14 +2084,14 @@ defmodule Phoenix.Component do
## Examples

```heex
<.live_title prefix="MyApp – ">
<%= assigns[:page_title] || "Welcome" %>
<.live_title default="Welcome" prefix="MyApp – ">
<%= assigns[:page_title] %>
</.live_title>
```

```heex
<.live_title suffix="- MyApp">
<%= assigns[:page_title] || "Welcome" %>
<.live_title default="Welcome" suffix="- MyApp">
<%= assigns[:page_title] %>
</.live_title>
```
"""
Expand All @@ -2101,15 +2101,33 @@ defmodule Phoenix.Component do
doc: "A prefix added before the content of `inner_block`."
)

attr.(:default, :string,
default: nil,
doc: "The default title to use if the inner block is empty on regular or connected mounts."
)

attr.(:suffix, :string, default: nil, doc: "A suffix added after the content of `inner_block`.")
slot.(:inner_block, required: true, doc: "Content rendered inside the `title` tag.")

def live_title(assigns) do
~H"""
<title data-prefix={@prefix} data-suffix={@suffix} phx-no-format><%= @prefix %><%= render_slot(@inner_block) %><%= @suffix %></title>
<title data-prefix={@prefix} data-default={@default} data-suffix={@suffix} phx-no-format><%= @prefix %><%= render_present(render_slot(@inner_block), @default) %><%= @suffix %></title>
"""
end

defp render_present(rendered_block, default) do
block_str =
rendered_block
|> Phoenix.HTML.html_escape()
|> Phoenix.HTML.safe_to_string()

if String.trim(block_str) == "" do
default
else
rendered_block
end
end

@doc ~S'''
Renders a form.

Expand Down
19 changes: 19 additions & 0 deletions test/phoenix_component/components_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,25 @@ defmodule Phoenix.LiveView.ComponentsTest do
assert t2h(~H|<.live_title>My Title</.live_title>|) ==
~X|<title>My Title</title>|
end

test "default with blank inner block" do
assigns = %{
val: """


"""
}

assert t2h(~H|<.live_title default="DEFAULT" phx-no-format> <%= @val %> </.live_title>|) ==
~X|<title data-default="DEFAULT">DEFAULT</title>|
end

test "default with present inner block" do
assigns = %{val: "My Title"}

assert t2h(~H|<.live_title default="DEFAULT" phx-no-format> <%= @val %> </.live_title>|) ==
~X|<title data-default="DEFAULT"> My Title </title>|
end
end

describe "dynamic_tag/1" do
Expand Down
Loading