I have come across a lot of examples where the backbone-view would be like var view1 = Backbone.View.extend( { } ) but unable to get one where the backbone view is returned directly. In the below code I am able to render the default values of the model attribute and display the same in the dust template but when I do model.fetch(), in the success function I am able to see the json response in the console but unable to set the fetched values to the model attributes and render the new values. Do, let me know what I am missing here. Any help is appreciated.
define(function (require) {
'use strict';
var $ = require('jquery');
var Backbone = require('backbone');
var g = require('global/dust-globals');
var template = require('text!/dust/table1.dust');
var SampleModel = Backbone.Model.extend({
initialize: function () {
},
defaults:{
SampleUpdate:'Test date',
SampleCount: 0
},
urlRoot: "/Sample"
});
var obj1 = new SampleModel();
return Backbone.View.extend({
events: {
// 'click .search-btn': 'searchBtnClick',
},
initialize: function(){
this.testfunc();
this.render();
this.model.on("change", this.render, this);
},
render: function () {
this.$el.html(g.renderTemplate('TabView', template, {}));
//template is compiled and rendered successfully
console.log('CHECK:'+obj1.get("lastUpdate"));
return this;
},
testfunc : function () {
obj1.fetch({
success: function (response) {
console.log(JSON.stringify(response));
obj1.set("SampleUpdate", response.get("sampleUpdate"));
obj1.set("SampleCount", response.get("sampleCount"));
console.log('CHECK1:'+obj1.get("SampleUpdate"));
}
});
}
});
});
My JS code calling the above code would be as below.
var TabView = require('/SampleTab');
return Backbone.View.extend({
initialize: function () {
this.tabView = new TabView({el: '#sample-div', model:this.model, appView: this});
this.render();
},
render: function() {
this.tabView.$el.show();
this.tabView.render();
}
});
I'm having trouble understanding what exactly it is you are trying to do with your code, but it doesn't look like you're using Backbone.View.extend({ ... }) correctly. From the documentation for Backbone.View.extend:
Get started with views by creating a custom view class. You'll want to override the render function, specify your declarative events, and perhaps the tagName, className, or id of the View's root element.
[Emphasis mine.]
The Backbone.View.extend is for creating your own Backbone View classes, not instantiating objects.
If you're looking for more information, I highly recommend that you read through Addy Osmani's free e-book, Developing Backbone.js Applications. You might know some of what it teaches already, but it has some good examples of extending Backbone Views and does a much better job of explaining other fundamentals of using Backbone.js than I could here.
Related
I have a Backbone SAP which has two subviews within its main App view. These are interdependent: the top one dispalys a music score rendered using Vexflow (Javascript music notation package), and the other below it displays an analysis of the score, also using Vexflow but with some extra objects (text, lines, clickable elements, etc).
The main problem I have is that a lot of the data I need for the analysis view doesn't come into existence until the score view has been rendered. For example, the x coordinate of a musical note is only available after the note has been drawn (the same isn't true of the y coordinate). Below is (in schematic terms) how my app view is set up:
var AppView = Backbone.View.extend({
//...
initialize: function() {
this.scoreView = new ScoreView();
this.analysisView = new AnalysisView({
data: this.getAnalysisData()
});
},
render: function() {
this.scoreView.render();
this.analysisView.render();
return this;
},
getAnalysisData: function() {
// Performs anaysis of this.scoreView,
// and returns result.
}
});
My work around is to move the analysis view setup into the render method, after the score view has been rendered. I dislike doing this, as the getAnalysisData method can be quite expensive, and I believe the render method should be reserved simply for rendering things, not processing.
So I'm wondering if - since there doesn't seem to be a Vexflow solution - there is a Backbone pattern that might fix this. I am familiar with the 'pub/sub' event aggregator pattern for decoupling views, as in:
this.vent = _.extend({}, Backbone.Events);
So on this pattern the analysis view render method subscribes to an event fired after the score view is rendered. I'm not sure how this would alter my code, however. Or perhaps use listenTo, like this:
// Score subview.
var ScoreView = Backbone.View.extend({
initialize: function() {
this.data = "Some data";
},
render: function() {
alert('score');
this.trigger('render');
}
});
// Analysis subview.
var AnalysisView = Backbone.View.extend({
initialize: function(options) {
this.data = options.data;
},
render: function() {
alert(this.data);
return this;
}
});
// Main view.
var AppView = Backbone.View.extend({
el: "#some-div",
initialize: function() {
this.scoreView = new ScoreView();
var view = this;
this.listenTo(this.scoreView, 'render', this.doAnalysis); // <- listen to 'render' event.
},
render: function() {
this.scoreView.render();
return this;
},
doAnalysis: function() {
this.analysisView = new AnalysisView({
data: this.getAnalysisData()
});
this.analysisView.render();
},
getAnalysisData: function() {
return this.scoreView.data;
}
});
Of course, the analysis step is still effectively being done 'during' the render process, but this seems a better pattern. It seems more like the Backbone way of doing things. Am I right? Or am I missing something?
Edit: I dont necessarily have to create the analysis view in the doAnalysis, I could still do that in the main view initialize (at the moment I'm not). But doAnalysis has to run after the score view has rendered, otherwise it cannot access the relevant score geometry information.
I'm using backbone js in my Project, I'm struck in a small confusion with views.
I'm having these 2 views. After writing them i am in a confusion whether i'm in right path or not. The reason for my doubt is that the code was looking almost the same except that the el in which the view is rendered and the template that is used in the view.
Will this type of code effect the performance?? can I optimize it ?
code:
Project.views.list = Backbone.View.extend({
// The DOM Element associated with this view
el: '.lists-section-content',
itemView: function(x){
return new Project.views.list(x);
},
// View constructor
initialize: function(payload) {
this.data = payload.data;
this.colStr = payload.colStr;
this.render();
},
events: {
},
render: function() {
sb.renderXTemplate(this);
return this;
}
});
Firstly you be better to provide el value at first element of tree initialization, otherwise all views will try to use same DOM element(s):
var myTreeRoot= new Project.views.list({
el: '.lists-section-content',
data: payload.data,
colStr: payload.colStr
});
After this you'll need to modify initialize function a little to utilize options argument of view constructor:
// View constructor
initialize: function(options) {
this.data = options.data;
this.colStr = options.colStr;
this.render();
},
And finally answering to your question, no this way it will not affect performance. You just need to track leaf views inside parent view and remove them with parent, it's needed to avoid memory leaks. Here is example of cleanup (all leaf views collected with this.subViews array and removed on parent removal):
Project.views.list = Backbone.View.extend({
// The DOM Element associated with this view
itemView: function(x){
var view = new Project.views.list(x)
this.subViews.push(view)
this.$('.item-container:first').append(view.el)
},
remove: function() {
_.invoke(this.subViews, 'remove')
return Backbone.View.prototype.remove.apply(this, arguments)
},
// View constructor
initialize: function(options) {
this.data = options.data;
this.subViews = [];
this.colStr = options.colStr;
this.render();
},
render: function() {
sb.renderXTemplate(this);
return this;
}
});
I am struggling with when to destroy backbone views. I know I need to destroy the view somewhere, but I am not sure where.
I have the following code in router.js
routes: {
"names/search": "nameSearch",
"companies/search": "companySearch"
},
initialize: function(){
Backbone.history.start();
this.navigate("#/", true);
}
nameSearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var obj = {};
obj.Status = [utils.xlate("On Assignment"), utils.xlate("Candidate")];
var view = new RecordSearchView({ model: obj, el: $(".content") }, { "modelName": "Candidate" });
view.delegateEvents();
});
},
companySearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var view = new RecordSearchView({ model: {}, el: $(".content") }, { "modelName": "Company" });
view.delegateEvents();
});
}
And then in RecordSearchView.js I have the following function that is called when a user clicks the search button
doSearch: function () {
require(["app/utils/SearchHelper", "app/models/" + modelName, "app/views/SearchResults"], function (SearchHelper, Model, SearchResultsView) {
var obj = $("#searchForm").serializeArray();
var params = SearchHelper.getQuery(obj);
params["page"] = 1;
params["resultsPerPage"] = 25;
var collection = new Model[modelName + "Collection"]({}, { searchParams: params });
params["Fields"] = collection.getSearchFields();
collection.getPage(params["page"], function (data) {
require(["app/views/SearchResults"], function (SearchResultsView) {
App.Router.navigate(modelName + "/search/results");
var view = new SearchResultsView({ collection: data, el: $(".content") });
view.delegateEvents();
});
});
return false;
});
And SearchResults.js
return BaseView.extend({
init: function () {
this.render();
},
render: function () {
var data = this.collection.convertToSearchResults();
this.$el.html(template(data));
return this;
}
});
The problem is the second time I perform any search (calling the doSearch function from RecordSearch.js). As soon as I perform the second search, the data shown is that belonging to the previous search I performed. (For example I do a name search and it works, then do a company search but the screen shows company search results but then is quickly replaced with name search results).
My questions are
I suspect I need to call some cleanup code on the view before it is re-used. Where is the proper place within a backbone application to run this.
Is there anything wrong with the way I load SearchResults view from within RecordSearch view? SearchResults does not have a path on my router, but it is basically a form post, so I assume it shouldn't?
Any help is appreciated.
This problem is quite common and is known as Zombie Views. Derick Bailey explains this issue very well here: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
However unfortunately you can't simply solve it without changing the way you are loading your views.
Because you are loading them inside RequireJS modules that will keep it in the local var scope, you are losing the reference to the views once the route has been fully processed.
In order to solve this problem, you would need to keep the reference of the current view somewhere, and then properly dispose it before calling another view, something like this:
showView: function(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.currentView.render();
$('#content').html(this.currentView.el);
}
More about this solution here: http://tiagorg.com/talk-backbone-tricks-or-treats-html5devconf/#/6
I personally suggest you adopting a solution that will take care of this for you, like Marionette.js
It will handle this and quite many other issues, by providing the missing gaps of every Backbone-based architecture.
I have a Backbone Collection that I'm trying to render in the View. The JSON data seems correct, however I can't access the values from within the view.
Here's the basic collection:
define(['backbone', 'BaseModel'], function(Backbone, BaseModel) {
var BaseCollection = Backbone.Collection.extend({
model: BaseModel,
url: "/collection/get?json=true",
initialize: function() {}
});
return BaseCollection;
});
Here's the View:
define(['backbone', 'BaseCollection'], function(Backbone, BaseCollection) {
var BaseView = Backbone.View.extend({
el: $('#baseContainer'),
template: _.template($('#baseTemplate').html()),
initialize: function() {
_.bindAll(this);
this.collection = new BaseCollection();
this.collection.bind('all', this.render, this);
this.collection.fetch();
},
render: function() {
//This returns 3 objects which is correct based on the JSON data being returned from the server
console.log(this.collection.toJSON());
var html = this.template(this.collection.toJSON());
this.$el.html(html);
return this;
},
});
return BaseView;
});
I think I need to iterate through this.render for each model within the collection. But, I'm not sure, because it shouldn't 'render' until it completes all iterations.
Any suggestions would be great! Thank you!
You need to give your template access to the models via name. When you do this:
var html = this.template(this.collection.toJSON());
You end up passing an array to the template function, which normally expects a context object (name/value pairs). Try this:
var html = this.template({collection: this.collection});
Then in your template you can iterate through them using the collection.each iterator function or any of the underscore utility methods for iteration/filtering/map/etc. I also recommend NOT using toJSON when giving your template access to the collection as it makes your data dumber and harder to work with. toJSON is best left for when you are making HTTP requests.
My backbone.js app with Handelbars does the following.
setup a model, its collection, view and router.
at the start, get a list of articles from the server and render it using the view via Handlebars.js template.
The code is below.
(function ($)
{
// model for each article
var Article = Backbone.Model.extend({});
// collection for articles
var ArticleCollection = Backbone.Collection.extend({
model: Article
});
// view for listing articles
var ArticleListView = Backbone.View.extend({
el: $('#main'),
render: function(){
var js = JSON.parse(JSON.stringify(this.model.toJSON()));
var template = Handlebars.compile($("#articles_hb").html());
$(this.el).html(template(js[0]));
return this;
}
});
// main app
var ArticleApp = Backbone.Router.extend({
_index: null,
_articles: null,
// setup routes
routes: {
"" : "index"
},
index: function() {
this._index.render();
},
initialize: function() {
var ws = this;
if( this._index == null ) {
$.get('blogs/articles', function(data) {
var rep_data = JSON.parse(data);
ws._articles = new ArticleCollection(rep_data);
ws._index = new ArticleListView({model: ws._articles});
Backbone.history.loadUrl();
});
return this;
}
return this;
}
});
articleApp = new ArticleApp();
})(jQuery);
Handlebars.js template is
<script id="articles_hb" type="text/x-handlebars-template">
{{#articles}}
{{title}}
{{/articles}}
</script>
The above code works fine and it prints article titles. However, my question is
When passing context to Handlebars.js template, I am currently doing $(this.el).html(template(js[0])). Is this the right way? When I do just "js" instead of js[0], the JSON object has leading and ending square brackets. Hence it recognizes as a array object of JSON object. So I had to js[0]. But I feel like it isn't a proper solution.
When I first create the "View", I am creating it like below.
ws._index = new ArticleListView({model: ws._articles});
But in my case, I should do
ws._index = new ArticleListView({collection: ws._articles});
Shouldn't I? (I was following a tutorial btw). Or does this matter? I tried both, and it didn't seem to make much difference.
Thanks in advance.
It seems like you are creating a view for a collection so you should initialize your view using collection instead of model.
As far as handlebars, I haven't used it a lot but I think you want to do something like this:
var ArticleListView = Backbone.View.extend({
el: $('#main'),
render: function(){
var js = this.collection.toJSON();
var template = Handlebars.compile($("#articles_hb").html());
$(this.el).html(template({articles: js}));
return this;
}
});
and then use something like this for the template
{{#each articles}}
{{this.title}}
{{/each}}
p.s. the line
JSON.parse(JSON.stringify(this.model.toJSON())) is equivalent to this.model.toJSON()
Hope this helps
var ArticleListView = Backbone.View.extend({
initialize: function(){
this.template = Handlebars.compile($('#articles_hb').html());
},
render: function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
/////////////////////////////////////
ws._index = new ArticleListView({model: ws._articles});