Skip to content

Commit

Permalink
Support default title attribute (#3491)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord authored Nov 7, 2024
1 parent 4b8be45 commit 5086beb
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 17 deletions.
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

0 comments on commit 5086beb

Please sign in to comment.