I'm toying around with the aura(http://github.com/addyosmani/backbone-aura) example implementing Facade and Mediator patterns in Backbone.js. I hope someone is familiar with the concept.. I'm trying to read variables (for instance i in this example in the renderComplete section of the facade.
how can i (if even possible) access the functions/variables of Appview?
The console.log(this.i); returns an undefined so i'm guessing i've lost scope somewhere
define([
'jquery',
'underscore',
'backbone',
'text!templates/master.html',
'../aura/mediator',
'../aura/facade',
'../subscriptions'
], function($, _, Backbone, masterTemplate, Mediator, Facade){
var AppView = Backbone.View.extend({
el: "body",
i : 5,
template: _.template(masterTemplate),
facade: {
routeChange: Facade.extend("masterViewChange", "routeChanged", function(route){
console.log("Change view to " + this.i);
}),
renderComplete: Facade.extend("postMasterRender", "masterRendered", function(){
console.log(this.i);
})
},
events: {},
initialize: function() {
this.render();
Mediator.publish("masterRendered", this);
},
render: function() {
$(this.el).html(this.template());
}
});
return AppView;
});
When you publish the 'masterRendered' notification, your are passing a reference to the current view as a second param (this). This second param is passed to the callback function you defined in renderComplete. So you need to write something like this:
function (obj) { console.log(obj.i); }
Instead of:
function () { console.log(this.i); }
Regards
You need to bind your facade methods to your View context by doing something like this in the initializer for the View:
initialize: function()
{
_.bindAll(this, "facade.routeChange", "facade.renderComplete");
}
Though I question whether _.bindAll can handle this case where the function you want to bind is a property of another object.
Related
I'm working on a Backbone.js app which utilizes a 'master view' which all views and subviews extend from.
Master view
define(['backbone'], function (Backbone) {
return Backbone.View.extend({
events: {
},
showSuccess: function (msg) {
alert(msg);
}
});
});
I then have a Main View which generates the page, this can call on sub views for smaller parts:
define(['backbone','masterView', 'mySubView'], function (Backbone, mView, mySubView) {
var mainView = mView.extend({
events: function () {
return _.extend({}, coreView.prototype.events, {
});
},
render: function () {
var sub = new mySubView({'foo': 'bar'});
}
});
return new mainView();
});
Finally, my subview, which when it's initialised, it says that options is undefined.
define(['backbone','masterView', 'mySubView'], function (Backbone, mView, mySubView) {
var subView = mView.extend({
events: function () {
return _.extend({}, coreView.prototype.events, {
});
},
initialize: function (options) {
console.log(options);
}
});
return new subView();
});
In this setup, why is options undefined when I passed them in my MainView? If the subview doesn't extend masterView, but Backbone.view instead, it works fine.
Your last line in the subview file:
return new subView();
You're returning a new instance instead of returning the constructor in the subview module. It should be:
return subView;
Note that as a convention, JavaScript code should use PascalCase for types (constructor, classes, etc.), and instances (variables, properties, etc.) should use camelCase.
Also, I'm sharing tricks on how to design a good base class with Backbone. You could shift the responsibility of merging the events to the base class instead of each child class.
I am writing a new backbone application, and I am getting some errors on code that I have working in other applications. I am using the newest version of backbone for this application, so I do not know if there have been changes that make what I am doing here break, or what. Google has not been any help to me all day.
I have a collection that I am breaking apart, and sending the individual models to views. The views report that this.model is undefined.
Code:
The view:
DigitalAnalytics.View.TopPage = Backbone.Model.extend({
tagName: 'tr',
className: 'db-toppage-tr',
template: DigitalAnalytics.Helper.template('db-analytics-toppages-template'),
initialize: function() {
console.log(this.model); // prints 'undefined'
},
render: function() {
console.log(this.model); // prints 'undefined'
// This throws an error saying that it can not call html() on undefined
this.$el.html(this.template( this.model.toJSON()) );
return this;
},
events: {}
});
The collection view:
DigitalAnalytics.View.Collection.TopPages = Backbone.View.extend({
el: $('#db-analytics-toppages'),
template: DigitalAnalytics.Helper.template(''),
initialize: function() {
this.listenTo(this.collection, 'reset', this.render); // Never fires even though the collection is returned
this.collection.fetch(); // Works just fine
},
render: function() {
this.collection.each( function(page, index) {
console.log(page.toJSON()); // writes the object to console just fine
var topPage = new DigitalAnalytics.View.TopPage({model: page});
this.$el.append( topPage.render().el );
}, this);
}
});
You probably have extended Backbone.Model where you should have extended Backbone.View
Change Backbone.Model.extend in your code to Backbone.View.extend, create a Backbone model using Backbone.Model then pass this model to view when you initiate it.
I am new in SPA's with backbone and I am trying to develop a small app by using backbone and requireJs.
The problem I faced is that I can't extend a view by passing a collection.
Well, this is the view with name MenuView.js
define([
'Backbone'
], function (Backbone) {
var MenuView = Backbone.View.extend({
tagName: 'ul',
render: function () {
_(this.collection).each(function (item) {
this.$el.append(new MenuListView({ model: item }).render().el);
}, this);
return this;
}
});
return new MenuView;
});
and this is the router.js in which the error is appeared
define([
'Underscore',
'Backbone',
'views/menu/menuView',
'views/createNew/createNew',
'collections/menu/menuCollection',
], function (_, Backbone, MenuView, CreateNewView,Menucollection) {
var AppRouter = Backbone.Router.extend({
routes: {
'index': 'index',
'action/:Create': 'Create'
},
index: function () {
CreateNewView.clear();
//----------- HERE IS THE PROBLEM ------------
$('#menu').html(MenuView({ collection: Menucollection.models }).render().el);
},
Create: function () {
CreateNewView.render();
}
});
var initialize = function () {
var appRouter = new AppRouter();
Backbone.history.start();
appRouter.navigate('index', { trigger: true });
};
return {
initialize: initialize
};
});
The error message is "object is not a function". I agreed with this since the MenuView is not a function. I tried to extend the MenuView (MenuView.extend({collection:Menucollection.models})) and the error message was "objet[object,object] has no method extend".
I suppose that the way I am trying to do this, is far away from the correct one.
Could anyone suggest how to do this?
Thanks
#Matti John's solution will work, but it's more of a workaround than a best practice IMHO.
As it is, you initializing your view just by requiring it, which:
Limits you to never accept arguments
Hits performance
Makes it really hard to unit-test if you relay on assigning properties ater constructing an instance.
A module should be returning a 'class' view and not an instance on that view.
In MenuView.js I would replace return new MenuView with return MenuView; and intitalzie it when required in router.js.
Your MenuView.js returns an initialized MenuView, so you could just do:
MenuView.collection = Menucollection
Note I haven't selected the models - I think it's better if you don't use the models as a replacement for your view's collection, since it would be confusing to read the code and not have a Backbone collection as the view's collection. You would also lose the method's contained within the collection (e.g. fetch/update).
If you do this, then you would need to update your loop (each is available as a method for the collection):
this.collection.each(function (item) {
this.$el.append(new MenuListView({ model: item }).render().el);
}, this);
I would like to access the app.vent from Marionette.ItemView.
Maybe an option could be to pass a parameter (app.vent) to Marionette.ItemView from Marionette.CompositeView.
Here my code:
// view/compositeView.js
define([
'marionette',
'views/item'
], function (Marionette, itemView) {
var ListView = Marionette.CompositeView.extend({
itemView: itemView
});
});
Any ideas?
P.S.:
I cannot access the app from itemView because there is a problem of circular dependency.
app -> view/compositeView -> view/itemView
v0.9 added an itemOptions attribute that can be used for this. It can either be an object literal or a function that returns an object literal.
Backbone.Marionette.CompositeView.extend({
itemView: MyItemViewType,
itemViewOptions: {
some: "option",
goes: "here"
}
});
All of the key: "value" pairs that are returned by this attribute will be supplied to the itemview's options in teh initializer
Backbone.Marionette.ItemView.extend({
initialize: function(options){
options.some; //=> "option"
options.goes; //=> "here"
}
});
Additionally, if you need to run specific code for each itemView instance that is built, you can override the buildItemView method to provide custom creation of the item view for each object in the collection.
buildItemView: function(item, ItemView){
// do custom stuff here
var view = new ItemView({
model: item,
// add your own options here
});
// more custom code working off the view instance
return view;
},
For more information, see:
the change log for v0.9
the CollectionView documentation for itemViewOptions - note that CompositeView extends from CollectionView, so all CollectionView docs are valid for CompositeView as well
the buildItemView annotated source code
Since Marionette v2.0.0, childViewOptions is used instead of itemViewOptions to pass parameters to the child view:
var MyCompositeView = Marionette.CompositeView.extend({
childView: MyChildView,
childViewOptions: function(model, index) {
return {
vent: this.options.vent
}
}
});
var MyChildView = Marionette.ItemView.extend({
initialize: function(options) {
// var events = options.vent;
}
});
new MyCompositeView({ vent: app.vent, collection: myCollection});
But to work with events, lets use Marionette.Radio instead of passing app.vent to the view.
I am trying to implement a simple app which is able to get a collection for a given object_id.
The GET response from the server looks like this:
[
{object_id: 1, text: "msg1"},
{object_id: 1, text: "msg2"},
{object_id: 1, text: "msg3"},
.......
]
My goal is:
render a collection when the user choose an object_id.
The starting point of my code is the following:
this.options = {object_id: 1};
myView = new NeView(_.extend( {el:this.$("#myView")} , this.options));
My question is:
* What is the best way:
1) to set the object_id value in the MyModel in order to
2) trigger the fetch in MyCollection and then
3) trigger the render function in myView?* or to active my goal?
P.S:
My basic code looks like this:
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (myCollection, MyModel) {
var MyView = Backbone.View.extend({
initialize: function () {
var myModel = new MyModel();
_.bindAll(this, "render");
myModel.set({
object_id: this.options.object_id
}); // here I get an error: Uncaught TypeError: Object function (){a.apply(this,arguments)} has no method 'set'
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.Collection.extend({
url: function () {
return "http://localhost/movies/" + myModel.get("object_id");
}
});
return new MyCollection
});
//MyModel
define([
], function () {
var MyModel = Backbone.Model.extend({
});
return MyModel
});
There's a few, if not fundamentally things wrong with your basic understanding of Backbone's internals.
First off, define your default model idAttribute, this is what identifies your key you lookup a model with in a collection
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
in your collection, there is no need to define your URL in the way you defined it, there are two things you need to change, first is to define the default model for your collection and second is to just stick with the base url for your collection
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
return "http://localhost/movies
}
});
return MyCollection // don't create a new collection, just return the object
});
and then your view could be something along these lines, but is certainly not limited to this way of implementing
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
},
onAddAll: function (collection, options)
{
collection.each(function (model, index) {
that.onAddOne(model, collection);
});
},
onAddOne: function (model, collection, options)
{
// render out an individual model here, either using another Backbone view or plain text
this.$el.append('<li>' + model.get('text') + '</li>');
}
});
return MyView;
});
Take it easy and go step by step
I would strongly recommend taking a closer look at the exhaustive list of tutorials on the Backbone.js github wiki: https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites ... try to understand the basics of Backbone before adding the additional complexity of require.js