Visitar URL original
[Feature] Bind property of component readonly · Issue #3381 · ractivejs/ractive · GitHub
Skip to content

[Feature] Bind property of component readonly #3381

Open

Description

@seijikun

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>

Activity

  1. evs-chris commented on May 18, 2021

    @evs-chris
    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.

  2. seijikun commented on May 18, 2021

    @seijikun
    ContributorAuthor

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions