Skip to content
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

[Feature] Bind property of component readonly #3381

Open
seijikun opened this issue May 16, 2021 · 2 comments
Open

[Feature] Bind property of component readonly #3381

seijikun opened this issue May 16, 2021 · 2 comments

Comments

@seijikun
Copy link
Contributor

Description:

I've now been in the situation where I wanted to observe one of my child component's state multiple times.
For example, imagine a validating TextBox component, that surfaces whether it is currently valid through a property.
Normally when binding to a child component's property, the binding is initialized by writing the parent's value into the child, thus overwriting the child's initial value.

Since there already is a bind-<property> syntax, I imagined something like robind-<property> or readonlybind-<property>:

<TextBox robind-isValid="{{state.isTextboxValid}}"/>

As far as I understood it, binding to the computed properties of a child is not possible at the moment (at least the binding always seemed to overwrite the computed property for me). I don't know the rationale behind this, but maybe such a readonly binding would make it possible to bind to the computed values of a child?

Overall, then allowing something like this (wishful thinking):

<html>
	<head>
		<title></title>
		<script src="https://cdn.jsdelivr.net/npm/ractive"></script>
	</head>
	<body>
		<div id="content"></div>
		<script>
			let ValidatingTextBox = Ractive.extend({
				data: function() { return { value: '' }; },
				computed: {
					'isValid': 'value.length > 0'
				},
				template: `
					<input type="text" value="{{value}}" required/>
				`
			});
			
			var instance = new Ractive({
				components: { ValidatingTextBox },
				el: '#content',
				data: function() {
					return {
						inputStates: {
							textbox0: false,
							textbox1: false
						}
					};
				},
				computed: {
					allValid: 'inputStates.textbox0 && inputStates.textbox1'
				},
				template: `
					<ValidatingTextBox robind-isValid="{{inputStates.textbox0}}"/>
					<ValidatingTextBox robind-isValid="{{inputStates.textbox1}}"/>
					
					Valid: {{allValid}}
				`
			});
		</script>
	</body>
</html>
@evs-chris
Copy link
Contributor

After thinking about it a bit, what you want isn't really a read-only binding. I think what you're after is a reverse binding, so rather than setting up a binding from the parent into the component, you want to set up a binding from the component into the parent that is controlled by the lifecycle of the parent, where there's no reason it couldn't also be read-write. Does that sound right?

I'm not sure exactly what the best way to indicate that in the template would be, so if anyone has any bike-shedding they'd like to apply, the feedback would certainly be welcome!

I also think a binding overriding a child's computed property could be problematic, so there may be some question as to whether or not that should be allowed.

As far as bind-property="value" and property="{{value}}", there's currently no difference in those at all. Perhaps link-property="value" could be the reverse of a binding. I think an issue with that would be that if you happened to have multiple instances of the same link, there's no way to change how it's resolved e.g. link-.foo="bar" vs link-^^/foo="bar". Those both look horrible in most editors too. Perhaps this would be the time to bring out the little know second argument to mustache interpolators and just use link="{{local.path component.expression}}"?

Another option is handling this with events, and that would be my suggested workaround for now. Something like

Ractive.helpers.link = function(parent, child, local, target) {
  parent.link(target, local, { instance: child });
}
<ValidatingTextBox on-init="link(@this, $1, 'inputStates.textbox0', 'isValid')" />

The helper could be simplified a bit if it always links from child to parent, as you could just pass the child ($1) and use it's parent property for the link. Anyways, here's what that looks like in the playground.

@seijikun
Copy link
Contributor Author

seijikun commented May 18, 2021

After thinking about it a bit, what you want isn't really a read-only binding. I think what you're after is a reverse binding, so rather than setting up a binding from the parent into the component, you want to set up a binding from the component into the parent that is controlled by the lifecycle of the parent, where there's no reason it couldn't also be read-write. Does that sound right?

I was somehow under the impression that bindings do not have a direction, since value updates are always synchronized in both directions. Though after reading your comment, and the documentation for ractive.link(), that makes absolute sense.

I also think a binding overriding a child's computed property could be problematic, so there may be some question as to whether or not that should be allowed.

When I first tried it, I somehow expected Ractive to know, that he can't write there. And that I get an error when changing the variable in the parent (like you do if you attempt to write into a readonly computed property within the same instance). But the property was simply overriden, and the initial value never reached my variable in the parent.
My next attempt then was to make the computed property read-write, using:
(hoping that the initially synchronized value will land in the set() method)

computed: {
   isValid: {
       get() {},
       set() {}
   }
}

Though that also didn't work. Now - after your comment, and the documentation of ractive.link() - I understand where this behavior comes from. Maybe throwing an error when a binding shadows an existing computed property in the child would be in order?
Or supporting to bind to computed properties in both directions. I'd also be quite happy with that... ;)

That's a cool workaround, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants