Detecting change to Knockout view model

Javascriptknockout.js

Javascript Problem Overview


Sure this is a very easy question to answer but is there an easy way to determine if any property of a knockout view model has changed?

Javascript Solutions


Solution 1 - Javascript

Use extenders:

ko.extenders.trackChange = function (target, track) {
    if (track) {
        target.isDirty = ko.observable(false);
        target.originalValue = target();
        target.setOriginalValue = function(startingValue) {
            target.originalValue = startingValue; 
        };
        target.subscribe(function (newValue) {
            // use != not !== so numbers will equate naturally
            target.isDirty(newValue != target.originalValue);
        });
    }
    return target;
};

Then:

self.MyProperty= ko.observable("Property Value").extend({ trackChange: true });

Now you can inspect like this:

self.MyProperty.isDirty()

You can also write some generic viewModel traversing to see if anything's changed:

self.isDirty = ko.computed(function () {
    for (key in self) {
        if (self.hasOwnProperty(key) && ko.isObservable(self[key]) && typeof self[key].isDirty === 'function' && self[key].isDirty()) {
            return true;
        }
    }
});

... and then just check at the viewModel level

self.isDirty()

Solution 2 - Javascript

You can subscribe to the properties that you want to monitor:

myViewModel.personName.subscribe(function(newValue) {
    alert("The person's new name is " + newValue); 
});

This will alert when personName changes.

Ok, so you want to know when anything changes in your model...

var viewModel = … // define your viewModel

var changeLog = new Array();  

function catchChanges(property, value){
    changeLog.push({property: property, value: value});
    viewModel.isDirty = true;
}

function initialiseViewModel()
{
    // loop through all the properties in the model
    for (var property in viewModel) {

        if (viewModel.hasOwnProperty(property)) { 

            // if they're observable
            if(viewModel[property].subscribe){

                // subscribe to changes
                viewModel[property].subscribe(function(value) {
                    catchChanges(property, value);
                });
            }
        }
    }
    viewModel.isDirty = false;
}

function resetViewModel() {
    changeLog = new Array();  
    viewModel.isDirty = false;
}

(haven't tested it - but you should get the idea)

Solution 3 - Javascript

Consider using Knockout-Validation plug-in

It implements the following:

> yourProperty.isModified() - Checks if the user modified the value. > > yourProperty.originalValue - So you can check if the value really changed.

Along with other validation stuff which comes in handy!

Cheers

Solution 4 - Javascript

You might use the plugin below for this:

https://github.com/ZiadJ/knockoutjs-reactor

The code for example will allow you to keep track of all changes within any viewModel:

ko.watch(someViewModel, { depth: -1 }, function(parents, child) { 
    alert('New value is: ' + child());
});

PS: As of now this will not work with subscribables nested within an array but a new version that supports it is on the way.

Update: The sample code was upgraded to work with v1.2b which adds support for array items and subscribable-in-subscribable properties.

Solution 5 - Javascript

I had the same problem, i needed to observe any change on the viewModel, in order to send the data back to the server, If anyone still intersted, i did some research and this is the best solution iv'e managed to assemble:

function GlobalObserver(viewModel, callback) {	
	var self = this;
	viewModel.allChangesObserver = ko.computed(function() {
	    self.viewModelRaw = ko.mapping.toJS(viewModel);
	});
	viewModel.allChangesObserver.subscribe(function() {
		callback(self.viewModelRaw);
	});
	self.dispose = function() {
		if (viewModel.allChangesObserver)
			viewModel.allChangesObserver.dispose();
		delete viewModel.allChangesObserver;
	};
};

in order to use this 'global observer':

function updateEntireViewModel() {
    var rawViewModel = Ajax_GetItemEntity(); //fetch the json object..    
	//enter validation code here, to ensure entity is correct.
    if (koGlobalObserver)
        koGlobalObserver.dispose(); //If already observing the older ViewModel, stop doing that!
    var viewModel = ko.mapping.fromJS(rawViewModel);		
    koGlobalObserver = new GlobalObserver(viewModel, Ajax_Submit);
    ko.applyBindings(viewModel [ ,optional dom element]);	
}

Note that the callback given (in this case 'Ajax_Submit') will be fired on ANY change that occurs on the view model, so i think it's really recommended to make some sort of delay mechanism to send the entity only when the user finished to edit the properties:

var _entitiesUpdateTimers = {};

function Ajax_Submit(entity) { 
    var key = entity.ID; //or whatever uniquely related to the current view model..
    if (typeof _entitiesUpdateTimers[key] !== 'undefined')
        clearTimeout(_entitiesUpdateTimers[key]);    
    _entitiesUpdateTimers[key] = 
        setTimeout(function() { SendEntityFunction(entity); }, 500);           
}

I'm new to JavaScript and the knockout framework, (only yestarday i started to work with this wonderfull framework), so don't get mad at me if i did something wrong.. (-:

Hope this helps!

Solution 6 - Javascript

I've adapted @Brett Green code and extended it so that we can have AcceptChanges, marking the model as not dirty plus having a nicer way of marking models as trackables. Here is the code:

var viewModel = {
    name: ko.observable()   
};

ko.track(viewModel);

http://jsfiddle.net/david_freire/3HZEu/2/

Solution 7 - Javascript

I did this by taking a snapshot of the view model when the page loads, and then later comparing that snapshot to the current view model. I didn't care what properties changed, only if any changed.

Take a snapshot:

var originalViewModel = JSON.stringify(ko.toJS(viewModel));

Compare later:

if(originalViewModel != JSON.stringify(ko.toJS(viewModel))){
	// Something has changed, but we don't know what
}

Solution 8 - Javascript

Consider a view model as follows

function myViewModel(){
    var that = this;
    that.Name = ko.observable();
    that.OldState = ko.observable();
    that.NewState = ko.observable();

    that.dirtyCalcultions - ko.computed(function(){
    // Code to execute when state of an observable changes.
});
}

After you Bind your Data you can store the state using ko.toJS(myViewModel) function.

myViewModel.Name("test");
myViewModel.OldState(ko.toJS(myViewModel));

You can declare a variable inside your view model as a computed observable like

that.dirtyCalculations = ko.computed(function () {});

This computed function will be entered when there is change to any of the other observables inside the view model.

Then you can compare the two view model states as:

that.dirtyCalculations = ko.computed(function () {
  that.NewState(that);
  
  //Compare old state to new state
  if(that.OldState().Name == that.NewState().Name()){
       // View model states are same.
  }
  else{
      // View model states are different.
  }

});

**Note: This computed observable function is also executed the first time when the view model is initialized. **

Hope this helps ! Cheers!!

Solution 9 - Javascript

I like Brett Green's solution. As someone pointed out, the isDirty comparison doesn't work with Date objects. I solved it by extending the subscribe method like this:

    observable.subscribe(function (newValue) {
            observable.isDirty(newValue != observable.originalValue);

            if (newValue instanceof Date) {
                observable.isDirty(newValue.getTime() != observable.originalValue.getTime());
            }
        });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1389723View Question on Stackoverflow
Solution 1 - JavascriptBrett GreenView Answer on Stackoverflow
Solution 2 - Javascriptweb_bodView Answer on Stackoverflow
Solution 3 - Javascriptdzl-ptView Answer on Stackoverflow
Solution 4 - JavascriptZiadView Answer on Stackoverflow
Solution 5 - JavascriptEitan H.S.View Answer on Stackoverflow
Solution 6 - JavascriptDavid FreireView Answer on Stackoverflow
Solution 7 - JavascriptSgraffiteView Answer on Stackoverflow
Solution 8 - JavascriptSushrut KanetkarView Answer on Stackoverflow
Solution 9 - JavascriptYamo93View Answer on Stackoverflow