I have to use valueHasMutated method in ko to fire subscription on page load while binding Country list dropdown so that I can fetch state on the basis of selected dropdown, is there an alternative to this ? How can I fire the country subscription without using this method ?
Country List: <select id="CountryDropdownList" data-bind="options: viewModel.CountryCollection,optionsText:'CountryName',optionsValue:'CountryName',value:viewModel.SelectedCountry"></select>
State List: <select id="StateDropdownList" data-bind="options: viewModel.StateCollection,optionsText:'StateName',optionsValue:'StateName',value:viewModel.SelectedState"></select>
<script>
var viewModel = ko.mapping.fromJS(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model)));
console.log(viewModel.SelectedState()); //State3 the initial value
viewModel.SelectedCountry.subscribe(function (newSelectedCountry) {
alert(newSelectedCountry);
console.log(viewModel.SelectedState()); //undefined why?
$.ajax({
url: 'Home/GetStateList?Country=' + newSelectedCountry,
success: function (data) {
viewModel.StateCollection(ko.mapping.fromJS(data)());
console.log(viewModel.SelectedState()); //state0 why?
}
})
});
ko.applyBindings(viewModel);
$(function () {
//to fire subscription
viewModel.SelectedCountry.valueHasMutated();
})
</script>
Don't specify viewmodel.on your bind variables in the HTML. When you call ko.applyBindings, it creates the default context for your bind variables.
If the values of the value bound variable doesn't match any of the options values, it will (by default) be set to undefined. This may be what is happening (see your "undefined why?" comment). Try adding the valueAllowUnset option. I'm probably explaining this wrong. But you'll want to check the initial values against the options, at any rate.
I guess that the problem is in attaching the event handler after the complete construction of the model object by calling fromJS.
All observables do already have their values set, so the subscribed function is not called without the manual call to valueHasMutated.
A solution could be by
first creating the model, including the subscribe to the SelectedCountry observable.
instantiate this model and set the observable values
This way the subscribed function will be called, because the handler is attached before values are set.
Related
I have a store where I want to set a filter in the beforequery event.
The problem is when I do it like this
store.addFilter({property:'name', value: 'xxx'});
the actuall addFilter call will send a request, which is a problem because it will fire the beforequery event again.
So I add the supresevent parameter to the function call
store.addFilter({property:'name', value: 'xxx'}, true);
but, in this case the request doesn't have the filter param at all.
Am I missing something ?
All I want to is to add a filter in a beforequery event and the let it do a request
There's a property on the store called autoFilter. Set it to false. It's a private property, therefore it's not in the doc.
I've been struggling with a problem in a similar context. I wanted to set a filter upon the store using the setFilter() method. But using this method I had encountered that the store triggered the load event twice. One request included the filter parameters, the other one didn't.
Request1:
Request2:
This behavior resulted in wrong information in my component. After a while of tinkering I encountered that I can bypass the setFilter() method by invoking the filter() method of the store directly.
Grid-Code:
...
initComponent: function () {
var me = this;
me.callParent(arguments);
me.getStore().filter([
{
property: someProperty,
value: someValue
}
]);
},
...
Applying the filter this way has solved the issue for me. I hope this will help you as well.
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've a view with knockout.js which has some textboxes and dropdowns.
known when the user changes a value i save the data with a $post
for this i created some computed propties like
self.subjectChanged ko.computed(function () {
var subject self.subject();
//save...
But this also triggers when the subject was loaded from database and set for first time.
What is the best practice for this ?
A similar problem is that i have a function getdata() which depends on two properties.
Now on load this method is raised twice (for each property)
What are best practices to handle this szenarios ?
One way of doing it is to load the page and bind the data as normal, and then use subscriptions to monitor changes to the observable you are interested in.
http://knockoutjs.com/documentation/observables.html#explicitly-subscribing-to-observables
viewModel.subject.subscribe(function(newValue) {
// code you want to run when the value changes...
});
for example http://jsfiddle.net/m8mb5/
This may not be best practice, but in the past I tied a loaded variable to the vm and when the data finished loading from the server I set it to true;
In my computeds I would surround the code that actually did the work in an if that checked the loaded. Computeds can be a little tricky though, you may need to reference the observables outside of the if to ensure they fire correctly.
com = ko.computed(function(){
if(loaded){
var subject = self.subject();
}
// reference observable outside of if to ensure the computed fires when the observable changes
self.subject();
});
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 backbone collection and under some circumstances I am fetching more models into the collection with:
collection.fetch({data: {...}, add: true});
I need view of the collection to be re-rendered when new members arrive.
"reset" event is not fired because of add:true parameter hence I see two options.
Bind this.render function to collection's "add" event. This is work but makes render function to be called for each new model arrived from server.
Pass silent:true as fetch parameter and call this.render() explicitly in next line, however at next line data still not arrived, hence render function called with old data.
I haven't found any other way to overcome the issue :(
Any advises what should I do in this case?
you can also pass success callback as option to the fetch method and trigger full rerender after successful ajax operation if this is your preferred way of doing it
collection.fetch({data: {...}, add: true, success: function() { ... } });
// or reference to function - you get the drill