The code can be found on http://jsfiddle.net/6kMWM/10/.
In the FilterViewModel I am creating an observable object.
var FilterViewModel= ko.observable({
Name: ko.observable("test"),
Code: ko.observable("test"),
Number: ko.observable("test")
});
Then in the BankViewModel I am running a computed method which when any of the input boxes change it should fire.
var BankViewModel = function(){
var self = this;
self.Collection = ko.observableArray([]),
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
alert("invoked");
}),
self.add = function (bankObject) {
self.Collection.push(bankObject);
},
self.isSelected = function (data) {
$('.bank').css('background-color', 'white');
$('.bank p').css('color', '#333');
$('#bank-row-' + data.Code()).css('background-color', 'blue');
$('#bank-row-' + data.Code()+" p").css('color', 'white');
}
};
For some reason it is not being fired. Can any one help me out please.
Thank-you in advanced
There are several problems with your fiddle:
You bind to values instead of observables. When you write <input
type="text" data-bind="value: global.filterViewModel().Name()"
placeholder="Filter by Name"/> ko uses the value of global.filterViewModel().Name not the observable. Thus there is no real binding (updating ko will not update the interface, updating the interface will not update ko). You need to remove the last parenthesis: global.filterViewModel().Name
You put Name instead of Code and vice versa in the binding
You subscribed to FilterViewModel's changes, but not it's child observable changes. To do this, include the evaluation of the child observables in your computed observable:
-
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
if (filter.Name() != 'testname')
alert("name");
if (filter.Code() != 'testcode')
alert("code");
if (filter.Number() != 'testnumber')
alert("number");
}),
You can test here http://jsfiddle.net/b37tu/1/
You need to instantiate your view model with a statement like this:
var model = new BankViewModel();
When the model is instantiated, its computed methods are evaluated initially. This is where your alert will fire.
But, i assume you want your computed method to subscribe to Name, Code and Number properties. In this case, you need to read these properties at least once in your computed method.
This is how dependency tracking works in KO. It records all the observables that you mention in your computed function and logs them. And your computed is evaluated again when one of those observables are updated.
For your code up there, your computed will subscribe to FilterViewModel but not to its individual properties Name, Code and Number. So if you need to subscribe for the changes in these individual properties, you have to mention them individually in your computed function. Well, it wouldn't make sense to have your computed to subscribe to them if they don't affect your computed function anyway.
If you want to learn how the process works, please take a look at its documentation:
http://knockoutjs.com/documentation/computedObservables.html
Related
I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.
I am using the click event on a button to set the value of an item that was generated using a foreach.
<table>
<tbody data-bind="foreach: Employees">
<a data-bind="click:$parent.delete()">
..
in my delete function I am setting the value but it doesn't update the screen
Delete :(emp) {
emp.active=false;
}
When I create I am setting all the individual properties as observable but seems like they are not when in the foreach loop.
Update
Employees is filtered.computed
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(AllEmployees(), function (empid) {
return empid.ID == filter();
});
When you get/set observables you need to call them like this:
var val = obj.prop(); //Getter
obj.prop(false); //Setter
One other issue you have is that you are using parenthesis in your click binding. Remember that Knockout bindings are just javascript, so it will actually execute that expression when it binds.
You need to get rid of those parenthesis or emp will be undefined initially.
UPDATE:
I've updated this jsFiddle to include three filtered lists similar to what you have shown above. You can see that using a filtered list via a computed has no bearing on how knockout handles the bindings, and the UI updates seamlessly.
http://jsfiddle.net/jwcarroll/ceRPK/
To set an observable, you have to call it (since observables are implemented as functions):
emp.active(false);
Your method simply overwrites the observable.
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually by using myObservable.subscribe()
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually using myObservable.subscribe()
Edit
If you are trying to have your computed keep track of what should be in your computed you can do so like this -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active === true;
});
});
That works if active is not an observable property of each allEmployees(). If it is an observable just change that to -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active();
});
});
I have this code in a 'widget'. Value is the widgets value, other is a source of data from elsewhere in the app. The computed creates a subscription for value even though it is never read. This means when I update value, comp is run and value is set back to other..
value = ko.observable(1); // Widgets value
other = ko.observable('a'); // Somewhere else in app
comp = ko.computed(function () {
value(other());
doSomeThingElse();
});
value(2);
// comp is run
value() === 'a'; // true
Basically I need comp to only run if other is changed. I see there is now a peek function but that is only for read. Why does a computed even create a subscription for an observable that is never read? This is very frustrating. Is there any way around this?
The comp functions are used on a widget to set their data from external sources, the actual layout of a widget would be:
new Widget({
id: 'widget',
initial: 5,
observables: {
data: function () {
var data = client.get('data'); // observable
this.value(data);
this.color(data.length ? 'red' : 'green');
}
}
})
When the widget is created it makes a new computed from observables.data. We would hope to keep this simple api without having to do manual subscriptions which would complicate the widget.
Edit:
The problem was infact an error with my code, the above examples were suppost to be simplified but actually cut out the issue. See this fiddle if interested http://jsfiddle.net/dominata/hu6Fr/.
After consideration we are going to use this model instead of using computeds purely for side effects. I agree this is more how they are intended to be used.
new Widget({
id: 'widget',
initial: 5,
value: function () {
return client.get('data');
},
color: function () {
return client.get('data').length ? 'red' : 'green';
}
})
Setting value in your computed does not create a subscription. You would have a subscription to other. The only question would be if you access value as part of doSomethingElse.
Here is a fiddle that shows that the computed is initially evaluated, but not evaluated after updating value: http://jsfiddle.net/rniemeyer/exV92/
I agree with #nemesv that you would likely want to be careful with setting an observable in the computed. Your current case should work (barring what is doing in doSomethingElse).
Another option might be to use a manual subscription. If you want to update value only when other changes, then you can do:
other.subscribe(function(newValue) {
value(newValue);
doSomethingElse();
});
With a manual subscription it will only trigger when that specific observable changes, so you don't have to worry about which dependencies you are accessing.
Does anyone have idea, if it's possible to detect whenever a change occur inside observableArray while using ko mapping, this mean without having to create the model by hand?
self.items = ko.observableArray([])
var data = ko.mapping.fromJS(result)
self.items.push(data);
I would like to log any changes occurred in any property inside my array of objects.
Thanks
If I'm not mistaken, you should be able to use subscribe on the observable to get that information.
See the bottom of this page for more info:
Explicitly subscribing to observables
self.items = ko.observableArray([])
self.item.subscribe(function(context){
//Comment if there is any changes in the observable array this function will get trigger and changed date ma get in context
});
In the following code the idea is that if you fill in 'test' that this is considered invalid and shouldn't write the value to the observable. The problem I have is that my custom binding is somehow resetting the observable's value.
A working version of the code below: http://jsfiddle.net/6hcpM/1/
var underlying = ko.observable('foo');
var errors = ko.observableArray()
var isValid = ko.dependentObservable(function(){
return errors().length === 0
});
var vm = {
name : ko.dependentObservable({
read : underlying,
write : function(value){
errors([]);
if(value==='test'){
errors([ 'Cant be test matey' ]);
};
if(isValid()){
underlying(value);
};
}
})
};
vm.name.isValid = isValid;
ko.bindingHandlers.validateCss = {
update: function(element, valueAccessor) {
observable = valueAccessor();
observable.isValid(); //this somehow causes the problem
}
};
ko.applyBindings(vm);
Your validateCss binding creates a dependency to vm.name.isValid. When you write "test" to the field, errors is updated, which causes isValid to update, which triggers all of the bindings in the input's data-bind attribute to be re-evaluated. So, the value binding gets evaluated again and replaces the current value with the value from name (which is stored in underlying).
So, basically the value binding is running again based on isValid changing.
There are several ways that you could handle it. You could let underlying get updated every time and, if necessary, store a clean value elsewhere that only gets updated when it is valid.
You could also put your custom binding on a different element. For example, you could wrap your input in a span or div and put the binding on it. I assume that you want the validateCss binding to assign a CSS class when the value is invalid.