backbone.js (underscore template) append masonry - javascript

I would like to append new views to my existing masonry list.
Why does my view not append? Am I missing something?
Masonry.js
function:
render_masonry_append: function() {
var self = this;
_(this.collection_append.models).each(function(item) {
var view = new Home_thumbnail_view({
model: item.attributes
});
//console.log(view);
$mainListContainer.append(view.render()).masonry("appended", view.render());
});
console.log($mainListContainer);
}
Thanks!

Make sure view.render() returns this, and then change that line to:
$mainListContainer.append(view.render().el).masonry("appended", view.render().el);
I'm not sure what masonry() does, so I added view.render().el there too, but that's your call. Most likely you don't need to be render()ing the view twice, so maybe even better would be:
view.render();
$mainListContainer.append(view.el).masonry("appended", view.el);

Related

Backbone.js views will not display on page load, but works with jQuery in console

I am trying to create a Backbone.js front end for a Rails api and I am running into a problem. I have been able to pull the data from the api and put it into a single view and a collection view. The problem is that the data is definitely there and I can run a jQuery command in the console that will append it to the page correctly however, it will not display when the page loads. It seems like maybe the javascript files are not loading in the correct order so I have tried rearranging in application.js with no luck. I have researched a lot and can't find anything that relates directly to this and I am a beginner with Backbone.js, so it's possible I may be going about this the wrong way. Any help would be appreciated. Here is my code:
singleUserModel.js
singleUser = Backbone.Model.extend({
defaults: {
name: null
}
});
userCollection.js
userCollection = Backbone.Collection.extend({
model: singleUser,
url: 'http://localhost:3000/users'
});
singleUserView.js
singleUserView = Backbone.View.extend({
tagName: 'li',
template: _.template("<%= name %>"),
render: function() {
var userTemplate = this.template( this.model.toJSON());
this.$el.html(userTemplate);
return this;
}
});
userCollectionView.js
allUsersView = Backbone.View.extend({
tagName: 'ul',
initialize: function( initialUsers ) {
this.collection = new userCollection();
this.collection.fetch({reset: true});
this.render();
this.listenTo(this.collection, 'add', this.renderUser);
this.listenTo(this.collection, 'reset', this.render)
},
render: function() {
this.collection.each(function(item) {
this.renderUser(item);
}, this);
},
renderUser: function( item ) {
var user = new singleUserView({
model: item
});
this.$el.append(user.render().el);
}
});
main.js
var userGroupView = new allUsersView();
$('#allUsers').html(userGroupView.el);
Using this command in the console will display it on the page correctly.
$('#allUsers').html(userGroupView.el);
The script probably executes sooner than #allUsers exists on page. When you run it inside console, the page is already loaded and therefore this element was rendered on the page.
You should put this function call inside DOMContentLoaded, which can be registered with jquery's constructor shorthand $(function() { /* your code here */ }). This will ensure that the DOM is rendered before you try to query for the #allUsers element. Also note, if you're loading the data asynchronously and the #allUsers exists only after the data is loaded, you should be calling the function after the content is rendered. but if that's the case, you'll have to figure it on your own.
Also, an useful tip: never rely on .js files being loaded in the correct order, or you'll have a bad time.

layzr.js doesn't load the last image

I'm using layzr.js in my backbone.js project, in order to do a lazy loading image.
Here I put it in my backbone view :
render: function(){
var _item = _.template(ItemTem);
this.$el.html(_item(this.model.toJSON()));
$(document).ready(function() {
var layzr = new Layzrr({
selector: '[data-layzr]',
attr: 'data-layzr',
retinaAttr: 'data-layzr-retina',
bgAttr: 'data-layzr-bg',
threshold: 0,
callback: null
});
});
}
});
Every image works fine except the last image, it is not loaded. I have no idea what's wrong with it or did I do any thing wrong.
I attempted to find an answer to my question with the search engine but i was unable to.
First off remove $(document).ready(function() { from your view render method.
You should have DOM ready before view is rendered.
Remove that lazyloader logic altogether form render. And move it to it's parent.
You need to make sure that your view is rendered in DOM before you bind anything to it.
So I would to it this way:
var yerrView = new YourView();
$('body').append(yerrView.render().el);
var layzr = new Layzrr({
selector: '[data-layzr]',
attr: 'data-layzr',
retinaAttr: 'data-layzr-retina',
bgAttr: 'data-layzr-bg',
threshold: 0,
callback: null
});
I would write custom lazy loader for backbone if I were you.

Backbone subview not rendered properly

I started developping a website using backbone.js and after trying during the whole morning, i'm quite stuck on the following problem.
I output here only the relevant code.
I've a View called Navigator, that contains a Collection of Records (initially empty) :
var NavigatorView = Backbone.View.extend({
template: JST['app/scripts/templates/Navigator.ejs'],
tagName: 'div',
id: '',
className: 'saiNavigator',
events: {},
initialize: function () {
this.currentRecords = new RecordsCollection();
this.currentRecords.on('reset', this.onRecordsCollectionReseted.bind(this));
},
onRecordsCollectionReseted: function(){
this.render();
},
render: function () {
var tplResult = this.template({
computeTemplate: this.computeTemplate,
records: this.currentRecords
});
this.$el.html(tplResult);
},
onDOMUpdated: function(){
var me = this;
var data = {
device : 'web',
gridId : this.model.get('gridId'),
filterId : this.model.get('filterId')
};
$.ajax({
url: App.getTokenedUrl() + '/task/getGridData.'+this.model.get('taskId')+'.action',
success: me.onRecordReceived.bind(me),
statusCode: {
500: App.handleInternalError
},
type: 'GET',
crossDomain: true,
data : data,
dataType: 'json'
});
},
onRecordReceived: function(result){
var newRecords = [];
for(var i = 0; i < result.items.length; i++){
var newRecord = new RecordModel(result.items[i]);
newRecords.push(newRecord);
}
this.currentRecords.reset(newRecords);
}
});
I've a View called dossier which html is
<div id="dossier1" class="dossier">
<div id="dossier1-navContainer" class="navigatorContainer"/>
<div class="pagesNavigatorContainer"/>
<div class="pagesContainer"/>
<div class="readOnlyFiche"/>
</div>
When i first render the dossier (and i render it only once) i create the navigator in the following render function
render: function () {
this.$el.html(this.template({
uniqBaseId: this.id,
className: this.className
}));
var nav = this.navigator = new NavigatorView({
model : this.model,
id: this.id+'navigator',
el: $('#'+this.id+'-navContainer')
});
this.navigator.render();
//We notify the navigator that it's ready. This will allow the nav to load records
nav.onDOMUpdated();
}
}
As we can see, i give the '#dossier1-navContainer' id to the navigator so that he renders there
So, here is how it works. When i render the dossier, it creates a navigator and inserts it in the DOM. When done, i notify the navigator that it can load its data from the server trough ajax request. When i receive the answer i reset the collection of data with the incoming record.
Juste before the this.$el.html(tplResult) in the navigator render function i output the resulting string.
First time it's
<div class="items"></div>
Second time when i get records, it's
<div class="items">
<div>item1</div>
<div>item2</div>
<div>item3</div>
</div>
So the template generation is correct. However, when the second rendering occurs, the this.$el.html(tplResult) does NOTHING. If i look at the DOM in the browser NOTHING CHANGED
However if i replace this line by
$('#dossier1-navigator').html(tplResult)
it works. Which means that the first time, $('#dossier1-navigator') and this.$el are the same object, the second time not.
I've NO idea why it doesn't work the second time with the standard this.$el.
Help!!
Thanks in advance
Edit : after discussing a lot with Seebiscuit, i'm adding the few lines that helped answering the question
newTask.render();
var taskHtml = newTask.$el.html();
$('#mainTaskContainer').append(taskHtml);
My hunch is that your having a binding problem. I would suggest that you replace
this.currentRecords.on('reset', this.onRecordsCollectionReseted.bind(this)); },
in your initialize, with:
this.listenTo(this.currentRecords, "reset", this.render);
No need to specially bind. Backbone's listenTo bids the callback to the Backbone object that sets the listener (the this in this.listenTo). Also has the added benefit that when you close the view (by calling this.remove()) it'll remove the listener, and help you avoid zombie views.
Try it out.
I think the problem is that you are not using what your are passing to your navigatorView;
In your navigatorView try this:
initialize:function(el) {
this.$el=el
...
}
Let me know if it helps
After countless minutes of discussion with seebiscuit, we came up with the solution. The problem is all on the definition of the $el element. The formal definition defines it as
A cached jQuery object for the view's element. A handy reference instead of re-wrapping the DOM element all the time
This is actually not very exact from a standard cache point of view. From my point of view at least the principle of a cache is to look for the value if it doesn't have it, and use it otherwise. However in this case this is NOT the case. As Seebiscuit told me,
Because when you first bound this.$el = $(someelement) this.$el will always refer to the return of $(someelement) and not to $(someelement). When does the difference matter?
When the element is not in the DOM when you do the assignment
So actually, $el holds the result of the first lookup of the selector. Thus, if the first lookup misses then it won't succeed ever! Even if the element is added later.
My mistake here is to add the main dossierView into the DOM after rendering its NavigatorView subview. I could have found the solution if the $el was a real cache as the 2nd rendering in the ajax callback would have found the element. With the current way $el works i had just nothing.
Conclusion : make sure every part of your view is properly rendered in the DOM at the moment your try to render a subview.

Backbone variables versus page containers . . .?

So I have inherited a bit of backbone.js code and need to make a change to it today. The guy who wrote the original code is on vacation. I am just barely studying up on backbone.js and am pretty much a backbone newbie.
The code below works and does what it was designed for. There is only one issue: The contents of the template file (see below) get rendered into a specific HTML page.
My problem is that I don't fully understand the flow of the code to make an educated guess as far as how and where to insert a reference to an actual container on that HTML page, and get the content to display inside that container.
The class name of the container where I need the output from this function to go is .mngmnt-main-sctn. Is this possible to do?
.
window.ManagementInstancesBackupView = ManagementView.extend({
events: _.extend({
}, ManagementView.prototype.events
),
initialize: function() {
this.model = this.options.model
this.collection = this.options.collection
this.template = _.template($('#instances-management-backup-template').html())
},
render: function() {
var instances = this.collection
// Append container and title
var $el = this.$el.html(this.template({}))
instances.each(function(instance) {
// THIS IS THE CONTAINER THAT SHOULD GET STUFF APPENDED TO:
// $(".mngmnt-main-sctn")
$el.append(this.renderParent(instance));
instance.get('nic').each(function(nic) {
$el.append(this.renderChild(nic));
}, this)
}, this)
return this
},
renderParent: function(instance) {
return new ManagementInstancesBackupParentView({model: instance}).render().$el
},
renderChild: function(nic) {
return new ManagementInstancesBackupChildView({model: nic}).render().$el
}
});
I believe what you are asking is possible like this.
window.ManagementInstancesBackupView = ManagementView.extend({
el: ".mngmnt-main-sctn"
[...code excluded...]
});
We are overriding the el property meaning that when this line is called
var $el = this.$el.html(this.template({}))
this.$el will refer to the element you have specified.
Jacob, thanks again for looking into this.
I found a solution and now I'm definitely going to hit additional backbonejs tutorials. Within the code, I was able to add the selector like so:
// Append container and title
var $el = this.$el.html(this.template({})).find('.mngmnt-main-sctn')
.
I'm always perplexed by stuff like this. You can't find any answers to solve the problem, then you try a 1,000 different things . . . and then the solution seems so simple and I always feel a bit foolish after such an experience.

Code from multiple view instances causing conflicts

I'm porting a web service into a single-page webapp with Backbone. There is a basic layout consisting on a header, an empty div#content where I'm attaching the views and a footer.
Every route creates the corresponding view and attachtes it to div#content replacing the view that was rendered before with the new one.
I'm using require.js to load the backbone app and it's dependencies.
All Backbone code is pretty small, only one file as I'm only using a router and a view.
This AMD module depends on a util.js file exporting functions that are used in the views.
After a view is created and rendered, It executes the utilities (jquery stuff, ajax, etc) it needs from util.js.
The problem is that when I render a view, it's utilities get called, and when I navigate to another route, and a new view is created, the new view's utilities are called now, but the older view's utilities are still running.
At some point, I have utilities from like five views running altogether, causing conflicts sometimes.
It's clear than my approach is not good enough, as I should have a way to stop/start utilities functions as some kind of services.
I'll paste relevant code that shows my current approach:
require(["utilities"], function(util) {
...
Application.view.template = Backbone.View.extend({
el: "div#content",
initialize: function(){
this.render();
},
render: function(){
var that = this;
// ajax request to html
getTemplate(this.options.template, {
success: function(template) {
var parsedTemplate = _.template( template, that.options.templateOptions || {});
that.$el.html(parsedTemplate);
// execute corresponding utilities
if(that.options.onReady) {
that.options.onReady();
}
},
error: function(template) {
that.$el.html(template);
}
})
}
});
...
Application.router.on('route:requestPayment', function(actions) {
var params = { template: 'request-payment', onReady: util.requestPayment };
var view = new Application.view.template(params);
});
...
});
util.requestPayment consist of a function having all stuff needed to make template work.
I'm confused about how should I handle this issue. I hope I was clear, and any suggestions or help will be appreciated.
EDIT: utilities.js snippet:
...
var textareaCounter = function() {
$('#requestMessage').bind('input propertychange', function() {
var textarea_length = 40 - $(this).val().length;
if(textarea_length === 40 || textarea_length < 0) {
$('#message-counter').addClass('error').removeClass('valid');
$("#submitForm").attr('disabled', 'disabled');
}
else if(textarea_length < 40 && textarea_length > 0) {
$('#message-counter').removeClass('error');
$("#submitForm").removeAttr('disabled');
}
$('#message-counter').text(textarea_length);
});
}
...
var utilities = utilities || {};
...
utilities.requestPayment = function() {
textareaCounter();
initForm();
preventCatching();
requestPaymentCalcFallback();
};
...
return utilities;
...
I would suggest that you should store reference to the currently active view somewhere in your app.
You create a new view here :
var view = new Application.view.template(params);
but you have no access to this variable afterwards. So it exists but you can't stop/delete/get rid of it.
What we normally do is to have a Parent App class which initializes the whole app and manages everything. Your every module in requirejs would be depenedent on it. When a new route is navigating, you ask the Parent App class to change the view. It will delete the old view, create a new one, populate div#content and then store the reference of it.
I think when you delete the old view, all the utilities will stop responding to it.
If you still have the issue with events being called, then you might need to use stopListening event binders before deleting the view reference.

Categories