I have a question using underscore isEqual to compare two JSON strings. Currently i have done an app in backbone, and I'm using _.isEqual(savedModel.toJSON(),changedModel.toJSON() ) to detect if the model has changed in the page and promt a "You have unsaved changes, do you want to save?" dialog if the user tires to navigate away.
For some reason I get the dialog in random places even though I have done nothing or have saved changes. Debugging is driving me crazy.
Could this be because JSON does not guarantee the order of the objects in the JSON and underscores isEqual does not handle this case properly? So even if the models are the same, some attributes in the JSON might be different and it returns false?
Pseudocode:
//when entering the page the original model is cloned, when user does changes to the
//page, the model is cloned again
var savedModel = currentModel.clone().toJSON();
//when the user tries to navigate away from the page
if( _.isEqual(savedModel, model.toJSON() ){
showSavePromptDialog();
}
Following the chain of functions used by backbone.toJSON(), it appears _.extend is used to copy the object and _.extend uses a for..in loop to iterate over the object. for..in iterates over an object in arbitrary order, which is likely the source of your problem.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
Hi this deep equals implementation done to solve similar problem, but I might have missed out some finer details, it was serving well for my purpose.
http://yui3.wordpress.com/2013/04/22/deep-compare-in-javascript/
Related
I have a user object, and I want to track changes in case the user edits their information, however they can say 'discard changes' and it returns to the previous state of the user object on load.
My solution was to deepClone the original object into a backup var, to give it it's own reference points, then compare the user object to the backup object --- again using lodash
I watch the object using angular #$watch, and if !_.isEqual(user, backup).
$scope.$on '$routeChangeStart', (e, next) ->
if !_.isEqual(user, backup)
console.log 'changes made'
However this returns false, even when there are no changes? It's as if the two objects are not equal any longer, even though all the keys and values are identical? I assume more happens than what I see when I deepClone.
Any better approaches? And what am I doing wrong?
The problem is most likely that user has been altered by angular. Angular adds some properties for tracking to objects bound to the view (these properties start with $ or $$). If your objects don't have such 'native' properties, you could try to do a deepOmit on user first.
This is a classic example when the concept of immutability would help a lot. If your object would be immutable a copy of it would only mean a pointer to the original object and comparisons are thereby more robust in my opinion. Check out https://facebook.github.io/immutable-js/ if you would like to know why facebook has embraced it (and maybe try using it yourself).
I do not have time to create a fiddle right now, but will definetely do so tomorrow.
Basically my problem is caching a data model retrieved from a restful get endpoint and comparing to a new model returned by a restful updated endpoint in order to be able to highlight the changed values in the UI.
The way I handled this is by using underscore's each() and angular.compare() methods in order to loop through a collection and compare it object key by object key.
However this feels wrong and I have problems in getting the updated key name.
Is there a better, accepted way to do this as I cannot find anything anywhere, just a bunch of people generally asking the same question and getting answers like: 'Use a watcher and underscore/angular methods, it is easy'.
For what I have understand, what you are trying to do is the correct way to see it.
You have to compare object by object.
To help you between the old collection and the new one, a watcher is not a bad answer. In fact the angular watcher can give you the old collection and the new one as parameters. So all you got to do is make a check object by object. And had a special treamenton the different values.
Hope this can help you.
I have two objects, obj1 and obj2. I want one to have the same properties and values as the other when the user clicks a button, so I write this line:
main.obj1.data=$.extend({},main.someArray[0].data);
But now updating main.obj1.data automatically updates main.someArray[0].data and vice versa. I tested it by checking that this is true with console.logs immediately after that line of code. I thought that this command would clone the objects but not make them aliases of the same object. What am I doing wrong?
I have messy code to sort through before this command...is there anything I might have put in my code before that point which would cause $.extend to no longer work like I think it should?
There is two way to solve this
1.
main.obj1.data={};
$.extend(main.obj1.data,main.someArray[0].data);
2.
main.obj1.data=$.extend(true,{},main.someArray[0].data)
Actually both is doing the same thing
You can read more about $.extend()
jQuery extend copy the values so the copied object shouldn't be linked to the initial object. You can verify that in this jsfiddle: http://jsfiddle.net/cFtA7/ (open your console and run the script).
The first parameter in $.extend(true,{},main.someArray[0].data) serve to deep copy your object. If your object has many levels, use this parameter, otherwise, it's not needed.
I'm guessing, but is that possible that your extend code is on a bind object that is called every time you update either one of the values?
There appear to be a number of different ways how to access properties of a Sencha (Touch) model. However, I don't seem to be able to find proper documentation of which is the "correct" way of doing it.
Model creation
var model = Ext.create('MyApp.model.MyModel', {
name: value,
foo: bar,
...
})
Property access
model.get('name') or model.set('name', newValue)
model.data.name or model.data.name = newValue
model.raw.name seems to always return a string no matter what the data type in the model definition is?
Let's sort this all out:
get and set methods are the intended accessors for model field values.
model.data is the object that stores the client side model value, that is that have been converted from the data received from the server proxy using the fields configuration (type, convert method, etc.).
model.raw is the raw data that was received from the server proxy, before it was converted to client side application domain model values. You should avoid using it, or you will tie yourself to your proxy/server.
model['name']: as you've said, it doesn't work. Don't hope for it to come back (I don't even really understand that it worked at one point).
Now, which one should you use? Clearly, the last two ones are already out of the match.
The model.data object should give you the expected result in most cases (see bellow), and should give you a marginal performance gain other calling a function.
However, IMO you should always prefer to use the getters and setters, for two reasons.
First, it might happen that someone in your team (or you from the past) decides that the getter/setter is a good point to add some custom logic. In this case, bypassing the accessor by using the data object directly will also bypass this logic, and yield unpredictable result.
Secondly, getters and setters make it really easier to debug some situations, by making it easy to know from where modifications of the model values are coming. I mean, if one day you were to ask yourself "f**k, why is my model field value changing to this??". If all the code uses the getters, you'll just have to put a breakpoint in there, and you'll catch the culprit hand in bag. On the other hand, if the incriminated code uses the data object directly, you'll be stuck to do a whole project search for... You can't tell exactly what... data.name =? data['name'] =? name:? etc.
Now that I think about it, there is yet another reason. A deprecated one apparently. But the data object name used to be customizable using this persistenceProperty option. So, in some cases, the data object won't even be available, and code doing model.data.name instead of model[model.persistenceProperty].name would crash, plain and simple.
Short answer: use the accessors.
I'm using JSON.stringify and JSON.parse to store and retrieve objects from localStorage. However, it appears that JSON.stringify strips out the instance functions from the object. Thus, after JSON.parse, I can no longer call myObject.doSomething(). I know that I can attach this function manually: myObject.doSomething = MyClass.prototype.myFunction, but that'll be troublesome if this action is repeated many times in the web app. How do people normally do this in JavaScript?
JSON obviously does not hold onto the functions themselves is only stores simple typed variables. The way I have addressed this in the pass is to be a restore method in my class and simply call that method with the data from JSON so as to re-populate the class with the data that belongs in it.
I have done this extensively with the Value Object ( VO ) design pattern in my code base and it has worked quite well for me. Just a word of a caution though, Ie7/Ie8 are not terribly friendly with this approach if you try to communicate across windows. As I recall I think it is IE7 that does not return the right "typeof" for some properties so I ran into a whole bunch of challenges in my restore when cross-window communication was involved.