How to make JSData save an instance property to localStorage - javascript

How can I make JSData update a modified object that is saved to localStorage?
The code below saves a Tree object with two apples saved to it in a container object. Now updating that container and saving it 'mixes in to the existing instances' as stated in the docs here.
Q: How can I prevent this mixin behavior so the object contains just one apple after saving?
Plunker
var adapter = new DSLocalStorageAdapter();
var store = new JSData.DS();
store.registerAdapter('localstorage', adapter, { default: true });
var Tree = store.defineResource('tree');
Tree.create({
id: 1,
apples: {1: 'one', 2: 'two'}
}).then(function(tree){
tree.apples = {1: 'one'}
tree.DSSave().then(function(tree){
console.log(tree.apples) //
})
});

You're looking for the onConflict option, which defaults to "merge".
This ought to do it:
tree.apples = {1: 'one', 2: null};
tree.DSSave({ onConflict: 'replace' })
.then(function (tree){
console.log(tree.apples);
});
2.x: http://www.js-data.io/v2.9/docs/dsdefaults#onconflict
3.x: http://api.js-data.io/js-data/latest/Collection.html#onConflict

Related

Knockout JS not setting all members observable

What I am trying to do is to get data from the server and then putting it all in an observable and then make all the properties observable. The issue I am facing is that it does not make all my properties observable and I need them all to be observable as sometimes depending on the data it makes some properties observable and sometimes it doesn't.
var viewModel = this;
viewModel.Model = ko.observable();
viewModel.SetModel = function (data) {
viewModel.Model(ko.mapping.fromJS(data));
}
The data that I am receiving from the server is like this for example: normaldata,items(this is an array with unknown number of elements).
so if i try to access data like viewModel.Model().Items[0]().Layer() i sometimes have Layer as a function and sometimes it is a normal element with observable elements.I want all my objects inside Items to have Layer as a function.
Server data example:
Name: "test"
Items: [Layer[ID: 132]]
In this example Name,Items and ID are observable but Layer is not.
Fiddle example:
jsfiddle.net/98dv11yz/3
So the problem is that sometimes the layer is null resulting in ko making the property observable but sometimes that property has id and ko makes only the child elements observable. The problem is that i have if's in the code and i want it to be a function so i can always reffer to it as layer() because now it is sometimes layer or layer()
An explenation for what's happening:
When the ko.mapping plugin encounters an object in your input, it will make the object's properties observable, not the property itself.
For example:
var myVM = ko.mapping.fromJS({
name: "Foo",
myObject: {
bar: "Baz"
}
});
Will boil down to:
var myVM = {
name: ko.observable("Foo"),
myObject: {
bar: ko.observable("Baz")
}
}
and not to:
var myVM = {
name: ko.observable("Foo"),
myObject: ko.observable({
bar: ko.observable("Baz")
})
}
The issue with your data structure is that myObject will sometimes be null, and sometimes be an object. The first will be treated just as the name property in this example, the latter will be treated as the myObject prop.
My suggestion:
Firstly: I'd suggest to only use the ko.mapping.fromJS method if you have a well documented and uniform data structure, and not on large data sets that have many levels and complexity. Sometimes, it's easier to create slim viewmodels that have their own mapping logic in their constructor.
If you do not wish to alter your data structure and want to keep using ko.mapping, this part will have to be changed client-side:
Items: [
{ layer: {id: "0.2"} },
{ layer: null}
]
You'll have to decide what you want to achieve. Should the viewmodel strip out the item with a null layer? Or do you want to render it and be able to update it? Here's an example of how to "correct" your data before creating a view model:
var serverData = {
Name: "Example Name",
Id: "0",
Items: [
{layer: {id: "0.2"} },
{layer: null}
]
};
var correctedData = (function() {
var copy = JSON.parse(JSON.stringify(serverData));
// If you want to be able to render the null item:
copy.Items = copy.Items.map(function(item) {
return item.layer ? item : { layer: { id: "unknown" } };
});
// If you don't want it in there:
copy.Items = copy.Items.filter(function(item) {
return item.layer;
});
return copy;
}());
Whether this solution is acceptable kind of relies on how much more complicated your real-life use will be. If there's more complexity and interactivity to the data, I'd suggest mapping the items to their own viewmodels that deal with missing properties and what not...

Accessing child objects in JavaScript

'tI am extending two JavaScript objects where one is a variable ajaxed in from a file and the other is a simple object with user preferences.
// local prefs
var prefs = { name: "Bob Barker", show: "Price is Right" };
// ajax in default prefs with dataType: "script"
var defaultPrefs = {
studio: { name: "CBS", location: "Hollywood, CA" },
producers: [ { name: "Producer1" }, { name: "Producer2" } ]
}
// merge prefs
var mergedPrefs = $.extend( prefs, defaultPrefs );
The problem is I can't access the producers or studio using prefs.producers or prefs.studio as they are Objects in the merged file. How can I get around this?
Try this:
var mergedPrefs = $.extend({}, prefs, defaultPrefs);
Extending an empty object will create a new copy of the original object(s).
You can also use Object.assign for the same task, however jQuery.extend also has a deep option to merge recursively whereas Object.assign is always flat (for better performance).
Try this code, but I recomend Object.assign in ES6 to don't use JQuery. Is the same:
var mergedPrefs = Object.assign({}, prefs, defaultPrefs);
Snippet with your code working without Object.assign (using $.extend):
// local prefs
var prefs = {
name: "Bob Barker",
show: "Price is Right"
};
// ajax in default prefs with dataType: "script"
var defaultPrefs = {
studio: {
name: "CBS",
location: "Hollywood, CA"
},
producers: [{
name: "Producer1"
}, {
name: "Producer2"
}]
}
// merge prefs
var mergedPrefs = $.extend({}, prefs, defaultPrefs);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How to Observe changes in an array of objects

Javascript now implements Object.observe and Array.observe, however I cannot seem to find a way to merge the functionality.
What I am trying to do is to have an array of objects. When any property of one of the objects changes, I would like to be notified.
Object.observe allows me to be notified when a property of a specific object changes.
Array.observe allows me to be notified when an array level change occurs.
Unless I specifically observe each element of an array, I have no way to know when a specific element property changes.
For example:
var model = [
{
data: 'Buy some Milk',
completed: false
},
{
data: 'Eat some Food',
completed: false
},
{
data: 'Sleep in a Bed',
completed: false
},
{
data: 'Make Love not War',
completed: false
}
];
Array.observe(model, function(changeRecords) {
console.log('Array observe', changeRecords);
});
model[0].data = 'Teach Me to code';
model[1].completed = true;
model.splice(1,1);
model.push({data: "It's a new one!",completed: true});
gives a console output of:
Array observe [Object, Object]
0: Object
addedCount: 0
index: 1
object: Array[4]
removed: Array[1]
type: "splice"
__proto__: Object
1: Object
addedCount: 1
index: 3
object: Array[4]
removed: Array[0]
type: "splice"
__proto__: Object
length: 2
__proto__: Array[0]
These two notifications relate specifically to:
model.splice(1,1);
and
model.push({data: "It's a new one!",completed: true});
but completely ignore
model[0].data = 'Teach Me to code';
and
model[1].completed = true;
Is there a way to observe every change, no matter whether it is an array level or object level change?
I know there are libraries that may be able to do this for me, however I would like to understand how to implement this directly in my own code.
EDIT: OK, So it seems I wasn't missing any magic functionality, and the only true option is to observe the individual elements of the array.
So to expand upon the original question, what is the most efficient method of adapting arrays to handle element observations?
Array observe observes array changes! You need to observe each object inside Array too using Object.Observe!
See this
function ObserveMyArray(myArray){
var arr=myArray||[];
for(var i=0;i<arr.length;i++){
var item=arr[i];
Object.observe(item , function(changes){
changes.forEach(function(change) {
console.log(change.type, change.name, change.oldValue);
});
});
}
Array.observe(arr, function(changeRecords) {
console.log('Array observe', changeRecords);
});
}

Merge attributes when using model.set with Backbone Associations

My problem is very specific to Backbone Associations (http://dhruvaray.github.io/backbone-associations). I'm wondering if it's possible to merge attributes when setting properties on nested models. Here is a reduction of the issue:
// define the Layout model
var Layout = Backbone.AssociatedModel.extend();
// define the User model, with layout as a Related model
var User = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'layout',
relatedModel: Layout
}
],
defaults: {
layout: {}
}
});
// create a new user
var user = new User({ user_name: 'pascalpp' });
// set a property on the layout model
user.set('layout.foo', 'bar');
user.get('layout.foo'); // returns 'bar'
// call set on the user directly, passing a JSON structure with no foo property
user.set({ layout: { 'baz': 'bing' } });
user.get('layout.foo'); // foo got wiped, so this returns undefined
The real-world scenario I'm facing is that we need to fetch partial data for a user and set that on the user model without obliterating previously set attributes that don't exist in the current fetch. So I'm hoping we can merge when setting attributes. Is this possible?
Backbone-associations updates existing nested model if id's of new and existing model match. If id's are undefined or they don't match, then the nested model gets replaced by a new one.
What I do for these singleton nested models is I introduce fake id=0 and then it works like expected.
Here is a working jsfiddle.
Working code:
// define the Layout model
var Layout = Backbone.AssociatedModel.extend({
defaults: {
id: 0
}
});
// define the User model, with layout as a Related model
var User = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'layout',
relatedModel: Layout
}
],
defaults: {
layout: {}
}
});
// create a new user
var user = new User({ user_name: 'pascalpp' });
// set a property on the layout model
user.set('layout.foo', 'bar');
user.get('layout.foo'); // returns 'bar'
// call set on the user directly, passing a JSON structure with no foo property
user.set({ layout: { id:0, 'baz': 'bing' } });
user.get('layout.foo'); // foo got wiped, so this returns undefined
alert(user.get('layout.foo'))

ExtJS creating record from existing store

I have created an ext store like so:
var store = new Ext.data.JsonStore({
root: 'vars',
fields: [{ name: 'rec_id', mapping: 'rec' }, { name: 'identity', mapping: 'id'}]
});
This works alright when I add data to the store via loadData(); and some json which looks like:
{ vars : {rec: '1', id:'John'} }
My problem is that if I use add(); to get this record into the store I have to first create it as an Ext.data.Record object.
I do this as pointed out here: https://stackoverflow.com/a/7828701/1749630 and it works ok.
The issue I have is that the records are entered with their mapped parameters rather than the ones I set. I.e, 'rec_id' becomes 'rec' and 'identity' becomes 'id'.
What am I doing wrong here?
You need to do the mapping manually, something like this:
var myNewRecord = new store.recordType({
rec_id: vars.rec,
identity: vars.id
});
store.add(myNewRecord);

Categories