Unexpected databinding in case of {{ syntax with subobjects in tapevent

databinding

#1

I have a Label showing the status of an editable variable and a Button which toggles the value of the editable state.

<Page loaded="onLoaded">

<StackLayout>

<Label text="{{'editable: ’ +title.editable}}"/>

<Button text=‘toggle editable’ tap=‘onTap’/>

</StackLayout>

</Page>

I expected the label content and the edit value would be synchronized automatically. However, i find that setting the new state does not trigger a screen update of the label value. The view is only updated if I first set the observable attribute to null and then set the new value. What is wrong in my code ?

I have made two examples. One use an observable pageBindingContextobject which contains a title.editable. In this example I would expect that pageBindingContext.set(‘title’, title) would trigger the update. It doesn’t. I have to precede it with pageBindingContext.set(‘title’, null) to get the job done.I do not understand why.

var observable = require(“data/observable”)

//… editable is part of observable

exports.onLoaded=function(args){
args.object.bindingContext=new observable.fromObject({‘title’:{‘editable’:‘false’}})
}
exports.onTap=function(args){
var pageBindingContext = args.object.bindingContext;
console.log(pageBindingContext)

var title=pageBindingContext.get('title')
var editable=title.editable

// if (editable===‘true’) {pageBindingContext.set(‘editable’,‘false’)} else pageBindingContext.set(‘editable’,‘true’)
if (editable===‘true’) {title.editable=‘false’} else title.editable=‘true’
console.log(‘tapped…new pageBindingContext…’)
// console.log(pageBindingContext.get(‘editable’))
console.log(title.editable)
pageBindingContext.set(‘title’,null) // if left out, editable is not updated in xml view
pageBindingContext.set(‘title’,title)
}

In the second example I made editable an attribute of an observable. In this case I would expect that simply setting editable to a new value would trigger an updata of the screen. This is not the case. Again I have to first set the attribute ti null and then set the new value. Again I do not understand why this is necessary.

//... editable is observable exports.onLoaded=function(args){ args.object.bindingContext=new observable.fromObject({'title':new observable.fromObject({'editable':'false'})}) }

exports.onTap=function(args){
var pageBindingContext = args.object.bindingContext;
console.log(pageBindingContext)

var title=pageBindingContext.get('title')
var editable=title.get('editable')
if (editable==='true') {editable='false'} else editable='true'
title.set('editable',null)  // if left out, editable is not updated in xml view
title.set('editable',editable) // if left out, editable is not updated in xml view
console.log(' editable...'+editable)

}

Please can someone explain what is the right way to code this case ? Thx


#2

I don’t see any issues with binding sub object.

If you still have issues, please update the playground example and share with us.


#3

Probably you have testedin the workaround text. The problem still exists.

  1. I opened the playground link above which appeared not to contain the example you entered. It showed a playground test I did before. Should I see the text you entered ?

  2. I entered my examples in playground and copied the link to my saved example here https://play.nativescript.org/?template=play-js&id=ffvqhj&v=5. I hope this is the right way to share ?

  3. In my playground test the same problem exists as I described before. That is, if you comment out in example 2 the line pageBindingContext.set(‘title’,null), then the edit state will not update on the screen. I did this in the playground example. If you undo the commenting out, i.e. pageBindingContext.set(‘title’,null) is executed,then it updates ok. The same is true in the first example ( P.S. you have to change loaded=‘onLoaded1’ into loaded =‘onLoaded2’; and tap=‘onTap1’ into tap=‘ontap2’). My question is why I need the workaround.


#4

@Jokro - I’ve seen weirdness when I try to databind an object, such as what you are doing. That is, if your binding context was simply {title: "text", editable: false} then in your onTap code you could simply have

pageBindingContext.set('editable', !pageBindingContext.editable);

By my experience, just changing the property of a databound object is not sufficient, and that’s why I suspect you need to replace the full title object. In my own code, I have several places where I’ve created booleans at the top binding context level to avoid this very problem.

Hope this helps…


#5

That is a valid error. You must verify whether the object exists (title) before accessing it’s property (editable).


#6

The line with pageBindingContext.set(‘title’,null) is not an error, but a workaround. Without this line the observable does not update its textfield. With this line the textfield is updated. What is not clear is why the observable needs this workaround.

My hypothesis is that if an observable is nested inside an object which is not an observable,its two-way binding is lost. For example if pageBindingContext={status:new observable.objectFrom{editable:false}} editable has no binding with {{status.editable}} in the xml code. This is unexpected behaviour. I am looking for an explanation and a solution to this unexpected loss of mustache binding.


#7

The basic problem is that a changing a nested observable does not trigger an update of the xml field.
The example contains a bindingContext=new observable.fromObject({‘title’:new observable.fromObject({‘editable’:‘false’})}), which should trigger a {{title.editable}} in the xml file.

The problem discussed above is that my workaround (also) raises a non-fatal error flag. I have further devoped the workaround so that it wont raise the error flag. , see playground demo.

In playground I have a Label with text= "{{title.editable}} and a button which onTap should toggle= the value of title.editable… In the js file title.editable is defined as a nested observable of the page context. My first try for the onTap function is shown in onTap1 (in the playground example, press button 1). This solution seems logic to me but it does not work. Until now nobody has made clear why.

In onTap2 (the second button in playground) I made a clone of the title observable, changed editable in the clone and then set title to clone. This solution does not raise a (non fatal) error. Still I am curious if there is a better solution. In my opinion there should be. I hope a designer of Nativescript is willing to react.


#8

As mentioned in the documentation when using custom expression, you must give the source property first followed by expression.

<Label text="{{title.editable, 'editable: ' + title.editable}}" />

https://docs.nativescript.org/core-concepts/data-binding#using-expressions-for-bindings


#9

manojdcoder, thx for your answer, this is the solution. Sorry I apparantly missed this point in the documentation.


#10

manojdcoder, In a listview with itemTemplateSelector I have the same problem, see playground example.In this case the solution you proposed is not valid. In tapping on an item both the shown age should change and the colour of the item. The age does change, but the colour does not, implying that the itemTemplateSelector in the xml file is not updated. I cant find a solution in the documentaion. Thank you for helping…


#11

It’s expected behavior, changing a property will not call the template selector again. A workaround could be using binding expression for background color property / refreshing the list item manually which requires some native code / refresh the whole list if that suits your requirement.