Apologies if this has been asked before, I have searched but I'm finding it very hard to express my problem in a search friendly way. And I can't figure it out from the knockout documentation, however it seems like a basic question.
I have 3 select lists and a Knockout view model. Selecting a value in the first list updates an observable in the view model. I then need to make an ajax post, sending that value to the server and retrieving a list of values which I put in an observable array in the view model, which will in turn update the other 2 lists.
I'm happy with wiring up to observables and that part is working fine, my question is how and where to trigger the ajax call.
If I trigger it on the change event of the first select it seems to cause a race condition which means it sometimes gets called before the view model has updated. I could trigger it without using the observable, but that doesn't seem very knockoutish.
If I use a custom binding for retrieving values it will cause the ajax call to be made twice and I can't put the retrieval in a function because it needs to run asynchronously (and it would be called twice).
I feel like I need something which listens to an observable and triggers an ajax call without any visual element.
Any help would be gratefully received.
Triggering things that should happen in response to view model changes generally works through subscriptions in knockout.
function ViewModel() {
var self = this;
self.someValue = ko.observable();
self.otherValue = ko.observable();
self.someValue.subscribe(function (newValue) {
// do something with newValue, like an Ajax request.
// assuming jQuery
$.get("your/url", {val: newValue})
.done(function (data) {
self.otherValue(data);
})
.fail(function () {
alert("could not retrieve value from server");
});
});
}
Related
After reading the docs, this is my understanding of sync.
I instantiate some Backbone.Model and call Collection.create(). create() eventually calls sync() and the Model is POSTed to the server. Then there is a sync in the opposite direction such that the Model on the client is given an id.
Does this update then trigger componentDidUpdate()?
Note: componentDidUpdate is a ReactJS thing, so if that doesn't make sense, the question reduces to "Is the client-side model updated and the view re-rendered?"
Since inside of my componentDidUpdate() I am making a call to save() to keep everything up to date, this subsequently makes a call to sync() which then fires a PUT request (because the Model already has an id).
I'm asking, because in my current application, creating a TodoItem seems to result in a POST and then a PUT which I find redundant. Perhaps it is for an unrelated reason.
It actually fires two POSTS and then two PUTS when adding one item, but that is another question.
The first time you save a model (one which doesn't have an id) it will make a POST, thereafter it will make a PUT (update). I think you are confusing when to use create/add/save:
Use save at any point to save the current client collection/model state to the server
Use add to add Model(s) to a collection (a single Model, an array of Models, or an array of objects which contain attributes and let the collection create them)
Use create as a shorthand for creating a model, adding it to the collection, and syncing the collection to the server.
My guess is that you are calling create and save in one operation - you should be using add and save instead, or just create.
The view will not automatically update for you, you will need to listen to changes or events on the collection/model and update the view yourself - there is no equivalent of componentDidUpdate. For example:
initialize: function() {
this.listenTo(this.collection, 'sync', this.onCollectionSync);
},
onCollectionSync: function() {
this.render();
}
I'm using bootstrap-switch together with the knockout binding handler referenced from this question shown below:
ko.bindingHandlers.bootstrapSwitchOn = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
$elem = $(element);
$elem.bootstrapSwitch();
// Set intial state
$elem.bootstrapSwitch('setState', ko.utils.unwrapObservable(valueAccessor()));
$elem.on('switch-change', function (e, data) {
// Update the model when changed.
valueAccessor()(data.value);
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var vStatus = $(element).bootstrapSwitch('status');
var vmStatus = ko.utils.unwrapObservable(valueAccessor());
if (vStatus != vmStatus) {
$(element).bootstrapSwitch('setState', vmStatus);
}
}
};
This seems to be working quite nicely and I've mocked up a fiddle to illustrate how I'm using it here:
http://jsfiddle.net/swervo/of0q42j0/5/
However, I have a few issues which I can't seem to solve in a satisfactory manner:
1) If I have an array of items in an ko.observable array I can put a click handler on all of them and have them call a function in the parent view model like this:
data-bind="click: $parent.clickHandler"
Which, when called, passes through the items own view model. This is really convenient for getting properties of the item that was clicked, eg., id. I've put a button in the fiddle above to illustrate how easy this is to do.
However, if I'm using the bootstrap-switch instead of a simple button the switch doesn't seem to know about it's parent and I can't find an elegant way of passing through the view model containing the switch to its parent - like you can with a button. I have tried giving each item in the array a reference to it's parent view model and this does work but creates a circular reference and thus doesn't seem like the correct approach.
2) In the application that I'm building the state of items in a list can be changed on a different clients - and the local state needs to update to reflect these remote clients. Equally the state can also be changed on the local client which is then propagated to other clients. My problem here is how to disambiguate between changes to state that have happened locally (ie., due to the user clicking on the switch), and changes that have happened remotely (ie., due to an update coming from the server). In my actual project I'm using knockout subscribe to listen for changes in the values linked to the switches like this:
viewModel.observableValue.subscribe(function(newValue) {
// test value on server and if it is different update
});
I want to avoid receiving an update from the server and then updating the server again (with the same state) when my switch changes to reflect the new state. At the moment I've fixed this by testing the server state (as implied in the code snippet above) before I send the update and if it is the same as the pending state update I discard it. (I've simulated a server update using a button in the referenced fiddle above).
Neither of my solutions to these problems feel elegant hence the question here.
Any help would be much appreciated.
I'm not sure what you mean by the 'the switch doesn't seem to know about it's parent'. Looking at http://knockoutjs.com/documentation/custom-bindings.html, I can see that init and update both have a 5th param, bindingContext that has the parent information, should you wish to access it.
Ahem, one of the projects we worked on the past had a toggle button that suffered from the same issue and it was fixed is a very simple way. For events that are generated locally, just attach a property to the object, like .local = true; and check for it in the update (or attach it in your REST handler) to distinguish local/vs REST. Don't forget to delete the property from the view model once done in update though.
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();
});
In my Controller, I'm quering data from a $resource object bundled in a caching service.
$scope.data = myService.query(); //myService caches the response.
In the same controller I have the configuration for a chart (for the GoogleChartingDirectve).
$scope.chart = {
'type': 'AreaChart',
'cssStyle': 'height:400px; width:600px;',
....
'rows' : convert($scope.data),
...
}
convert is just a function which takes the data from myService and returns an array for the chart.
This is only working, if the response of the query has already been cached (After changing the route, I can see the graph, but if I call the route directly it is empty).
Perhaps the problem is my caching?
angular.module('webApp')
.service('myService', function myService($cacheFactory,res) {
var cache = $cacheFactory('resCache');
return {
query: function() {
var data = cache.get('query');
if (!data) {
data = res.query();
cache.put('query', data);
}
return data;
}
};
});
I've tried the $scope.$watch in my Controller. It is fired only once with no data. If I change to a different route and back again it is fired again; this time with data.
$scope.$watch('data',function(newValue){
console.log("Watch called " + newValue);
}
I'm not sure what the problem actually is.
It looks like the $watch event is missing.
If I call the refresh with a button, $watch is fired and data is shown.
$scope.refresh = function(){
$scope.data = $scope.data.concat([]);
}
I'm just an angular newbie and playing around in a small demo project.
So I'm looking for the best way to solve some common problems.
Any idea how to solve my problem?
I guess the problem is that you are not aware that res.query() is an asynchronous call. The $resource service works as follow: the query call returns immediatly an empty array for your data. If the data are return from the server the array is populated with your data. Your $watch is called if the empty array is assigned to your $scope.data variable. You can solve your problem if you are using the $watchCollection function (http://docs.angularjs.org/api/ng.$rootScope.Scope):
$scope.$watchCollection('data', function(newNames, oldNames) {
var convertedData = convert($scope.data);
...
});
Your cache is working right. If you make your server call later again you got the array with all populated data. Btw. the $ressource service has a cache option (http://docs.angularjs.org/api/ngResource.$resource) - so you don't need to implement your own cache.
You may ask why the $ressource service works in that way - e.g. returning an empty array and populating the data later? It's the angular way. If you would use your data in a ng-repeat the data are presented to the view automatically because ng-repeat watches your collection. I guess you chart is a typical javascript (for example jQuery) that doesn't watch your data.
The title of the question pretty much sums it up, I'd like my view to respond differently to a model instances initial save vs any future saves. Right now I'm grabbing the model's isNew attr before I save and then triggering a custom event, but I was wondering if there was anything built in?
Checking model.isNew() is the built-in way of telling whether the initial save has happened yet. If checking isNew is working for you, keep on doing it.
The initial save should issue an ID for the object, so you could bind a function to "change:id" and it would execute after the initial save succeeds. Or you could add logic to the "success" and "error" callbacks of create().
With help from this answer, I came up with the following solution:
var originalSync = Backbone.sync;
Backbone.sync = function(method, model, options) {
console.log(method);
originalSync.apply(Backbone, [method, model, options]);
};
I can now check what method is being called.