-
Notifications
You must be signed in to change notification settings - Fork 359
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
Remove innerHTML assignments from js #4065
Conversation
There was a problem hiding this 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!
@demiankatz Sure! Will resolve in short future |
There was a problem hiding this 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.
}); | ||
} 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>' |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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?
There was a problem hiding this 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?
themes/bootstrap3/js/common.js
Outdated
if (property === 'innerHTML') { | ||
elm.innerHTML = tmpDiv.innerHTML; | ||
elm.textContent = ''; | ||
elm.append(...tmpDiv.childNodes); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
themes/bootstrap3/js/search.js
Outdated
@@ -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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
been little busy, but havent forgotten. Will try to adjust this week to match reviews :) |
There was a problem hiding this 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.
themes/bootstrap5/js/common.js
Outdated
@@ -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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
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: