-
Notifications
You must be signed in to change notification settings - Fork 44
Mutable List interface for children and childNodes
#490
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
base: main
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @fsw, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
This pull request introduces a very useful feature, providing a mutable List interface for childNodes and children, similar to the old dart:html API. The overall approach of using a wrapper class is solid.
My review includes a critical fix for a type safety issue in JSLiveNodeListWrapper that could lead to incorrect behavior when manipulating lists of elements. I've also included a few medium-severity suggestions to improve documentation, robustness, and test coverage.
Regarding your questions:
- Extending
JSImmutableListWrapperis a good approach to reuse code. - Your chosen implementation is more powerful than adding extension methods like
removeWheredirectly, as it provides the full mutableListAPI. - Adding an
asImmutableListfor staticNodeLists is a great idea for API consistency and could be a good follow-up. ListMixinis not deprecated and is the correct tool for this job.
Co-authored-by: Kevin Moore <kevmoo@users.noreply.github.com>
For clarification, I am talking about this comments: https://github.com/dart-lang/sdk/blob/9a049d5b2defab33aa953e7882492639ce1f677b/sdk/lib/collection/list.dart#L33 |
srujzs
left a comment
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.
Does this implementation make sense and matches ideas behind helpers in this module? JSLiveNodeListWrapper don't have to extend JSImmutableListWrapper but have its own implementation but it kinda felt it makes sense this way.
I think so! I'll say that it might better if we started from the dart:html implementations instead. I say this mostly because if users are trying to use these helpers to bridge gaps, divergence in behavior would be painful to discover. See _ChildNodeListLazy and _ChildrenElementList. It's fine if we think some of the dart:html implementation methods need to be updated, though.
could/should NodeList (returned f.e. from querySelector) be extended with asImmutableList via extension? I think it would be more similar to above and dart:html interfaces and JSImmutableListWrapper and JSLiveNodeListWrapper could then be used internally or even marked as private.
Can you elaborate on what you mean here? Do you mean only offer an extension that does the wrapping?
While doing this I noticed ListMixin and ListBase is going to be deprecated in core? Could implementing List interface directly by both those wrappers while making changes here give any benefits?
I don't think they are but those comments are indeed suspect. @lrhn, is this something we should worry about?
| if (value > length) { | ||
| throw UnsupportedError('Cannot add null to live node List.'); | ||
| } | ||
| for (var i = length - 1; i >= value; i--) { |
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 believe this is new compared to the dart:html version. I think that's okay though since we were throwing in the dart:html version anyways.
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.
yes, it is here to reuse JSImmutableListWrapper logic without overriding many methods
web/lib/src/helpers/lists.dart
Outdated
| @override | ||
| void removeRange(int start, int end) { | ||
| RangeError.checkValidRange(start, end, length); | ||
| for (var i = 0; i < end - start + 1; i++) { |
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.
This is also new compared to the dart:html version, but again, it was throwing before so this is okay.
| void removeRange(int start, int end) { | ||
| RangeError.checkValidRange(start, end, length); | ||
| for (var i = 0; i < end - start + 1; i++) { | ||
| parentNode.removeChild(this[start]); |
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.
Should this be start + i? end is non-inclusive as well so that might be another issue.
I think this would be less confusing if we just modified the given parameter e.g.
for (; start < end; start++) {
parentNode.removeChild(this[start]);
}
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.
d'oh, indeed I made end inclusive, fixed and added tests.
But this[start] is OK. This does not look intuitive but removeChild(N) shifts node list and element N+1th becomes Nth immediately. So for removeRange(5, 8) we want to call removeChild(5) 3 times
We can do something like:
for (; start < end; end--) {
parentNode.removeChild(this[start]);
}
But not sure if this would be less confusing.
| /// [live](https://developer.mozilla.org/en-US/docs/Web/API/NodeList#live_vs._static_nodelists) | ||
| /// can be safely modified at runtime. This requires an instance of `P`, a | ||
| /// container that elements would be added to or removed from. | ||
| class JSLiveNodeListWrapper<P extends Node, T extends JSObject, U extends Node> |
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.
dart:html has overrides for first and last for children that use firstElementChild and lastElementChild, respectively.
I think this is the same as item(0) and item(len - 1) for children, but may be worth double-checking.
remove as well is a bit tricky because we should likely use removeChild instead of the gap-closing that ListMixin does.
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.
Not sure if this is realiable source:
https://stackoverflow.com/questions/43324751/is-there-a-difference-between-children0-and-firstelementchild
seems difference is only in returned value when list is empty but we throw exceptions in both cases.
I've added remove. It was indeed tricky as type of parameter in List interface is Object?, please take a look if type checking i used makes sense.
web/lib/src/helpers/extensions.dart
Outdated
| } | ||
|
|
||
| extension NodeExtension on Node { | ||
| /// Returns [childNodes] as a modifiable [List] |
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.
nit: Dart doc comments should be full sentences.
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, I thought subjectless sentences are allowed? (sorry, I am not native speaker) or do you mean just the dot at the end?
|
|
||
| extension NodeExtension on Node { | ||
| /// Returns [childNodes] as a modifiable [List] | ||
| List<Node> get childNodesAsList => JSLiveNodeListWrapper(this, 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.
Maybe this should be nodesAsList, because that was a special helper that dart:html provided in addition to childNodes (which was an immutable list).
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, I cant see nodesAsList but nodes in dart:html. I think this is less confusing 'nativeMethod+AsList' but up to you of course.
Hmm, I was trying to reuse what we have in JSImmutableListWrapper and put both in single implementation.
Precisely. Something like: To have a somehow unified interface. (field is native, fieldAsList implements list, querySelector(X) is native and querySelector(X).asList implements list) |
|
If we have |
dart:htmlallowed modifying child elements and nodes using List interface:This was a pretty clear and handy interface IMHO.
As discussed in this PR dart-lang/site-www#7040 with @srujzs it might be useful to implement something similar in
package:web. This PR is my attempt at doing so.It adds
JSLiveNodeListWrapperthat requires a container node and aHTMLCollectionorNodeListthat is live and implements mutableListmethods.For easier usage it also adds
childrenAsListandchildNodesAsListvia extension so you can do:Questions/Notes:
Does this implementation make sense and matches ideas behind helpers in this module?
JSLiveNodeListWrapperdon't have to extendJSImmutableListWrapperbut have its own implementation but it kinda felt it makes sense this way.Alternatively handy methods like
removeWherecould be added via extension directly toHTMLCollectionandNodeListbut methods likeadd()might not be possible as those require a parent node to add elements to. This would require knowing ifNodeListwas returned fromquerySelectororchildNodesand in further case accessing parent somehow. Also implementing fullListinterface might have other pros.could/should
NodeList(returned f.e. fromquerySelector) be extended withasImmutableListvia extension? I think it would be more similar to above anddart:htmlinterfaces andJSImmutableListWrapperandJSLiveNodeListWrappercould then be used internally or even marked as private.While doing this I noticed
ListMixinandListBaseis going to be deprecated in core? Could implementingListinterface directly by both those wrappers while making changes here give any benefits?