`ReflexiveAttribute`?
Closed this issue · 4 comments
Create an attribute type which invokes an instance of the attribute it is placed on, along with its attributes, in order to allow e.g., a DefaultValue
, ImplicitValue
, or other value attributes to be defined using the same type and settings as the attribute. This would be an improvement over the current approach, which effectively only supports scalar values, and requires knowledge of how those types will be serialized.
That said, this introduces some challenges—and notably, how to dynamically set the EditorType
in order to load the value from the correct source—which may not be practical, and will require additional thought.
Establishing the ModelType
shouldn’t be too difficult. We know it’s grandparent should be a ContentTypeDescriptor
describing the type it’s intended to map. It can use that information to construct a new AttributeDescriptor
derivative using TopicFactory.Create()
, and then relay that ModelType
.
The only caveat is it won’t be able to do this if the grandparent is the ReflexiveAttributeDescriptor
as that would cause an infinite loop. In that case, we can hard code ModelType.ScalarValue
.
Update: The above information is incorrect. The grandparent will always return the
ContentTypeDescriptor
theReflexiveAttribute
was added to, but this may not be the type of theEditingTopicViewModel
sinceAttributeDescriptor
s can be inherited from parent content types.
We’ll also need to handle this type of scenario in the view, so it doesn’t attempt to invoke a ReflexiveAttributeViewModel
, which would likewise result in an infinite loop. In that case, we can just return a message explaining the situation. It doesn’t make sense in our case to have e.g., a DefaultValue
on a DefaultValue
regardless, as that’s highly specific to the individual AttributeDescriptor
, so this shouldn’t be a major limitation.
The second challenge is how to get access to the appropriate view model for invoking the target view component. This seems easy in that he AttributeDescriptorViewModel
will also be the EditingTopicViewModel
.
Unfortunately, though, the type of object will be incorrect, as we need a derivative of AttributeDescriptorViewModel
, not EditingTopicViewModel
, which the EditorController
explicitly maps the currentTopic
to.
Options
EditorController
Explicitly handle this scenario in the EditorController
by mapping to an AttributeDescriptorViewModel
if the CurrentTopic
is an AttributeDescriptor
. This would require that AttributeDescriptorViewModel
derive from EditingTopicViewModel
. This is less than ideal, as the editor shouldn’t be aware of the needs of any one attribute type.
ITopicMappingService
Inject an ITopicMappingService
into the ReflexiveViewComponent
so that’s the currentTopic
can be remapped as the specific AttributeDescriptorViewModel
derivative identified in the ITypeLookupService
. This introduces a dependency on ITopicMappingService
and ITypeLookupService
, which the StandardEditorComposer
doesn’t currently have. This would either need to break backward compatibility by exposing those dependencies to the constructor, or be hardcoded, which isn’t ideal in terms of dependency injection.
Note: Since the OnTopic Editor is an application, it might seem to make sense for it to define its own dependency graph. But as it is embedded in other applications, we’d expect it to take dependencies supplied to the
StandardEditorComposer
, as it does today. Ultimately, as well-known dependencies that can optionally be constructed independently of theStandardEditorComposer
, this might be tolerable. But it’s less-than-ideal, and should be resolved in OnTopic 6.0.0 (since we don’t want to make a breaking change to a minor release).
The above proposal regarding the ModelType
is incorrect. Unfortunately, there isn’t an easy way around this without making the EditorController
aware of the ReflexiveAttribute
(e.g., to get the ModelType
off of the CurrentTopic
if the attribute is a ReflexiveAttributeDescriptor
). I’d really like to avoid making the editor aware of any one attribute type—which is why we currently store them as plugins in a separate library.
Without this, the value will always be stored as in Topic.Attributes
instead of e.g., Topic.Relationships
or Topic.References
.
This is accomplished by mapping the CurrentTopic
as an AttributeDescriptorViewModel
in the ReflexiveViewComponent
(aebcc6f), so that the view can invoke the CurrentTopic
as an attribute view component using the AttributeDescriptorViewModel
it expects, with the current values configured for the AttributeDescriptorViewModel
(2f44fc1).
This required that a dependency on the ITypeLookupService
and ITopicMappingService
(aebcc6f) be introduced, which have been added to the StandardEditorComposer
(3efa14f).
Unfortunately, adding these to the constructor, as we'd usually do with dependencies—and which is a best practice for dependency injection!—isn't possibly without breaking the OnTopic Editor 5.0.0 contract, and thus this change will need to wait until OnTopic Editor 6.0.0. Positively, these are well-known objects which will always be available—since one is local, and the other is from the core OnTopic
library—and these aren't dependencies we'd expect implementors to need to swap out. Therefore, for backward compatibility, they're being hard-coded into the StandardEditorComposer
. As this operates as part of the composition root, and implementors can always wire up attribute view components using their own custom logic without relying on the StandardEditorComposer
, this isn't a huge issue, but it's still a bit _ugly), and something we'll want to fix with OnTopic Editor 6.0.0.
This functionality was merged into develop
as part of ebcc2b3.