Closed hkvstore closed 4 years ago
To explain what you're doing wrong consider the following template:
<script id="myTmpl" type="text/x-jsrender">
Name (as JSON): <input data-link="{toJson:name || name.first || name.last:fromJson}"/><br/><br/>
First: <input data-link="name^first"/>{^{:name^first}}<br/>
Last: <input data-link="name^last"/>{^{:name^last}}
</script>
<div id="page"></div>
There are a couple of issues with your code. Your fromJson converter creates and returns a new object. It does not modify properties on an already existing object. What this means is that when you type into the json textbox your converter will be called and it will return a completely new object. This is why you need to use the name^first
expression for your first and last textbox. The ^
character makes the expression react to replacing the name object as well as modifying the property.
Typing into the first or last textbox modifies those properties on the name object but your json textbox does not monitor those properties. It's looking to see if the name object itself is replaced by a new instance which it's not. A hackish solution I just showed you just includes the first and last properties in the expression to trick the template into updating when the properties change as well but return the actual name object.
So basically you're not keeping track on which objects and properties are being monitored and updated by each data link expression.
I hope this explains why you have these issues.
Thank you for your explanation. What I'm trying to do was to use JsViews to modify a JSON string with form controls. What will be the best solution if the object has a lot of properties and it becomes hard to put them all in the expression to trick the template?
You could use a computed observable instead of the converter. Try this:
<script id="myTmpl" type="text/x-jsrender">
Name (as JSON): <input data-link="json()"/><br/><br/>
First: <input data-link="name.first"/>{^{:name.first}}<br/>
Last: <input data-link="name.last"/>{^{:name.last}}
</script>
<div id="page"></div>
var myTmpl = $.templates("#myTmpl"),
data = { name: { first: "Jo", last: "Blow" } };
// create a computed observable
data.json = function() {
return JSON.stringify(data.name);
}
data.json.set = function(val) {
var x = JSON.parse(val);
// observably update the individual properties on the name object
$.observable(data.name).setProperty(x);
}
// make the computed observable depend on whatever properties are on the name object
data.json.depends = ['name.first', 'name.last'];
myTmpl.link("#page", data);
The the idea is to attach all logic to the computed property "json".
In this example the get function will be invoked as a result of either "first" or "last" properties being changed as expressed by the data.json.depends = ['name.first', 'name.last'];
statement.
Typing into the json textbox will call the set function which will parse the string into a javascript object and the setProperty function will (when passed an argument of type "object") observably copy the values from the properties of this temporary object to the already existing name object. This is what updates the first and last textboxes.
@BorisMoore as discussed in this issue it was stated that parens at the leaf node is optional for depends and observe paths. Does this apply to data-link expressions in templates as well?
This template work correctly <input data-link="json()"/>
but <input data-link="json"/>
does not.
Here's a fiddle. Type into the name textboxes and the second json textbox to see the effect.
I understand I can use JsObservable to observe the name but I wonder if I can do it with JsViews only.
Problem of above suggestions is, it is better not necessary to list each property that would trigger changes.
Use Object.keys(name) to get the list of property names to make it dynamic like so data.json.depends = Object.keys(data.name).map(function(x){return "name."+x});
Or data.json.depends = ['name.*'];
Hah how simple. I didn't even consider using '*' . Excelent!
@johan-ohrn and @hkvstore - thanks for your discussion above.
Johan, in response to your question, above, parens are required, in the case of data-link="someComputed()"
.
If you don't put the parens, on a <span data-link="someComputed"
>for example, then it renders as someComputed.toString(). However
<input data-link="someComputed">
will evaluate someComputed() because jQuery detects that it is a function, just in the case of <input>
and evaluates it. So it is a spurious behavior...
if ( isFunction ) {
args[ 0 ] = value.call( this, index, self.html() );
}
I'll close this now...
I tried the follows:
When the page is run, the "Name" textbox can display JSON string, but subsequent changes to the "First" or "Last" text box will not change the value in the "Name" textbox.
Is it possible to bind an object directly? (I understand I can use JsObservable to observe the name but I wonder if I can do it with JsViews only.)