Skip to content

Commit

Permalink
Add navigate support for replacing html attributes (#9149)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshhanley authored Jan 28, 2025
1 parent c8d9d66 commit 5c9341c
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 8 deletions.
17 changes: 17 additions & 0 deletions dist/livewire.esm.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dist/livewire.esm.js.map

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions dist/livewire.js
Original file line number Diff line number Diff line change
Expand Up @@ -7715,13 +7715,15 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
];
function swapCurrentPageWithNewHtml(html, andThen) {
let newDocument = new DOMParser().parseFromString(html, "text/html");
let newHtml = newDocument.documentElement;
let newBody = document.adoptNode(newDocument.body);
let newHead = document.adoptNode(newDocument.head);
oldBodyScriptTagHashes = oldBodyScriptTagHashes.concat(Array.from(document.body.querySelectorAll("script")).map((i) => {
return simpleHash(ignoreAttributes(i.outerHTML, attributesExemptFromScriptTagHashing));
}));
let afterRemoteScriptsHaveLoaded = () => {
};
replaceHtmlAttributes(newHtml);
mergeNewHead(newHead).finally(() => {
afterRemoteScriptsHaveLoaded();
});
Expand All @@ -7741,6 +7743,21 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
i.replaceWith(cloneScriptTag(i));
});
}
function replaceHtmlAttributes(newHtmlElement) {
let currentHtmlElement = document.documentElement;
Array.from(newHtmlElement.attributes).forEach((attr) => {
const name = attr.name;
const value = attr.value;
if (currentHtmlElement.getAttribute(name) !== value) {
currentHtmlElement.setAttribute(name, value);
}
});
Array.from(currentHtmlElement.attributes).forEach((attr) => {
if (!newHtmlElement.hasAttribute(attr.name)) {
currentHtmlElement.removeAttribute(attr.name);
}
});
}
function mergeNewHead(newHead) {
let children = Array.from(document.head.children);
let headChildrenHtmlLookup = children.map((i) => i.outerHTML);
Expand Down
6 changes: 3 additions & 3 deletions dist/livewire.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/livewire.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/manifest.json
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@

{"/livewire.js":"b963ec8d"}
{"/livewire.js":"13b7c601"}
25 changes: 25 additions & 0 deletions js/plugins/navigate/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ let attributesExemptFromScriptTagHashing = [

export function swapCurrentPageWithNewHtml(html, andThen) {
let newDocument = (new DOMParser()).parseFromString(html, "text/html")
let newHtml = newDocument.documentElement
let newBody = document.adoptNode(newDocument.body)
let newHead = document.adoptNode(newDocument.head)

Expand All @@ -16,6 +17,8 @@ export function swapCurrentPageWithNewHtml(html, andThen) {

let afterRemoteScriptsHaveLoaded = () => {}

replaceHtmlAttributes(newHtml)

mergeNewHead(newHead).finally(() => {
afterRemoteScriptsHaveLoaded()
})
Expand Down Expand Up @@ -50,6 +53,28 @@ function prepNewBodyScriptTagsToRun(newBody, oldBodyScriptTagHashes) {
})
}

function replaceHtmlAttributes(newHtmlElement) {
let currentHtmlElement = document.documentElement

// Process attributes that are in the new element...
Array.from(newHtmlElement.attributes).forEach(attr => {
const name = attr.name
const value = attr.value

if (currentHtmlElement.getAttribute(name) !== value) {
// Add or update attribute if the value differs...
currentHtmlElement.setAttribute(name, value)
}
})

// Remove remaining attributes that are not in the new element...
Array.from(currentHtmlElement.attributes).forEach(attr => {
if (!newHtmlElement.hasAttribute(attr.name)) {
currentHtmlElement.removeAttribute(attr.name)
}
})
}

function mergeNewHead(newHead) {
let children = Array.from(document.head.children)
let headChildrenHtmlLookup = children.map(i => i.outerHTML)
Expand Down
41 changes: 41 additions & 0 deletions src/Features/SupportNavigate/BrowserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public static function tweakApplicationHook()
Livewire::component('first-page-with-link-outside', FirstPageWithLinkOutside::class);
Livewire::component('second-page', SecondPage::class);
Livewire::component('third-page', ThirdPage::class);
Livewire::component('first-html-attribute-page', FirstHtmlAttributesPage::class);
Livewire::component('second-html-attribute-page', SecondHtmlAttributesPage::class);
Livewire::component('first-asset-page', FirstAssetPage::class);
Livewire::component('second-asset-page', SecondAssetPage::class);
Livewire::component('third-asset-page', ThirdAssetPage::class);
Expand Down Expand Up @@ -57,6 +59,8 @@ public static function tweakApplicationHook()
Route::get('/second', SecondPage::class)->middleware('web');
Route::get('/third', ThirdPage::class)->middleware('web');
Route::get('/fourth', FourthPage::class)->middleware('web');
Route::get('/first-html-attributes', FirstHtmlAttributesPage::class)->middleware('web');
Route::get('/second-html-attributes', SecondHtmlAttributesPage::class)->middleware('web');
Route::get('/first-asset', FirstAssetPage::class)->middleware('web');
Route::get('/second-asset', SecondAssetPage::class)->middleware('web');
Route::get('/third-asset', ThirdAssetPage::class)->middleware('web');
Expand Down Expand Up @@ -392,6 +396,25 @@ public function test_can_persist_elements_across_pages()
});
}

public function test_html_element_attributes_are_replaced_on_navigate()
{
$this->browse(function ($browser) {
$browser
->visit('/first-html-attributes')
->assertSee('On first html attributes page')
// ->assertAttribute() won't work as it's scoped to the body...
->assertScript('document.documentElement.getAttribute("class")', 'class1')
->assertScript('document.documentElement.getAttribute("attr1")', 'value1')
->assertScript('document.documentElement.hasAttribute("attr2")', false)
->click('@link.to.second')
->waitForText('On second html attributes page')
->assertScript('document.documentElement.getAttribute("class")', 'class2')
->assertScript('document.documentElement.getAttribute("attr2")', 'value2')
->assertScript('document.documentElement.hasAttribute("attr1")', false)
;
});
}

public function test_new_assets_in_head_are_loaded_and_old_ones_are_not()
{
$this->browse(function ($browser) {
Expand Down Expand Up @@ -1177,6 +1200,24 @@ public function render()
}
}

class FirstHtmlAttributesPage extends Component
{
#[\Livewire\Attributes\Layout('test-views::html-attributes1')]
public function render()
{
return '<div>On first html attributes page <a href="/second-html-attributes" wire:navigate dusk="link.to.second">Go to second page</a></div>';
}
}

class SecondHtmlAttributesPage extends Component
{
#[\Livewire\Attributes\Layout('test-views::html-attributes2')]
public function render()
{
return '<div>On second html attributes page</div>';
}
}

class FirstAssetPage extends Component
{
#[\Livewire\Attributes\Layout('test-views::layout')]
Expand Down
13 changes: 13 additions & 0 deletions src/Features/SupportNavigate/test-views/html-attributes1.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html class="class1" attr1="value1" dusk="html">
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="/test-navigate-asset.js?v=123"></script>
</head>
<body>
{{ $slot }}

@stack('scripts')
</body>
</html>


13 changes: 13 additions & 0 deletions src/Features/SupportNavigate/test-views/html-attributes2.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html class="class2" attr2="value2" dusk="html">
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="/test-navigate-asset.js?v=123"></script>
</head>
<body>
{{ $slot }}

@stack('scripts')
</body>
</html>


0 comments on commit 5c9341c

Please sign in to comment.