el is not populated correctly in short example - javascript

According to backbonejs.org
All views have the el property at all times. Also, to link a view to an already existing element id, you should pass it in when instantiating the view.
In this small example below, I pass in the already existing id in as a string. When I log it to the console later it is logged incorrectly as an empty div. Was I suppose to pass in the actual element and not a string that specified the element id?
Either, way I find it strange that logging the id, logs an actual empty div with that id.
/***************************************************************************************************
*/
var ModelMediaPane = Backbone.Model.extend({
defaults: {
visible: false,
}
});
var model_media_pane = new ModelMediaPane();
var ViewMediaPane = Backbone.View.extend({
});
var view_media_pane = new ViewMediaPane({
model: model_media_pane,
id: 'mi_holder'
});
console.log(model_media_pane.get('visible'));
console.log(view_media_pane.id);

You don't pass an existing id like that, you do it like this:
var view_media_pane = new ViewMediaPane({
model: model_media_pane,
el: '#mi_holder'
});
Otherwise you're just setting the id to the created element.

Related

Backbone model property getting updated on update of another property

Here is my model:
var SendAlertsModel = Backbone.Model.extend({
defaults: {
customSubject: "",
customNote: "",
userList:[],
alertUserList:[]
}
});
Inside view:
initialize: function(options) {
var self= this;
if(_.isUndefined(options)===false){
self.model= new SendAlertsModel();
self.loggedInUser = app.user;
self.model.set("userList",options.previousTabData.get("userList"));
self.model.set("alertUserList",options.previousTabData.get("userList"));
self.model.get("alertUserList").push(self.loggedInUser);
}
},
The issue which i am facing here is when i push the loggedInUser to alertUserList array, it automatically pushes the same loggedInUser to userList array.
Please give your suggestions on this.
Thanks
//create new alerts model
model = new SendAlertsModel();
//assign logged in user to local variable
loggedInUser = app.user;
//set the user list to be the user list from the previous tab data
model.set("userList",options.previousTabData.get("userList"));
//set the alertUserList to be the previous tab user list by reference
model.set("alertUserList",options.previousTabData.get("userList"));
//push the logged in user into the alert user list
model.get("alertUserList").push(self.loggedInUser);
I think the issue occurs when you set the alertUserList to be the userList. As the user list is an object the alertUserList now contains a reference to the userList. It's not a copy. When you update the alertUserList you are actually updating the userList too.
Think of it like this:
var alertUserList = userList = {some object in memory};
In this line here you will want to create a copy rather:
model.set("alertUserList",options.previousTabData.get("userList"));
I'm not sure of what data type userList is, so it will depend on that. If you only need a shallow copy then you could do this using your underscore/lodash library (I assume that is what the "_" is):
model.set("alertUserList",_.clone(options.previousTabData.get("userList")));

Ember not updating template when I add or remove an object from an array field in the model

So I've got a model that has a field of an array objects it looks like this
App.Post = DS.Model.extend({
...
codes: attr(),
...
});
and Codes looks like this
codes: [
{
code: stuff
comment: stuff_1
other_things: other_stuff
},
{
...
},
{
...
}
...
]
So now I have an add / remove button which has actions attached to them and this is what they do
add_code_input: function() {
var codes = this.get('model.codes');
var self = this;
var last_code = codes[codes.length-1];
// Cannot edit as an ember.set error is occurring
last_code.code = 'Add new (please change)';
last_code.code_type = "";
last_code.comment = "";
console.log(last_code);
codes.push(last_code);
this.set('model.codes', codes);
console.log(codes);
},
remove_code_input: function() {
var codes = this.get('model.codes');
codes.pop();
console.log(codes);
this.set('model.codes', codes);
}
So the remove works fine but the add doesn't work.
It gives me this error when I try to update last_code: Uncaught Error: Assertion Failed: You must use Ember.set() to access this property (of [object Object])
I essentially want to add a dummy object that user can change.
So first issue is figuring out how to add dummy objects into the array properly and secondly how to update the template as the model changes.
You should be using arr.pushObject(obj) and arr.popObject() for manipulating an array in Ember (think of it as the setter/getter of arrays).
is codes really just attr() because it appears to be behaving like a DS record.
If it is a record, you just use record.set('foo', 'bar') if it's just a POJO you can use Ember.set(obj, 'foo', 'bar').
It should be as easy as this (I'm assuming you're using and in the ObjectController here)
var newCode = {
code:'foo'
};
this.get('codes').pushObject(newCode);

Retrieve Backbone Collection from Model and keep it as a Collection

I'm currently fooling around with backbone.js and came across some wierd behaviour trying to create some relationships between models and collections.
So here is my Model/Collection
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
initialize: function() {
this.elements = new Elements(this.get('elements'));
}
});
When I retrieve the elements via application.get('elements') I get a 'false' asking if this new Object is an instanceof Backbone.Collection.
var gotElements = application.get('elements');
var isCollection = gotElements instanceof Backbone.Collection;
So am I doing something wrong, or do I have to create a new Collection-Instance and fill it up with the Collection I receive from the application?
In your initialize function doing this.elements sets a property called 'elements' directly on your model (so model.elements will be your Backbone collection). The problem is when you try to retrieve this.elements by calling application.get('elements'), you will see it returns undefined (which is why it is not an instance of a Backbone collection).
In order for a model attribute to be retrievable using model.get it needs be set with model.set(attribute). If you examine the model in the console you will see the difference. model.set(myData) adds your data to the model's attributes hash. model.myData = myData adds a 'myData' property directly to the model.
To get this to work you can do the following:
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
elements: null
});
var application = new Application({
elements: new Elements()
});
var myElements = application.get('elements');
myElements should now be an empty Backbone Collection
Instead of putting your collection into another model, you should put it in the according view:
var ListView = new Backbone.View.extend({
initialize:function(){
this.Elements= new Elements();
// more stuff to go
},
render:function(){
_.forEach(this.Elements, function(){
//Do rendering stuff of indivdual elements and get HTML
});
//put HTML in the DOM
}
});
There is no need of a model containing a collection containing several models.
Edit: And instead of putting your app into a Model, you should put it into a view.

ember.js .find() only works when called 2nd time

Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
What triggers the comparison:
I have a button inside a template with {{ action "isResponse"}}. This template's controller has an isResponse : function() {...}
The problem I have: The action is fired every time I click the button, but App.Answer.find() only returns content after the 2nd click. I'm wondering if this is because the Answer model hasn't loaded, but am unsure how to properly set up an observer for isLoaded in my example (if that is even the issue)
So how come App.Answer.find() returns empty the first time it's called??
App.ChoiceController = Ember.ObjectController.extend({
chosen: false,
isResponse: function() {
// successfully returns what I want from this controller's model
var questionId = this.get('question.id')
// gets DS.RecordArray of the model i'd like to compare with
var answers = App.Answer.find()
// filter to get a result that matches this.get('question.id')
var answer = answers.filter(function(ans) {
// returns all entries that match
if(ans.get('question.id') == questionId) { return true }
}, 'answers.isLoaded'); // this observer doesn't seem to hurt or help
// get the final value I need
var choice = answer.mapProperty('choice.id')
// if choice array is not empty, (should only have 1 element anyways)
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
})
Here are the models involved. Both include DS.belongsTo attributes
App.Choice = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
})
App.Answer = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
"choice" : DS.belongsTo('App.Choice')
})
App.Question = DS.Model.extend({
})
EDIT
Here is jsfiddle showing the behavior. Make sure to open your browser console to notice that each button requires 2 clicks for action isResponse to function properly. http://jsfiddle.net/iceking1624/QMBwe/
After reading your comment I've retought a solution to your problem and one possible way might be that you can define a AnswerController of type ArrayController (since it's for a collection of answers) and then setup this controller in your ApplicationRoute's setupController hook.
Main goal: Using .find() to access a model other than the one available in the current controller -in order to compare data from the current controller's model with a piece of data from a 'foreign' controller's model.
Later on you can then require access to the AnswerController's data using the needs API with needs:['answers'] from inside whatever controller that needs access to the answers collection, and finally have access to the data with this.get('controllers.answer'). You can find here more info on the needs API.
See here a possible solution that works correctly, displaying the right choice already on the 1st click:
App.AnswerController = Ember.ArrayController.extend({});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('answer').set('content', App.Answer.find());
}
});
App.ChoiceController = Ember.ObjectController.extend({
needs: ['answer'],
chosen: false,
isResponse: function() {
var questionId = this.get('question.id');
var answers = this.get('controllers.answer');
var answer = answers.content.filter(function(ans) {
if(ans.get('question.id') == questionId) { return true }
}
var choice = answer.mapProperty('choice.id');
if(!choice) {
this.set('chosen', choice[0]);
} else {
this.set('chosen', false);
}
}
});
And here a working fiddle.
Hope it helps.

backbone views not working with fetched json

I'm having issues syncing JSON data received from the server with my views after a fetch.
I do not have a collection of "mainmodel", because I'm only working with one "mainmodel" at a time but numerous "mymodel", anyhow, the structure follows:
var mymodel = Backbone.Model.extend({
defaults: {k1:"",
k2:"",
k3:""}
});
var collection = Backbone.Collection.extend({model:mymodel,});
var mainmodel = Backbone.Model.extend({
defaults: {v1:"",
v2:"",
v3:"",
v4:new collection()
});
I create the nested views for "mymodel" from a render function in a parent view. This works..., only when I'm working with a new model.
// My ParentView render function
render: function() {
for (var i = 0; i < this.model.v4.length;i++) {
var view = new MyModelView({model:this.model.v4.at(i)});
this.$el.append($(view.render().el));
}
this.$el.append(this.template(this.model.toJSON()));
return this;
},
// The MyModelView render function below
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
Now, the above works if I open my application and create models from there. However, If I open my app and supply an id, I make a fetch to the server, retrieve the data, and create a new ParentView I end up getting an error that says "this.model.v4.at not a function". Ugh.
So now, if I change the FIRST render function to be, changing the at(i) to [i]
var view = new MyModelView({model:this.model.v4[i]});
And change the second render function, removing toJSON, to be:
this.$el.html(this.template(this.model));
It renders. But I still can't move around views without errors. No surprise.
I have used console.log(JSON.stringify(this.model)); as they arrive into the parentView and MyModelView. The JSON returned looks like this, whether fetched or created.
{"v1":"val1",
"v2":"val2,
"v3":"val3",
"v4":[{"k1":"key1","k2":"key2","k3","key"}, { ... }, { ... }]
}
The JSON data structures appear to be identical. I thought the JSON format was incorrect, so I tried using JSON.parse before handing the model to the view, but that didn't work. Maybe I'm way off, but I originally thought I had a JSON formatting issue, but now I don't know. The server is returning content as 'application/json'.
Edit: The JSON values for v1,v2,v3 render correctly.
Any ideas?
You have two problems: one you know about and one you don't.
The problem you know about is that your mainmodel won't automatically convert your v4 JSON to a collection so you end up with an array where you're expecting a collection. You can fix this by adding a parse to your mainmodel:
parse: function(response) {
if(response.v4)
response.v4 = new collection(response.v4);
return response;
}
The problem you don't know about is that your defaults in mainmodel has a hidden reference sharing problem:
var mainmodel = Backbone.Model.extend({
defaults: {
//...
v4: new collection()
}
});
Anything you define in the Backbone.Model.extend object ends up on your model's prototype so the entire defaults object is shared by all instances of your model. Also, Backbone will do a shallow copy of defaults into your new models. So if you m1 = new mainmodel() and m2 = new mainmodel(), then m1 and m2 will have exactly the same v4 attribute. You can solve this by using a function for defaults:
var mainmodel = Backbone.Model.extend({
defaults: function() {
return {
v1: '',
v2: '',
v3: '',
v4: new collection()
};
}
});

Categories