-
Notifications
You must be signed in to change notification settings - Fork 24
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
POC - Adding subscription controllers #1901
Conversation
this.removeEventListener('d2l-selection-input-subscribe', this._handleSelectionInputSubscribe); | ||
} | ||
|
||
getController(controllerId) { |
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 don't love this, but I couldn't think of another great way to do both:
- Expose the controller without a forced naming convention AND
- Have the ability for there to be multiple provider controllers in a single component
Visual diff tests failed - pull request #1902 has been opened with the updated goldens. |
helpers/subscriptionControllers.js
Outdated
} | ||
} | ||
|
||
export class SubscriberController { |
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 don't think this controller reads super well on how you can use either the event method or the for
method because the consumer of the controller can set themselves up for both. Tried to add some comments throughout
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 sense to just split it into a EventSubscriberController
and a ForPropertySubscriberController
? Looking at how the logic is split (event fired in hostConnected
) and property changes handled in updated
, it seems like that could work and be cleaner.
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.
Yeah they're fairly different. The tricky part is that selection components can use either, depending on whether the for
property is set, and we don't know which method is being used in the constructor. But attaching both controllers and then just using one of them sounds cleaner, it's probably not a common use case. Filter would just use the ForPropertySubscriberController
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.
Cool I like that.
Still trying to work through the labelledby mixin to see if I can use any of this, but it's really specific. I think I'll open that as a separate PR to determine if it's worth it or not. |
helpers/subscriptionControllers.js
Outdated
} | ||
} | ||
|
||
export class SubscriberController { |
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 sense to just split it into a EventSubscriberController
and a ForPropertySubscriberController
? Looking at how the logic is split (event fired in hostConnected
) and property changes handled in updated
, it seems like that could work and be cleaner.
|
||
_updateProviders() { | ||
let providerIds = this._host[this._forPropertyName]; | ||
if (typeof(providerIds) === 'string') providerIds = [providerIds]; |
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.
Shouldn't it always be a string? If we want to support multiple I'd expect it to still be a string but have the IDs be space-separated.
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.
Interesting, I automatically assumed it'd be an array of strings. But string with spaced ids works 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'm curious what Lit's default attribute processor does for arrays. Like would it turn <d2l-foo ids="one two three">
into an array if that property had an Array
type? I'm good with the property being an array as long as that's how the parsing works (which matches what HTML does for things like aria-labelledby
).
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.
So looks like no - if I set type
to Array
and pass in a string, things blow up. It's trying to run JSON.parse(<string_I_pass_in>)
. It looks like I should be able to write a custom converter to take in the string and convert it to an array. Or we just expect a spaced string and handle it, or just expect an array and handle it.
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.
Yeah I was just thinking about those custom parsers -- if they're easy to write that could be nice.
@@ -0,0 +1,140 @@ | |||
import { cssEscape } from '../../helpers/dom.js'; | |||
|
|||
export class ProviderController { |
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 later: I don't love this name since it overlaps with our RequesterMixin
/ProviderMixin
and could cause some confusion.
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.
Yeah I tried out lots of different names and didn't like most of them. We could do publisher/subscriber, but that's an actual model that this doesn't seem to really follow. Sender/Receiver maybe, but that seems more like a 1-to-1 relationship. Sender/Subscriber?
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 don't mind "provider" but maybe just add more context to it? Similar to ForAttributeSubscriberController
-- it's a controller for subscribing to the "for" attribute of a host.
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.
SubscriptionProviderController
, EventSubscriberController
, and IdSubscriberController
?
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 those work, ya! Also wondering if we should start a controllers
top-level directory... they're similar to mixins in that sense.
this.dispatchEvent(evt); | ||
this._provider = evt.detail.provider; | ||
}); | ||
this._subscriberController.hostConnected(); |
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 a good sign -- once we go to Lit 2, connectedCallback
, disconnectedCallback
and updated
can be removed.
helpers/subscriptionControllers.js
Outdated
} | ||
|
||
hostConnected() { | ||
if (!this._eventName || this._host[this._forPropertyName]) return; |
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 weirdest part about splitting this controller into two is this line. Should I add a callback to ask the consumer whether they actually want us to fire the event (or if they ended up getting the for
attribute set instead), or do I just send the event? Sending the event when we don't need to means:
- It goes off into space and no one actions it, and we don't care
- A provider does hear it and adds us as a subscriber, but we basically ignore that provider. There is a hypothetical case where the provider is trying to update us and we don't want it to, but I can specifically protect against that in the selection components
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 think this highlights that I didn't really understand what was going on here. I assumed that the code was only needed in the EventSubscriberController
case, but it looks like additionally if it's using BOTH controllers then it should still not fire the event?
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's only needed for the event case, but the selection components are weird. They basically ask you to provide a selectionFor
property, and if you don't, they use the event case. If you do, they use the ForProperty/Attribute
case.So I only evere need to use one or the other, but when I'm setting up controllers in the constructor
, I don't yet know if we're going to get a for
property. I'm assuming I can't setup controllers (the real Lit ones) in the connected
callback?
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 do think the divided works much better, I'll push that change so you can see
|
||
this._eventSubscriberController = new EventSubscriberController(this, {}, | ||
{ eventName: 'd2l-validation-custom-connected' } | ||
); |
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.
Is the event subscriber needed? Seems like the custom validator isn't expecting descendent elements to fire this event.
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.
As far as I can tell, the event is used by FormMixin
and FormElementMixin
to allow for someone to put a d2l-validation-custom
element inside of one of them without adding a for
id. Like, if d2l-input-text
wanted to instead just put a d2l-validation-custom
element inside of itself. There are no examples of this on the demo pages and I had to hack something together to see it happen. When we actually implement this, I think it's worth trying to figure out if anyone actually ever uses this, and decide if that's something we'd still want to support.
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.
Whoa! Yeah I'd definitely lean towards just removing that functionality altogether.
return this._forElement; | ||
if (this.for) { | ||
// Validation custom components only support one for element | ||
return this._forPropertySubscriberController.providers[0]; |
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 suggest doing an array length check first and return null
otherwise. Consumers could have been relying on this being null
if no element was connected and now accessing this property will throw an array index out of bounds exception.
} | ||
_onUnsubscribe() { | ||
if (this._forPropertySubscriberController.provider.length === 0) { | ||
throw new Error(`validation-custom failed to find element with id ${this.for}`); |
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.
When does this end up getting thrown? Seems like before it would throw during connectedCallback
-- which arguably could be too soon if there was a delay rendering the element it's looking for.
Having some kind of built-in error throwing capability in the controller itself might be a nice future enhancement too... could simplify the LabelledBy
code as well as it does a lot of its own error throwing.
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.
Yeah I didn't test this out before/after but wanted to keep it in to remember - I assume it always "just worked" because people are putting the validator right beside the element. I was looking at adding a "waitTime" to handle LabelledBy
anyways, I wonder if I could generalize that to always search for three seconds, and then call an error callback. I don't think we'd setup an error callback for selection or filter because it's not the end of the world if the element doesn't exist, they'll just handle themselves accordingly?
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 like the idea of an error callback a lot. 👍
Closing this - #3819 is an up-to-date version. |
This create a new
ProviderController
andSubscriberController
that can be used in mixins or components to setup a subscription model. There are pieces I like about it, and pieces I don't. I'll add more context throughout.