-
Notifications
You must be signed in to change notification settings - Fork 3
List things related to a resource with a custom display #119
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?
List things related to a resource with a custom display #119
Conversation
6a40e50
to
72edc1e
Compare
if (templateElement == null) { | ||
this.error = "No template element found" | ||
} else if (templateElement.content.childElementCount != 1) { | ||
this.error = "Template element should only have one child, e.g. li" |
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.
The reason for this is that the component will provide resources to each child based on their index. Compared to #43 (comment) this gets rid of the intermediate pos-resource. However, it will still not be possible to have li nested directly within ul, given that pos-list lies between them.
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.
One option would be to allow pos-list to be used as a customized built-in element, i.e. <ul is="pos-list"></ul>
. I would need to look into whether/how stenciljs supports this.
Update: stenciljs does not support customized built-in elements stenciljs/core#4089 (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.
What about assigning role="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.
The issue I had in mind is that the only permitted parents for li are ul and ol (or menu). So if a dashboard user cares about correct HTML then I suppose they could use <div role="listitem" style="display:list-item">
I hadn't thought of that.
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've currently ignored correct HTML for this PR. In storybook I've used bare li
s. Let me know if it's an issue.
|
||
receiveResource = (resource: Thing) => { | ||
this.items = [] | ||
if (this.rel) this.items = resource.relations().filter(relation => relation.predicate == this.rel).flatMap(relation => relation.uris); |
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 instead of querying all relations and then filter Thing should provide a method to get the relation in question directly. Also is it limited to relations to other resources or could the predicate refer to literals as well?
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.
Ok. I'll work on that. There'll be a similar method for rev. Literals are out of scope for this particular PR but will need to be revisited as part of #105 . I'll think about options to handle forward compatibility.
}); | ||
expect(page.root).toEqualHtml(` | ||
<pos-list rel="http://schema.org/video" /> | ||
<template><div>Test</div></template> |
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.
Why use template and not (named) slots?
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.
What happens within a list is that templated content gets repeated and the original template should never actually be rendered. Slots could be used but to me the semantics are all wrong. We're not actually slotting anything anywhere - we're using a template to create a new set of items.
async provideResource(event: SubscribeResourceEvent) { | ||
event.stopPropagation(); | ||
this.provideQueue.push(event); | ||
this.provideResources(); |
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 contrast to pos-resource, pos-list is in control of the uris of children, so we don't need to store consumers and call them when uris change. However, we do still need to deal with race conditions where provideResource is called before this.os has been set, which is why I'm using a queue. We will eventually also need to deal with updating the children when this.items changes, but that's out of scope of this PR.
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 did not get the need for the provide queue yet, tbh. Is there a respective test that fails if no queue is used that helps to highlight the problem?
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.
Iguess, while this.os is not present the list should not start rendering anything. If it doesnt there is no chance of children requesting a resource. After that we could just provide the resource as soon as requested, without a queue?
while (this.provideQueue.length > 0) { | ||
let ev = this.provideQueue.pop(); | ||
let resourceUri = ev.currentTarget.getAttribute('about'); | ||
let resource = this.os.store.get(resourceUri); |
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.
For a subject-connected list, I could use Thing methods directly. I've used this architecture so that the same component will be able to handle enumerating lists too.
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've also excluded fetching from this PR - the current list element will only display already loaded resources.
This is consistent with pos-list being lazy-first - it's a display helper that only secondarily triggers network access. A follow up PR with fetching will also need to revisit how core is dealing with repeated fetches of the same resource.
"https://jane.doe.example/container/file.ttl#fragment", | ||
store, | ||
); | ||
const result = it.relations("http://vocab.test/first"); |
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'm suggesting this solution because it results in a minimal and backward compatible change to the core API. If we were to add a completely new method, it would be useful to rationalise what return types we want to provide, given that we're departing from RDF/JS specifications.
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 am not 100% convinced about this, but if this works for the list we can go this path for now
d8624cc
to
ffc74ab
Compare
</template> | ||
</pos-list>: | ||
<pos-value predicate="http://www.w3.org/ns/solid/terms#publicId"/></pos-value> | ||
</li> |
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.
The use of nested lists is a bit more complex than originally intended, but it turns out the example used in pos-relations uses schema:skills in a way that ends up like this. In the process it demonstrates the flexibility of pos-list, including being able to change subject for a singleton list (solid:publicId has a single object), which provides a basic implementation of #89. So I think I'm happy with it.
I've also put it under the composition menu because it doesn't make sense to use pos-list without pos-resource as a parent and with some kind of child.
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.
Really cool example. My first reaction was "Isn't there an easier way to get the singleton value, like pos-value" but this indeed would only provide the publicId URI and no way to get the label. So really cool to see that lists can be used that way as well.
Yet I would appreciate to have a simpler example for pos-list. Yes, you need a pos-resource and a child, but still a minimal viable example would be useful imho. Perhaps similar to what is in the unit tests. Perhaps we need a new category for this. "Templating" comes to my mind.
I've marked as ready for review because I'm not sure what e2e test should be included (is the integration test enough?), and I think we can maybe descope and implement rev in a separate PR - this one is probably complex enough as it is? |
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.
Good work so far! I have several comments and questions, but many of them are minor, I think this goes into a good direction.
"https://jane.doe.example/container/file.ttl#fragment", | ||
store, | ||
); | ||
const result = it.relations("http://vocab.test/first"); |
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 am not 100% convinced about this, but if this works for the list we can go this path for now
@@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | |||
### Added | |||
|
|||
- `IndexedDbOfflineCache`: A PodOS cache implementation that uses IndexedDB to store data for offline usage | |||
- [pos-list](../docs/elements/components/pos-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.
Make sure this goes into the correct section while rebasing
.calledWith('https://video.test/video-2') | ||
.mockReturnValue({ uri: 'https://video.test/video-2', label: () => 'Video 2' }); | ||
const page = await newSpecPage({ | ||
components: [PosApp, PosLabel, PosList, PosResource], |
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.
You could say supportsShadowDom: false,
here to make the test output more consice (get rid of mock:shadow-root)
it('sets about attribute on children', async () => { | ||
const page = await newSpecPage({ | ||
components: [PosList], | ||
html: `<pos-list rel="http://schema.org/video"><template><div>Test</div></template></pos-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.
some newlines would make this more readable
componentWillLoad() { | ||
subscribePodOs(this); | ||
subscribeResource(this); | ||
let templateElement = this.host.querySelector('template'); |
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 could be const
<this.templateNodeName | ||
innerHTML={this.templateString} | ||
about={it} | ||
onPod-os:resource={ev => this.provideResource(ev)} |
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.
Why ware we doing this instead of wrapping each child in a pos-resource
?
async provideResource(event: SubscribeResourceEvent) { | ||
event.stopPropagation(); | ||
this.provideQueue.push(event); | ||
this.provideResources(); |
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 did not get the need for the provide queue yet, tbh. Is there a respective test that fails if no queue is used that helps to highlight the problem?
@Watch('os') | ||
private async provideResources() { | ||
while (this.provideQueue.length > 0) { | ||
let ev = this.provideQueue.pop(); |
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.
Please use const whenever possible
if (resource) { | ||
ev.detail(resource); | ||
} else { | ||
this.provideQueue.push(ev); |
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 else part is untested but also unreachable, since resource is always != null
async provideResource(event: SubscribeResourceEvent) { | ||
event.stopPropagation(); | ||
this.provideQueue.push(event); | ||
this.provideResources(); |
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.
Iguess, while this.os is not present the list should not start rendering anything. If it doesnt there is no chance of children requesting a resource. After that we could just provide the resource as soon as requested, without a queue?
You don't need to bother with this in the PR, I will do this after merging before releasing a new version |
I am having an issue using this within another stencil component. If I paste an example directly in index.html it works, but the same example fails when I e.g. use it in pos-app-browser (just replacecing the regular pos-app content with the pos-list example). In the latter case it fails, because it does not recognize the template child (childElementCount == 0). Don't know why yet. |
It seems to be related to what is described here: https://stackoverflow.com/a/70778523 If I pass the content to |
I managed to show my bookmarks using
What is unfortunately still not possible with PodOS is actually linking to the value because I cannot use |
Partly addresses #43
end-to-end testintegration testarchitectural decisions are documented as an ADRKeep a Changelog
This PR descoped to exclude rev, given this will involve changes very similar to rel. Covering rev in a separate PR will hopefully make both PRs easier to review.