Skip to content

Commit 34e78f6

Browse files
committed
feature #1290 [Autocomplete] Attempting a simpler "reset" of items (weaverryan)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Autocomplete] Attempting a simpler "reset" of items | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Issues | Possibly #1290 (need to check and remove data-live-ignore), fixes #1261 possibly #1241 possibly #909 | License | MIT Tomselect is a real pain in the butt. When you select an option, it re-orders the `option` elements and uses the elements themselves internally to remember which one is selected. It plays poorly with LiveComponents, which is why we added the MutationObserver logic. However, at least in one straightforward case that I had today, when you selected a new item, the new value was being lost. This will only get worse when Turbo 8 adds morphing. This PR attempts to simplify: when needed, completely destroy TomSelect and recreate it. It seems to work well, but 2 tests are failing. And I'm pulling my hair out - so it may not even be fixable. No matter what I try, when I reinitialize TomSelect on a set of HTML that has been reordered, it gets confused. It's as if it has a static cache somewhere that I can't find... Commits ------- af20ad8a [Autocomplete] Attempting a simpler "reset" of items
2 parents d6c86eb + 27bc4a7 commit 34e78f6

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

assets/dist/live_controller.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,13 @@ function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements,
12641264
fromEl.removeAttribute('parent-live-id-changed');
12651265
return true;
12661266
}
1267+
if (fromEl.hasAttribute('data-skip-morph')) {
1268+
fromEl.innerHTML = toEl.innerHTML;
1269+
return true;
1270+
}
1271+
if (fromEl.parentElement && fromEl.parentElement.hasAttribute('data-skip-morph')) {
1272+
return false;
1273+
}
12671274
return !fromEl.hasAttribute('data-live-ignore');
12681275
},
12691276
beforeNodeRemoved(node) {

assets/src/morphdom.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,21 @@ export function executeMorphdom(
112112
return true;
113113
}
114114

115+
if (fromEl.hasAttribute('data-skip-morph')) {
116+
fromEl.innerHTML = toEl.innerHTML;
117+
118+
return true;
119+
}
120+
121+
if (fromEl.parentElement && fromEl.parentElement.hasAttribute('data-skip-morph')) {
122+
return false;
123+
}
124+
115125
// look for data-live-ignore, and don't update
116126
return !fromEl.hasAttribute('data-live-ignore');
117127
},
118128

119-
beforeNodeRemoved(node) {
129+
beforeNodeRemoved(node: Node) {
120130
if (!(node instanceof HTMLElement)) {
121131
// text element
122132
return true;

assets/test/controller/render.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'use strict';
1111

1212
import { shutdownTests, createTest, initComponent } from '../tools';
13-
import { getByText, waitFor } from '@testing-library/dom';
13+
import { getByTestId, getByText, waitFor } from '@testing-library/dom';
1414
import userEvent from '@testing-library/user-event';
1515
import { htmlToElement } from '../../src/dom_utils';
1616

@@ -208,6 +208,41 @@ describe('LiveController rendering Tests', () => {
208208
expect(ignoreElement?.outerHTML).toEqual('<div data-live-ignore="">Inside Ignore Name: <span>Kevin</span></div>');
209209
});
210210

211+
it('overwrites HTML instead of morph with data-skip-morph', async () => {
212+
const test = await createTest({ firstName: 'Ryan' }, (data: any) => `
213+
<div ${initComponent(data)}>
214+
<div data-skip-morph data-name="${data.firstName}">Inside Skip Name: <span data-testid="inside-skip-morph">${data.firstName}</span></div>
215+
216+
Outside Skip Name: ${data.firstName}
217+
218+
<button data-action="live#$render">Reload</button>
219+
</div>
220+
`);
221+
222+
const spanBefore = getByTestId(test.element, 'inside-skip-morph');
223+
expect(spanBefore).toHaveTextContent('Ryan');
224+
225+
test.expectsAjaxCall()
226+
.serverWillChangeProps((data: any) => {
227+
// change the data on the server so the template renders differently
228+
data.firstName = 'Kevin';
229+
});
230+
231+
getByText(test.element, 'Reload').click();
232+
233+
await waitFor(() => expect(test.element).toHaveTextContent('Outside Skip Name: Kevin'));
234+
// make sure the outer element is still updated
235+
const skipElement = test.element.querySelector('div[data-skip-morph]');
236+
if (!(skipElement instanceof HTMLElement)) {
237+
throw new Error('skipElement is not an HTMLElement');
238+
}
239+
expect(skipElement.dataset.name).toEqual('Kevin');
240+
const spanAfter = getByTestId(test.element, 'inside-skip-morph');
241+
expect(spanAfter).toHaveTextContent('Kevin');
242+
// but it is not just a mutation of the original element
243+
expect(spanAfter).not.toBe(spanBefore);
244+
});
245+
211246
it('cancels a re-render if the page is navigating away', async () => {
212247
const test = await createTest({greeting: 'aloha!'}, (data: any) => `
213248
<div ${initComponent(data)}>${data.greeting}</div>

doc/index.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3243,6 +3243,23 @@ an element, that changes is preserved (see :ref:`smart-rerender-algorithm`).
32433243
``data-live-id`` attribute. During a re-render, if this value changes, all
32443244
of the children of the element will be re-rendered, even those with ``data-live-ignore``.
32453245

3246+
Overwrite HTML Instead of Morphing
3247+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3248+
3249+
Normally, when a component re-renders, the new HTML is "morphed" onto the existing
3250+
elements on the page. In some rare cases, you may want to simply overwrite the existing
3251+
inner HTML of an element with the new HTML instead of morphing it. This can be done by adding a
3252+
``data-skip-morph`` attribute:
3253+
3254+
.. code-block:: html
3255+
3256+
<select data-skip-morph>
3257+
<option>...</option>
3258+
</select>
3259+
3260+
In this case, any changes to the ``<select>`` element attributes will still be
3261+
"morphed" onto the existing element, but the inner HTML will be overwritten.
3262+
32463263
Define another route for your Component
32473264
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32483265

0 commit comments

Comments
 (0)