I understand that using pluck method we can get an array of attributes of each model inside a backbone collection
var idsInCollection = collection.pluck('id'); // outputs ["id1","id2"...]
I want to if there is a method that sets up an attribute to each model in the collection,
var urlArray = ["https://url1", "https://url1" ...];
collection.WHAT_IS_THIS_METHOD({"urls": urlArray});
There's not exactly a pre-existing method, but invoke let's you do something similar in a single line:
collection.invoke('set', {"urls": urlArray});
If you wanted to make a re-usable set method on all of your collections, you could do the following:
var YourCollection = Backbone.Collection.extend({
set: function(attributes) {
this.invoke('set', attributes);
// NOTE: This would need to get a little more complex to support the
// set(key, value) syntax
}
});
* EDIT *
Backbone has since added its own set method, and if you overwrite it you'll completely break your Collection. Therefore the above example should really be renamed to setModelAttributes, or anything else which isn't set.
I don’t there is a method for it, but you can try:
collection.forEach(function(model, index) {
model.set(url, urlArray[index]);
});
Expanding on David's answer, you can easily put this functionality into a custom method on the collection. Here's my way of doing it using coffeescript:
class Collection extends Backbone.Collection
setAll: () ->
_args = arguments
#models.forEach (model) -> model.set _args...
class SomeCollection extends Collection
url: '/some-endpoint.json'
myLovelyCollection = new SomeCollection()
myLovelyCollection.fetch
success: (collection, response) ->
collection.setAll someVar: true
collection.setAll anotherVar, 'test'
If you wanted to do it in vanilla JS it's exactly the same but not harnessing the power of classes or splats. So more like:
var Collection = Backbone.Collection.extend({
setAll: function () {
var _args = arguments;
this.models.forEach(function (model) {
model.set.apply(model, _args);
});
}
});
Just thought I'd post my slightly updated method based on machineghost's version. This uses lodash invokeMap method instead of underscore's invoke. It supports the same optional syntax as the standard model.set method ... e.g. ('prop', 'val') or ({prop: 'val', prop: 'val'}) as well accepting and passing an options object.
var YourCollection = Backbone.Collection.extend({
setModels: function(key, val, options) {
var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if (attrs) {
_.invokeMap(this, 'set', attrs, options);
}
return this;
}
});
If you are using invoke the syntax according to the underscore site should be
_.invoke(list, methodName, *arguments) http://underscorejs.org/#invoke
So the above function mentioned by machineghost should be
collection.invoke({'url': someURL},'set');
Hope that helps :)
Related
I have a model, which has a backbone collection as one of its attributes.
If I output the model, I can see the collection and all looks ok.
If I output the result of the collection toJSON(), it apparently outputs the whole of the collection object as json.
So the following two lines:
console.log('about to sync', model);
console.log('files', model.attributes.files.toJSON());
Gives the following output:
As you can see, the collection is present and correct in the model, but the toJSON call returns all of the functions in the object as well as the models and so on, instead of "an array containing the attributes hash of each model in the collection"
Backbone doesn't handle sub models/collections by default, you have to plug in your desired behavior. In your case, you just have to override toJSON on your model to replace the collection by its array representation.
Something like
var M = Backbone.Model.extend({
toJSON: function() {
var json = Backbone.Model.prototype.toJSON.call(this);
json.files = this.get('files').toJSON();
return json;
}
});
And a demo http://jsfiddle.net/nikoshr/1jk8gLz4/
Or if you prefer a more general change, alter Backbone.Model.prototype.toJSON to include this behaviour for all models. For example
(function () {
var originalMethod = Backbone.Model.prototype.toJSON;
Backbone.Model.prototype.toJSON = function(options) {
var json = originalMethod.call(this, options);
_.each(json, function(value, key) {
if ((value instanceof Backbone.Collection) ||
(value instanceof Backbone.Model))
json[key] = value.toJSON();
});
return json;
};
})();
http://jsfiddle.net/nikoshr/1jk8gLz4/2/
Tom, You will need to loop through each element in the object to see the contents after you have done toJSON(). I have also faced same problem. See how to parse the JSON object. While printing there would be any issues.
Hope this helps!!
Thanks
override toJSON method.
var yourModel = Backbone.Model.extend({
toJSON: function(){
var base = Backbone.Model.prototype.toJSON.call(this);
for (key in base) {
item = base[key];
if (item.toJSON != null) {
base[key] = item.toJSON();
}
}
return base;
}
});
Are there any possibilities of extending the knockout mapping plugin to handle the mapping of different types when unmapping?
I have this model:
var Model = function () {
var self = this;
self.requestFrom = ko.observable(moment().subtract("days", 1));
self.requestTo = ko.observable(moment());
// additional properties.
};
And to get a plain JavaScript object out of that, I'm doing:
var model = new Model();
var obj = mapping.toJS(model, {
"ignore": ["requestFrom", "requestTo"]
});
obj.requestFrom = model().requestFrom().toISOString();
obj.requestTo = model().requestTo().toISOString();
I would like to avoid handling the translation of moment objects manually, and instead write some extension to knockout mapping that knows how to handle objects of type moment, and return them as their ISO string representation.
Any ideas?
Usually toJSON() is called automatically on your object when you pass it as an Ajax parameter.
It may not be directly the answer that you're looking for, but adding a custom toJSON() on your Models works pretty good I think. You have very precise control on what part of the data is send and in what format.
var Model = function () {
var self = this;
self.requestFrom = ko.observable(moment().subtract("days", 1));
self.requestTo = ko.observable(moment());
// additional properties.
self.toJSON = function()
{
return {
requestFrom: self.requestFrom(),
requestTo: self.RequestTo(),
otherProperty: self.otherProperty()
}
}
};
You can than use it directly on your Ajax requests, without converting it manually.
var model = new Model();
$.ajax({
url: '/test/PersonSubmit',
type: 'post',
dataType: 'json',
data: model
});
Note that the default toJSON() implementation of moment() is already toISOString(). See the source code of moment.js:
// add aliased format methods
moment.fn.toJSON = moment.fn.toISOString;
have you thought about using prototype to extend the ko object? I would clone the observable method into a new momentObservable method that could then be called like so:
var Model = function () {
var self = this;
self.requestFrom = ko.momentObservable.subtract("days", 1));
//other operations
};
I could not find any way to accomplish the task of such conversion as I could not find any means of getting Ember.js properties for the object. Ember.keys returns only the properties I set in create or with get and the properties declared in Ember.extend do not show up there. I use such properties to set up default values (e.g. [] for array properties)
Here is my dirty workaround
var newModel = JSON.parse(JSON.stringify(model));
I would do something similar to the person above, but I'd do it a little bit differently.
Mixin
App.NativeObject = Ember.Mixin.create({
toNative: function() {
var properties = [];
for (var key in this) {
if (jQuery.inArray(Ember.typeOf(object[key]), ['string', 'number', 'boolean']) !== -1) {
properties.push(key);
}
}
return this.getProperties(properties);
}
});
Object
Then you just need to implement the App.NativeObject mixin in your objects that you would like the toNative on:
var Object = Ember.Object.extend(App.NativeObject, {
name: 'Adam',
count: 4
});
We then have the toNative method on all the objects that implement our mixin.
Obligatory jsFiddle: http://jsfiddle.net/jumUx/
If your object is a subclass of ember-data model notice you can use the toJSON method otherwise you can use:
JSON.parse(JSON.stringify(emberObj))
To grab any values which support native json serialization (i.e. not functions/methods)
This worked for me:
myModel.toJSON({includeId: true})
I'm using Ember 3.
This is what I did and it works quite well. Note, this should be ready only, as any changes to an object or array in the copied object will affect the original object
App.BaseValidations = Ember.Object.create({
toObject: function() {
var destination = {}
for (var k in this) {
if (this.hasOwnProperty(k) && typeof(this[k]) !== 'function') {
destination[k] = this[k];
}
}
return destination;
}
})
something quite simple that worked properly enough for me is :
Ember.Object.reopen({
toJson: function() {
return JSON.parse(JSON.stringify(this));
}
});
at app loading time.
At the moment I solve it with the following snippet:
App.plainCopy = function (obj) {
if (Ember.isArray(obj)) {
return obj.map(App.plainCopy);
} else if (typeof(obj) === "object") {
if (App.Plainable.detect(obj)) {
return obj.plainCopy();
} else {
throw new Error(Ember.String.fmt("%# is not Plainable", [obj]));
}
} else {
return obj;
}
}
App.Plainable = Ember.Mixin.create({
plainCopy: function() {
var props = Ember.keys(this);
var proto = this.constructor.prototype;
for(p in proto) {
if (proto.hasOwnProperty(p) && typeof(this[p])!=="function") {
props.push(p);
}
}
var copy = {};
props.forEach(function(p) {
copy[p] = App.plainCopy(this.get(p));
}, this);
return copy;
}
});
It does not go up the class hierarchy and does not look into mixins (as I use for data objects which are quite simple form that point of view)
With modern (3.17) ember, I've used myEmberObject.getProperties('id', 'name', 'foo', 'bar')
It produces a plain object.
Another possible solution that may suit your needs while not being fully recursive for nested Ember objects:
// where myEmberObject is.. an ember object
var plainJavaScriptObject = myEmberObject.toJSON();
This will only include actual properties that you've defined and no Ember internals. Again, the drawback here is that any nested Ember objects will not, themselves, be converted but will appear as Strings in style of "".
I have tried the following to set an id to my model:
var globalCounter = 1;
var Model = Backbone.Model.extend({
initialize: function () {
this.id = globalCounter;
globalCounter += 1;
}
});
myModel = new Model();
console.log(myMode.get('id')); // prints undefined
How can I set an id to my models?
You need to use the set() function instead (http://jsbin.com/agosub/1/);
var globalCounter = 1;
var Model = Backbone.Model.extend({
initialize: function () {
this.set('id', globalCounter);
globalCounter += 1;
}
});
myModel = new Model();
console.log(myModel.get('id')); // prints 1
You must use :
this.set('id', globalCounter);
instead of this.id = globalCounter;
You are adding the id value to the Model object, but you want to add it to Model.attributes object. And that what is doing Model.set() method.
model.set("key", value) will put the value in model.attributes.key;
model.get("key") will return the value inside model.attributes.key
This is a little weird for new comers to Backbone, but it's a major (and easy) point to get. It's designed so that using model.set(...) will fire change events you can easily catch to update your views.
Backbone and ES6 Update :
The Backbone attribute object is outdates by ES6 getters and setters. Theses functions can overwrite the standard access.
Warning : this is pseudo-code that may be one day used with ES6 !
class MyModel extends Backbone.Model{
get id(){ return this.attributes.id; }
set id(id){ this.attributes.id = id; }
}
This would allow to write :
let myModel = new Model();
myModel.id = 13; // will use myModel.id(13)
console.log (myModel.id); // will show myModel.id()
As of today, this is only a dream of a Backbone 2. After basic searches, I've seen nothing about that coming.
I have a simple question about Backbone.js' get and set functions.
1) With the code below, how can I 'get' or 'set' obj1.myAttribute1 directly?
Another question:
2) In the Model, aside from the defaults object, where can/should I declare my model's other attributes, such that they can be accessed via Backbone's get and set methods?
var MyModel = Backbone.Model.extend({
defaults: {
obj1 : {
"myAttribute1" : false,
"myAttribute2" : true,
}
}
})
var MyView = Backbone.View.extend({
myFunc: function(){
console.log(this.model.get("obj1"));
//returns the obj1 object
//but how do I get obj1.myAttribute1 directly so that it returns false?
}
});
I know I can do:
this.model.get("obj1").myAttribute1;
but is that good practice?
While this.model.get("obj1").myAttribute1 is fine, it's a bit problematic because then you might be tempted to do the same type of thing for set, i.e.
this.model.get("obj1").myAttribute1 = true;
But if you do this, you won't get the benefits of Backbone models for myAttribute1, like change events or validation.
A better solution would be to never nest POJSOs ("plain old JavaScript objects") in your models, and instead nest custom model classes. So it would look something like this:
var Obj = Backbone.Model.extend({
defaults: {
myAttribute1: false,
myAttribute2: true
}
});
var MyModel = Backbone.Model.extend({
initialize: function () {
this.set("obj1", new Obj());
}
});
Then the accessing code would be
var x = this.model.get("obj1").get("myAttribute1");
but more importantly the setting code would be
this.model.get("obj1").set({ myAttribute1: true });
which will fire appropriate change events and the like. Working example here: http://jsfiddle.net/g3U7j/
I created backbone-deep-model for this - just extend Backbone.DeepModel instead of Backbone.Model and you can then use paths to get/set nested model attributes. It maintains change events too.
model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
Domenic's solution will work however each new MyModel will point to the same instance of Obj.
To avoid this, MyModel should look like:
var MyModel = Backbone.Model.extend({
initialize: function() {
myDefaults = {
obj1: new Obj()
}
this.set(myDefaults);
}
});
See c3rin's answer # https://stackoverflow.com/a/6364480/1072653 for a full explanation.
I use this approach.
If you have a Backbone model like this:
var nestedAttrModel = new Backbone.Model({
a: {b: 1, c: 2}
});
You can set the attribute "a.b" with:
var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);
Now your model will have attributes like:
{a: {b: 3, c: 2}}
with the "change" event fired.
There is one solution nobody thought of yet which is lots to use. You indeed can't set nested attributes directly, unless you use a third party library which you probably don't want. However what you can do is make a clone of the original dictionary, set the nested property there and than set that whole dictionary. Piece of cake.
//How model.obj1 looks like
obj1: {
myAttribute1: false,
myAttribute2: true,
anotherNestedDict: {
myAttribute3: false
}
}
//Make a clone of it
var cloneOfObject1 = JSON.parse(JSON.stringify(this.model.get('obj1')));
//Let's day we want to change myAttribute1 to false and myAttribute3 to true
cloneOfObject1.myAttribute2 = false;
cloneOfObject1.anotherNestedDict.myAttribute3 = true;
//And now we set the whole dictionary
this.model.set('obj1', cloneOfObject1);
//Job done, happy birthday
I had the same problem #pagewil and #Benno had with #Domenic's solution. My answer was to instead write a simple sub-class of Backbone.Model that fixes the problem.
// Special model implementation that allows you to easily nest Backbone models as properties.
Backbone.NestedModel = Backbone.Model.extend({
// Define Backbone models that are present in properties
// Expected Format:
// [{key: 'courses', model: Course}]
models: [],
set: function(key, value, options) {
var attrs, attr, val;
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
_.each(this.models, function(item){
if (_.isObject(attrs[item.key])) {
attrs[item.key] = new item.model(attrs[item.key]);
}
},this);
return Backbone.Model.prototype.set.call(this, attrs, options);
}
});
var Obj = Backbone.Model.extend({
defaults: {
myAttribute1: false,
myAttribute2: true
}
});
var MyModel = Backbone.NestedModel.extend({
defaults: {
obj1: new Obj()
},
models: [{key: 'obj1', model: Obj}]
});
What NestedModel does for you is allow these to work (which is what happens when myModel gets set via JSON data):
var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });
It would be easy to generate the models list automatically in initialize, but this solution was good enough for me.
Solution proposed by Domenic has some drawbacks. Say you want to listen to 'change' event. In that case 'initialize' method will not be fired and your custom value for attribute will be replaced with json object from server. In my project I faced with this problem. My solution to override 'set' method of Model:
set: function(key, val, options) {
if (typeof key === 'object') {
var attrs = key;
attrs.content = new module.BaseItem(attrs.content || {});
attrs.children = new module.MenuItems(attrs.children || []);
}
return Backbone.Model.prototype.set.call(this, key, val, options);
},
While in some cases using Backbone models instead of nested Object attributes makes sense as Domenic mentioned, in simpler cases you could create a setter function in the model:
var MyModel = Backbone.Model.extend({
defaults: {
obj1 : {
"myAttribute1" : false,
"myAttribute2" : true,
}
},
setObj1Attribute: function(name, value) {
var obj1 = this.get('obj1');
obj1[name] = value;
this.set('obj1', obj1);
}
})
If you interact with backend, which requires object with nesting structure.
But with backbone more easy to work with linear structure.
backbone.linear can help you.