Skip to content
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
9 changes: 9 additions & 0 deletions assets/js/phoenix_live_view/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,15 @@ const DOM = {
isLocked(el) {
return el.hasAttribute && el.hasAttribute(PHX_REF_LOCK);
},

attributeIgnored(attribute, ignoredAttributes) {
return ignoredAttributes.some(
(toIgnore) =>
attribute.name == toIgnore ||
toIgnore === "*" ||
(toIgnore.includes("*") && attribute.name.match(toIgnore) != null),
);
},
};

export default DOM;
22 changes: 13 additions & 9 deletions assets/js/phoenix_live_view/js.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,19 @@ const JS = {
ignoreAttrs(el, attrs) {
DOM.putPrivate(el, "JS:ignore_attrs", {
apply: (fromEl, toEl) => {
Array.from(fromEl.attributes).forEach((attr) => {
if (
attrs.some(
(toIgnore) =>
attr.name == toIgnore ||
toIgnore === "*" ||
(toIgnore.includes("*") && attr.name.match(toIgnore) != null),
)
) {
let fromAttributes = Array.from(fromEl.attributes);
let fromAttributeNames = fromAttributes.map((attr) => attr.name);
Array.from(toEl.attributes)
.filter((attr) => {
return !fromAttributeNames.includes(attr.name);
})
.forEach((attr) => {
if (DOM.attributeIgnored(attr, attrs)) {
toEl.removeAttribute(attr.name);
}
});
fromAttributes.forEach((attr) => {
if (DOM.attributeIgnored(attr, attrs)) {
toEl.setAttribute(attr.name, attr.value);
}
});
Expand Down
42 changes: 40 additions & 2 deletions assets/test/view_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,40 @@ describe("View + DOM", function () {
expect(view.el.firstChild.textContent.replace(/\s+/g, "")).toEqual("A1");
});

test("ignore_attributes skips boolean attributes on update when not set", () => {
let liveSocket = new LiveSocket("/live", Socket);
let el = liveViewDOM();
let updateDiff = {
"0": ' phx-mounted="[["ignore_attrs",{"attrs":["open"]}]]"',
"1": "0",
s: [
"<details open",
">\n <summary>A</summary>\n <span>",
"</span></details>",
],
};

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

expect(view.el.firstChild.tagName).toBe("DETAILS");
expect(view.el.firstChild.open).toBe(true);
view.el.firstChild.open = false;
view.el.firstChild.setAttribute("data-foo", "bar");

// now update, the HTML patch would normally reset the open attribute
view.applyDiff("update", { "1": "1" }, ({ diff, events }) =>
view.update(diff, events),
);
// open is ignored, so it is kept as is
expect(view.el.firstChild.open).toBe(false);
// foo is not ignored, so it is reset
expect(view.el.firstChild.getAttribute("data-foo")).toBe(null);
expect(view.el.firstChild.textContent.replace(/\s+/g, "")).toEqual("A1");
});

test("ignore_attributes wildcard", () => {
let liveSocket = new LiveSocket("/live", Socket);
let el = liveViewDOM();
Expand Down Expand Up @@ -1031,8 +1065,10 @@ describe("View + DOM", function () {
expect(view.el.firstChild.getAttribute("data-foo")).toBe("bar");
expect(view.el.firstChild.getAttribute("data-bar")).toBe("bar");
expect(view.el.firstChild.getAttribute("data-other")).toBe("also kept");
expect(view.el.firstChild.getAttribute("data-new")).toBe("new");
expect(view.el.firstChild.textContent.replace(/\s+/g, "")).toEqual("A1");

// Not added for being ignored
expect(view.el.firstChild.getAttribute("data-new")).toBe(null);
});

test("ignore_attributes *", () => {
Expand Down Expand Up @@ -1072,8 +1108,10 @@ describe("View + DOM", function () {
expect(view.el.firstChild.getAttribute("data-bar")).toBe("bar");
expect(view.el.firstChild.getAttribute("something")).toBe("else");
expect(view.el.firstChild.getAttribute("data-other")).toBe("also kept");
expect(view.el.firstChild.getAttribute("data-new")).toBe("new");
expect(view.el.firstChild.textContent.replace(/\s+/g, "")).toEqual("A1");

// Not added for being ignored
expect(view.el.firstChild.getAttribute("data-new")).toBe(null);
});
});
});
Expand Down
Loading