I have two components: A panel and a custom text field.
The panel has a viewmodel and I want to bind a value (called testData) from that viewmodel to a property (called test) of the custom text field.
That works fine ...basically.
But when the test property of the text field is changed, the testData in the viewmodel of the panel does not update accordingly. I mean when the test property of the child element (the textfield) is modified, the testData property of the panel's viewmodel should contain the same value that is in test, just like a normal two-way bind.
I'm not sure what I'm doing wrong, but here is what I've tried to far:
https://fiddle.sencha.com/#fiddle/20pu&view/editor
Ext.define('MyMain', {
extend: 'Ext.panel.Panel',
alias: 'widget.main',
width: '100%',
bodyPadding: 10,
viewModel: {
data: {
testData: 'Example Data'
}
},
bind: {
title: '{testData}'
},
items: {
xtype: 'myField',
bind: {
test: '{testData}'
}
}
})
Ext.define('MyField', {
extend: 'Ext.form.field.Text',
alias: 'widget.myField',
fieldLabel: 'Data',
width: '100%',
config: {
test: null // when test is changed, it should also affect the {testData} bind of the main component, causing the title to change
},
setTest(value) {
this.test = value + ' modified!' // because of the bind, this /should/ automatically get appied to the viewmodel's `testData` and thus to the panel title
this.setValue(this.test) // whenever the `test` property is changed, we write the contents to the value of the text field (just to visualize the `test` property).
// But as you can see, the panel title will still just say `Example Data` and not `Example Data modified!` as it should.
},
getTest(){
return this.test
}
})
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create('Ext.container.Viewport', {
items: [{
xtype: 'main'
}]
})
}
})
Update: (after reading your comments on other answers)
In general, mentioning the property in the config block and include it in publishes will make any property two-way bindable.
ExtJS will generate the getter and setter methods for it. The setter method takes care of binding. Now, whenever anyone updates the property value (using the setter), the new value will be passed on to the bound viewModel and in turn to the other components.
Accessing the property directly, this.test or this.viewModel.data.testData and assigning values to them will not be reflected in the controls bound to this property.
In case you are providing an implementation for the setter function (setTest) of a published property, ensure that this.callParent(...) gets called from it.
The usage of field's value property to display the contents of test caused the earlier confusion. Here is a fiddle with two-way bindable test property without any special handling in the MyField class.
Click on the 'Get test' button, the value should be 'Example Data' (from viewModel).
'Set testData' button will update the value in the viewModel. Use the 'Get test' button again to verify that the value of test has also been updated.
'Set test' button assigns a new value to the field's test property and this will be reflected in the panel's title.
Have a look at this forked fiddle.
In your implementation, the setTest method is directly changing the value of this.test to value + ' modified!'. This will not update the value of testData in viewModel as binding works via the getter and setter functions implemented of the properties specified in the config.
If you want to change title while changing Textfield then you have to bind value property because changing textfield's value only changes value property of field.
bind: {
test: '{testData}',
value : '{testData}'
},
If you don't want to bind it with value then on change event you have to set value of test property.
listeners : {
change : function(field, newValue, oldValue, eOpts ){
field.setTest(newValue);
}
}
Please refer fiddle.
First of all, you need to make the test config twoWayBindable.
This object holds a map of config properties that will update their
binding as they are modified.
Secondly, you don't need to define getters and setters for the config object, in your case.
Each config item will have its own setter and getter method
automatically generated inside the class prototype during class
creation time, if the class does not have those methods explicitly
defined.
You might to, but it will override the default methods which take care of updating the binding, among other things.
By standardize this common pattern, the default generated setters
provide two extra template methods that you can put your own custom
logic into, i.e: an "applyFoo" and "updateFoo" method for a "foo"
config item, which are executed before and after the value is actually
set, respectively.
The twoWayBindable config relies on the update template method, and when you specify your own setter, the update method will never get called, and the binding won't be updated.
In other words, when leveraging the config feature, you mostly never
need to define setter and getter methods explicitly. Instead, "apply"
and "update" methods should be implemented where necessary.
So, in your example, here are the steps you need to take:
Remove the setTest and getTest method declarations.
Add the twoWayBindable config containing test.
twoWayBindable: ['test']`
Hook up any additional logic into the applyTest or updateTest template methods. For example, updating the field value after the test value gets set.
updateTest(testValue) {
this.setValue(testValue)
}
Here is the working fiddle: https://fiddle.sencha.com/#fiddle/20rs&view/editor
In order to be able to bind custom class properties you need to list these in the twoWayBindable config.
Don't modify the value to be set in the setter and don't call the setter recursively. It is better to write an update<Fieldname>() function. Those are meant to handle updates in your view and they usually don't modify your data structures.
Based on 2.): Override the view update function of the form field to catch changes done to the value.
Here is the complete fiddle:
https://fiddle.sencha.com/#fiddle/218m&view/editor
Some things to note here:
after 3 seconds, the ViewModel testData value is updated
after 6 seconds, the setTest() setter of the field is called
after 9 seconds, the setValue() method from your input field is triggered
at the end, you could change the input field value to change the panel title
This is to illustrate the various scenarios.
Related
I have problems with data binding of my custom control.
My control inherits from sap.m.Input and extends it with a special value helper. One of my new properties of my new control is a simple header for the value help dialog. This is bound to an i18n model.
When I now use my control in a normal form, everything works. The title is bound correctly and shows the value of the bound i18n property in that model. If I use my control as a template in a column of a sap.ui.table control, it only shows the default value of the title property. Data binding does not seem to work. But is still working on the inherited properties (such as value).
For simplification here my control which now has only that title property and if value help is requested, it shows the current value in an alert box. In table, it shows the default value. And without table, it shows the bound value from i18n model.
Here the simplified control code:
sap.ui.define([
"sap/ui/core/Control",
"sap/m/Input",
], function(Control, Input) {
"use strict";
return Input.extend("DvpClsSuggestInput", {
"metadata": {
"properties": {
// Title of Value-Help Dialog
"vhTitle": {
type: "string",
defaultValue: "Title"
}
}
},
init: function() {
Input.prototype.init.apply(this, arguments);
this.setShowValueHelp(true);
this.attachValueHelpRequest(this.onValueHelpRequest.bind(this));
},
onValueHelpRequest: function(oEvent) {
var lvTitle = this.getVhTitle();
alert(lvTitle);
},
});
});
});
Usage in sap.ui.table.Table (which doesn't work and shows the default value of the title property):
<table:Column>
<m:Label text="{i18gn>HausWaehrung}" />
<table:template>
<dvp:MyInput
value="{ path: 'Inv>Hwaer', type: 'sap.ui.model.type.String' }"
vhTitle="{i18n>Currency}" />
</table:template>
</table:column>
Usage which works:
<VBox>
<dvp:MyInput
value="{ path: 'Cls>/Currency', type: 'sap.ui.model.type.String' }"
vhTitle="{i18n>Currency}" />
</VBox>
Once again, binding against the value property works in both ways. Problem only exists with my own property vhTitle. Any Ideas are welcome.
Do NOT use .bind when attaching event handlers to ManagedObject's events. The same applies to detaching event handlers. UI5 has its own documented mechanism for passing listener objects for those cases.
Example 1
Attaching / detaching a valueHelpRequest-handler using the corresponding APIs and passing values to the list of arguments as documented in the API reference:
myInput.attachValueHelpRequest(/*obj?,*/this.onValueHelpRequest, this); // No .bind!
myInput.detachValueHelpRequest(this.onValueHelpRequest, this); // Same references
Example 2
Attaching an event handler on control instantiation as documented in ManagedObject's API reference (All controls are ManagedObjects):
new MyInput({
// ...,
valueHelpRequest: [/*obj?,*/this.onValueHelpRequest, this]
});
Valid Names and Value Ranges:
[...]
For events, either a function (event handler) is accepted or an array of length 2 where the first element is a function and the 2nd element is an object to invoke the method on; or an array of length 3, where the first element is an arbitrary payload object, the second one is a function and the 3rd one is an object to invoke the method on [...].
Example 3 (For control developers)
In control definition, however, the listener can be omitted completely because the event provider itself (i.e. your control instance) becomes the listener by default if no listener object is passed.
this.attachValueHelpRequest(this.onValueHelpRequest); // the control instance will be used as the context in that event handler
This is described in the API reference as well:
If <oListener> is not specified, the handler function is called in the context of the event provider.
Drawbacks of using Function.prototype.bind in UI5
When calling .bind on a function, an entire new function is created!
const myFn = function() {};
myFn === myFn.bind(); // returns: false
Meaning if a handler is passed with .bind, that handler becomes never detachable because detachEvent awaits the same function reference and the same listener object reference as when attachEvent was called.
To make things worse, the function created with .bind won't let you change the previously passed thisArg (this) even if the EventProvider tries to call the function afterwards with a different thisArg. This limitation is described in the ECMAScript specification (See Note 2), and also the cause of the issue described in the question. When ManagedObject clones the template control for aggregation binding, the listener cannot be overwritten!
I have a directive that displays the logo of the selected component object.
<div avatar component-id="3"
component-object="Modio.selectedFacility"
image-url="Modio.selectedFacility.logo_url"
bind="true"
style="display: inline-block"
wrapper-style="{'display': 'inline-block' }"
allow-change-photo="true"
size="50"
change-photo="modioModal.showTeamProfileModal"
label="Profile">
</div>
When the user selects an object from the dropdown list, the method setselectedFacility is called from the service. In this method I call another method getFacilityLogoUrl that sends a request to the server to get the logo and as long as there is no response, a directive is called and set default image. When I get the response the update of logo does not occur.
Directive seems doesn't know that something happened.
this.setSelectedFacility = function(facility) {
this.selectedFacility = facility;
if (this.selectedFacility) {
this.getFacilityLogoUrl(this.selectedFacility.id).then(function(url) {
_this.selectedFacility.logo_url = url;
}, modioException.errorFn);
}
//Save to local storage
localStorageService.set('selected-facility', this.selectedFacility);
};
How can I say to a directive to update an old value.
Watcher in my directive:
if (scope.bind) {
scope.$watch('componentObject', function(newValue, oldValue){
if (newValue) {
render();
}
});
} else {
render();
}
I'm not certain because you haven't posted the code for your selectedFacility watcher, but the problem could be that you are using a shallow watcher, but you need a deep watcher, because you are watching object properties.
Try using the third argument to the $watch function. For example:
$scope.$watch('selectedFacility', function (newVal, oldVal) { /*...*/ }, true);
By default, $scope.$watch() will only check for value changes, where the value for primitives is their value, and the value for objects is their reference. Therefore, default watches will only trigger when the object being watched is reassigned.
For objects and arrays you can use a deep watcher, as above, or $scope.$watchCollection, which will watch for reference changes one level deep.
Another solution is to use immutable objects, so that all changes to an object require creation of a new object (and therefore a reference change.) This works very well with AngularJS and Angular's dirty-checking change-detection strategy.
I have this Vue.js code:
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
return this['my' + 'Value']
}
}
})
As you can see the computed property will be cached and it is depended only on data.myValue. My question is how Vue.js caching system knows that run the computed function again only if myValue is changed?
If I change the myOtherValue variable, the myComputed function will use the cache, and will not be run again will I call it.
I thought about several ways how it is possible. But how Vuejs doing that?
I have read this article: https://v2.vuejs.org/v2/guide/computed.html and found no answer.
And what happen in this code, what it will be depeneded on?
const flag=2
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
if (flag==1){
return this['my' + 'Value']
}
else
return this['my' + 'Other' + 'Value']
}
}
})
Bonus: I will appreciate I link to the relevant function in the VueJS code: https://github.com/vuejs/vue
I will address only the specific question how does vue.js know which dependencies affect which computed property?
The simple answer is that each time vue evaluates a computed property it creates a map of all the reactive properties that were accessed in the span of that call. The next time any of these reactive properties change they will trigger a reevaluation of the computed property.
If during the most recent evaluation of a computed property, one of its reactive dependencies is never reached (maybe because it is within the non-traveled path of an if/else construct), subsequent changes to that reactive property will not trigger a reevaluation of the computed property.
Observe this behavior by modifying the two reactive properties in this fiddle (by simply typing in their corresponding input boxes). A few things to note:
the called computed property is evaluated once on document load (it's triggered because it's rendered in the template).
because the path is set to 1 the reactive property that will be mapped as a dependency is val1. As a result it will be the only one that can trigger a reevaluation of called when it changes. The value of val2 can also change but will not have the same effect on called, even though it's clearly present in the function.
When you click on the "Change Path" button, path is toggled from 1 to 2.
right after the path switch, note that a change to val1 will affect called only once more. Because path has been set to 2 prior to that last reevaluation, val1 will not be reachable and will not be mapped as a dependency of called any longer. Subsequent changes to its value won't trigger a reevaluation of called from that point on. But then val2 has now been mapped as a dependency of called and changes to it trigger the reevaluation the same way they did for val1 earlier. It will be so until the next path toggle from 2 back to 1.
Here's the code.
let path=1
let count=0
const vm=new Vue({
el:"#app",
data:{
val1:null,
val2:null,
},
computed: {
called: function(){
if (path==1){
this.val1
}
if (path==2){
this.val2
}
return "I was just called "+ ++count +" times"
}
},
methods: {
changePath(){
path = path==2 ? 1 : 2
}
}
})
and corresponding template
<div id="app">
<input v-model="val1"/> {{val1}}
<br>
<input v-model="val2"/> {{val2}}
<br>
<button #click="changePath">change path</button>
<br>
{{ called }}
</div>
It's the reactivity system of Vue.js, not a caching system.
The data in a component will be convert to getters and setters. When you access a value via a getter, the getter will add it to the dependencies, and when you modify the value via a setter, the setter will notify everyone who depends on the value.
Here is the source code, all the magic happens in this function: https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L131
From the docs it reads that:
Computed properties are cached, and only re-computed on reactive dependency changes.
However the following fiddle shows something a bit different.
https://jsfiddle.net/z11fe07p/267/
From the fiddle if you set the flag to 2, the computed property will be re-evaluated and executed if you change myOtherValue, however this will not happen if the flag is set to 1. I think it keeps track of your if conditions.
In the docs usually you can find links to the relevant source code.
Here is the code for computed properties:
https://github.com/search?utf8=%E2%9C%93&q=repo%3Avuejs%2Fvue+extension%3Ajs+%22computed%22&type=Code
I'm building an application with ExtJS 6.
I've already read the guides, tutorials and best practice tips.
But what I dont understand yet is, why should I use the config object?
With config:
Ext.define('MyProject.foo.Bar', {
extends: 'Ext.window.Window',
...
config: {
title: 'My title'
}
});
Without config:
Ext.define('MyProject.foo.Bar', {
extends: 'Ext.window.Window',
...
title: 'My title'
});
Both are working as expected.
Can anyone tell me the difference and possible benefits?
It's all described in the Class System guide:
Configurations are completely encapsulated from other class members
Getter and setter methods for every config property are automatically generated into the class prototype during class creation
if methods are not already defined.
The auto-generated setter method calls the apply method (if defined on the class) internally before setting the value. You may override
the apply method for a config property if you need to run custom logic
before setting the value. If your apply method does not return a
value, the setter will not set the value. The update method (if
defined) will also be called when a different value is set. Both the
apply and update methods are passed the new value and the old value as
params.
I recently found this great component -> Knockout-Kendo.js.
I use it to handle some behaviors with kendoComboBox.
The synchronization with the viewmodel works perfectly.
I want to listen changes of the control to execute some actions based on the current selected value.
I don't see any property that I can bind in the 'data-bind' attribute to listen changes but I know that internally, the knockout-kendo component listen changes and this is how the viewmodel is able to by sync with the control.
If I try to listen the valueChange event of the control, the problem is my eventhandler is catched before the viewmodel and while running in my eventhandler, I just have the previous value of the control using the viewmodel.
Look at this binding configuration in the component. What I understand is I'm able to use 'enabled', 'search', 'data', 'value' and any other exposed properties of the telerik control. What would be nice would be to define in data-bind attribute a property 'change' with an eventhandler linked in my viewmodel and be sure my eventhandler would be called after the internal eventhandler of the knockout-kendo component.
createBinding({
name: "kendoAutoComplete",
events: {
change: VALUE,
open: {
writeTo: ISOPEN,
value: true
},
close: {
writeTo: ISOPEN,
value: false
}
},
watch: {
enabled: ENABLE,
search: [SEARCH, CLOSE],
data: function(value) {
ko.kendo.setDataSource(this, value);
},
value: VALUE
}
});
I know I can try to modify the order of bind of events to be sure my eventhandler must be called after the synchronization of the viewmodel but I think it's a very bad practice.
Anybody have an idea how I can solve this problem with elegance?
You haven't mentioned why you want to do this. I can imagine two reasons:
To trigger some UI behavior/logic directly;
To trigger business logic (which may in turn trigger UI changes of course);
For people landing at this question with the latter case, here's an alternative solution. (This answer may not be a straight up answer to the OP's question, but seems useful enough to post it here.)
Suppose you have this basic view model:
var ViewModel = function() {
var self = this;
self.kendoObservable = ko.observable("Some text")
};
There are two ways you can indirectly respond to changes by Kendo. First, for simple cases, there's computed observables:
// Option 1, add this to ViewModel
self.dependentObservable = ko.computed(function() {
return self.kendoObservable() === "" ? "Empty" : "Not empty"; // example
});
This dependentObservable will be modified each time the kendoObservable changes. Basic stuff.
If you want to do something more complex when kendoObservable changes, e.g. do an AJAX call or whatnot, you may need a manual subscription:
// Option 2, add this to ViewModel
self.kendoObservable.subscribe(function(newValue) {
// Possibly do an AJAX call here or whatnot. Example:
alert("The new value is: " + newValue);
});
This will allow you to fire some complex logic each time the kendoObservable changes. AFAIK you need to check yourself whether the newValue is actually a changed value, at least in some versions of KO.