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

Remove innerHTML assignments from js #4065

Merged
merged 16 commits into from
Dec 17, 2024

Conversation

LuomaJuha
Copy link
Contributor

@LuomaJuha LuomaJuha commented Nov 7, 2024

InnerHTML assignment is pretty costly and usually it is adviced to not be used, so I created this pr to remove or adjust code to use proper assignments. Requires littlebit testing and adjustments.

TODO:

  • Tests (Tests are not passing so will have to see and fix the broken parts.)
  • Work still to be done

Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @LuomaJuha! I may not have a chance to review this in detail right away... but in the meantime, I see that a PR I just merged has introduced a conflict here. Do you mind resolving it? Sorry for the inconvenience!

@LuomaJuha
Copy link
Contributor Author

@demiankatz Sure! Will resolve in short future

@LuomaJuha LuomaJuha requested a review from demiankatz November 12, 2024 11:31
Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @LuomaJuha -- just a couple of questions.

Also, the full test suite is not passing for me on this branch. I'm seeing:

There were 2 errors:

1) VuFindTest\Mink\CombinedSearchTest::testCombinedSearchResultsMixedAjaxDOIs with data set "all ajax" (true, true)
Exception: Failed to call getText on '#combined_Solr____one .doiLink a' after 6 attempts.

/usr/local/vufind/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php:794
/usr/local/vufind/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php:713
/usr/local/vufind/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CombinedSearchTest.php:275

2) VuFindTest\Mink\HoldingsTest::testItemStatusFull with data set "custom template test" (true, 'On Shelf', 'On Shelf', 'success', 'msg', true, true, true)
Exception: Selector '.js-status-test.hidden' remains accessible

/usr/local/vufind/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php:599
/usr/local/vufind/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/HoldingsTest.php:274

--

There was 1 failure:

1) VuFindTest\Mink\RecordVersionsTest::testVersionsTabInit
Element not found: .result-links .qrcode img index 0
Failed asserting that null is of type object.

/usr/local/vufind/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php:527
/usr/local/vufind/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/RecordVersionsTest.php:130

I've re-run all of the failing test classes individually to confirm that these failures occur consistently in my environment. I suspect some (possibly all) of it has to do with the tricky code related to executing scripts included in AJAX responses.

themes/bootstrap3/js/common.js Outdated Show resolved Hide resolved
});
} else {
// Default case -- load call number and location into appropriate containers:
el.querySelectorAll('.callnumber').forEach((callnumber) => {
callnumber.innerHTML = typeof(result.callnumberHtml) !== 'undefined' && result.callnumberHtml
? result.callnumberHtml + '<br>'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you're dropping the <br> here, which is probably a good thing. Just double-checking whether you confirmed that doing so has no visible side-effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill double check this one

@demiankatz demiankatz added the architecture pull requests that involve significant refactoring / architectural changes label Nov 12, 2024
@demiankatz demiankatz added this to the 11.0 milestone Nov 12, 2024
Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @LuomaJuha. I can't see any problems with this, and all tests are passing on my end now. However, Javascript is not my speciality, so I'll leave this open a couple of days in case others like @crhallberg or @EreMaijala have anything to add.

Also, you still have an unchecked "Tests" TODO item -- is there still something you're planning to work on on your end?

Copy link
Contributor

@crhallberg crhallberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the replacement of innerHTML with textContent in the case of strings makes a lot of sense. I am curious where you read about the performance of .innerHTML = str.

Do you think it would be worth it to add a element builder function, like this very simple one?

if (property === 'innerHTML') {
elm.innerHTML = tmpDiv.innerHTML;
elm.textContent = '';
elm.append(...tmpDiv.childNodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this instance - and perhaps on line 313 as well - it may be clearer and more appropriate to use replaceChildren.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ye, it was added in 2020 i suppose, but I think it is enough for being used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EreMaijala, do you have an opinion on this? I know you're most concerned about certain older Safari versions...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't use replaceChildren just yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought, let's use it and just provide a polyfill (example) for browsers that don't support it.

newElm = parentElm.lastElementChild;
}
elm.textContent = '';
elm.replaceWith(...tmpDiv.childNodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need to reset textContent if we are replacing the whole element.

// Set any attributes (N.B. has to be done before scripts in case they rely on the attributes):
Object.entries(attrs).forEach(([attr, value]) => newElm.setAttribute(attr, value));

// Append any scripts:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if we preserved this blank line and comment.

@@ -293,7 +293,7 @@ VuFind.register('search', function search() {
loadingOverlay.classList = 'loading-overlay';
loadingOverlay.setAttribute('aria-live', 'polite');
loadingOverlay.setAttribute('role', 'status');
loadingOverlay.innerHTML = VuFind.loading();
VuFind.setInnerHtml(loadingOverlay, VuFind.loading());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense to have VuFind.loading and VuFind.spinner return elements instead of HTML and use append or replaceChildren here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be even better to have loadingElements and spinnerElements methods instead of changing the existing loading and spinner for backward-compatibility. We could always mark those as deprecated and remove them later if they're not widely used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. ill adjust a lttilebit more, might take a while but in close future :) will request re reviews when adjusted

const recordTotalSpan = document.createElement('span');
recordTotalSpan.classList.add('record-total');
recordTotalSpan.textContent = '(' + count + ')';
template.append(recordTotalSpan);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're creating the element directly, we don't need this template any more.

@LuomaJuha
Copy link
Contributor Author

been little busy, but havent forgotten. Will try to adjust this week to match reviews :)

Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't thoroughly reviewed everything yet, but I did see one thing that looked like there might be an opportunity for simplification/clarification.

@@ -293,46 +348,35 @@ var VuFind = (function VuFind() {
* @param {string} property Target property ('innerHTML', 'outerHTML' or '' for no HTML update)
*/
function setElementContents(elm, html, attrs = {}, property = 'innerHTML') {
// Extract any scripts from the HTML and add them separately so that they are executed properly:
const tmpDiv = el('div', '', {}, stringToNodes(html));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little weird that we use stringToNodes here to create a div and pull out its contents, and then we use el to create a different div and put those elements back inside... and then down below, we pull the elements back out of the second div on various code paths. Would it be better to refactor this to just use the result of stringToNodes directly without creating a second container? I realize this would prevent querySelectorAll from being used below, but is there perhaps some other way of filtering the array to achieve the same effect? Or, since stringToNodes is only used in one place, should we consider defining it differently or giving it an argument to just avoid stripping the container in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, could try and have a look!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, backtracked a littlebit here, as stringToNodes was used in only this place and added the assignment to innerhtml here. Idea is that there is one path to use and if any sanitization is required then its easier to manage the content when its all set with the same function.

@LuomaJuha LuomaJuha requested a review from demiankatz December 17, 2024 13:28
Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the simplification, @LuomaJuha. This looks pretty clear to me now, and all tests are passing, so I'm merging it!

@demiankatz demiankatz merged commit 9799997 into vufind-org:dev Dec 17, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
architecture pull requests that involve significant refactoring / architectural changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants