I am developing my backbone application using require.js. all were fine. up to I integrate the routers. after I integrate my router I'm getting errors saying:
TypeError: appView is undefined
[Break On This Error]
that.$el.append(new appView.getView({model:data}).render());
and in the view.js I'm unable to route using this line(i intentionally commented)
listTrigger:function(){
myApp.navigate("/student");
}
and i post my all codes here... anyone can suggest or correct my code. or give me the reasons what i am doing wrong here?
main.js
require.config({
baseUrl: 'js/',
paths:{
'jquery' :"lib/jquery.min",
'underscore' :"lib/underscore-min",
'backbone' :"lib/backbone-min",
'appModel' :"app/model/model",
'appView' :"app/views/view",
'appViews' :"app/views/views",
'appRoute' :"app/router/router"
},
shim:{
underscore:{
exports:"_"
},
backbone:{
exports:"Backbone",
deps:["jquery","underscore"]
}
}
});
require(["appRoute"], function(appRoute) {
var myApp = new appRoute.getRoute();
Backbone.history.start();
});
model.js
define("appModel", ['backbone'], function(Backbone){
"use strict"
var appModel = Backbone.Model.extend({});
var appCollection = Backbone.Collection.extend({
model:appModel,
initialize:function(){
// console.log("initialized from collection");
}
});
return {
model: appModel,
collect:appCollection
}
});
view.js
define("appView", ["backbone","appViews"], function(Backbone,appViews){
"use strict"
var appView = Backbone.View.extend({
tagName:"li",
template:_.template($("#listTemp").html()),
events:{
"click" : "listTrigger"
},
initialize:function(){
this.render();
},
render:function(){
return this.$el.html(this.template(this.model.toJSON()));
},
listTrigger:function(){
// myApp.navigate("/student");
}
});
return{
getView: appView
}
})
views.js with some json data:
define('appViews', ["backbone","appModel","appView"], function(Backbone,appModel,appView){
"use strict";
var students = [
{"name":"student1"},{"name":"student2"},
{"name":"student3"},{"name":"student4"},
{"name":"student5"},{"name":"student6"},
{"name":"student6"},{"name":"student8"},
{"name":"student9"},{"name":"student0"}]
var appViews = Backbone.View.extend({
el:$("#app").find('ul'),
initialize:function(){
this.collection = new appModel.collect(students);
this.collection.on('reset', this.renderAll);
this.renderAll();
},
render:function(){
console.log("render called from views");
},
renderOne:function(){
console.log("render one")
},
renderAll:function(){
var that = this;
this.collection.each(function(data,i){
that.$el.append(new appView.getView({model:data}).render());
})
}
});
return {
appViews : appViews
}
})
router.js
define('appRoute', ["backbone","appModel","appView","appViews"], function(Backbone,appModel,appView,appViews){
var appRouter = Backbone.Router.extend({
routes:{
"":"initiate"
},
initialize:function(){
// console.log("called from routersssss");
},
initiate:function(){
new appViews.appViews();
}
})
return {
getRoute : appRouter
}
})
across this all are working correct up to using routers. after I'm not getting the result. Am I using routers incorrectly?
#TomasKirda: you can new-up without the trailing parentheses, but I wouldn't recommend it! Also, see here.
Having said that, you have identified the problem in this instance!
This code:
new appView.getView(...);
Is trying to create a new appView.getView which isn't a reference to a constructor (it would be if the appView constructor had a property getView).
So in this case, you are quite right that the parentheses are required:
new appView().getView(...);
Related
I have a backbonejs application that contains a router file and some views , and also i'm using requirejs to add views to routes and add templates to views. here is my codes :
routes.js
var AppRouter = Backbone.Router.extend({
routes: {
"": "getLogin",
"login": "getLogin",
"register": "getRegister",
"forget-password": "getForgetPassword"
},
getLogin: function() {
require(['views/auth/loginView'], function(view) {
view = new this.LoginView();
});
},
getRegister: function() {
require(['views/auth/registerView'], function() {
view = new this.RegisterView();
});
},
getForgetPassword: function() {
require(['views/auth/forgetPasswordView'], function() {
view = new this.ForgetPasswordView();
});
},
});
var route = new AppRouter();
Backbone.history.start();
loginView.js
var LoginView = Backbone.View.extend({
el: '#wrapper',
initialize: function() {
NProgress.start();
this.render();
},
render: function() {
require(['text!partials/auth/login.html'], function(t) {
var json = { title: 'title', formName: 'frmLogin' };
var template = _.template(t);
$('#wrapper').html(template(json));
});
NProgress.done();
},
events: {
"click #btnLogin": "login"
},
login: function(e) {
e.preventDefault();
alert('some message');
}
});
also registerView.js and forgetPasswordView.js are similar to loginView.js.
now! when i change routes multiple times and hit #btnLogn it fires alert('some message'); function multiple times...!
Have you tried un-delegating the events in the view, on route change?
You could override the route method (annotated source) in your AppRouter and run it before each route is rendered.
route: function(route, name, callback) {
view.undelegateEvents();
return Backbone.Router.prototype.route.apply(this, arguments);
}
Note: Just an idea, not tested with your code
Expanding my original question located here
If my Userview is in Userview.js file and I want to inherit from that class in AdminView.js file, how would I go about it.
I tried this, but would not fit my need as I don't have a class.
UPDATE 1:
define([
'modules/userdetail'
],
function(UserView) {
var adminView
adminView.Views.Content = UserView.Views.Content.extend({
initialize: function() {
//looking to override the fn that is declared in UserView
console.log("AAA");
},
});
}
UPDATE 2:
So digging deep, the User Detail is
define(
[ 'modules/baseClass'],
function(BaseClass) {
// Create a new module
//Create Model
//Create View
UserDetails.Views.Content = Backbone.View
.extend({
template :
initialize : function() {
this.model = new UserDetails.Model();
},
events : {
},
render : function(LayOut) {
return LayOut(this).render().then(this.pageReady);
},
pageReady : function() {
},
});
UserDetails.activate = function() {
app.router.navigate('UserDetails', true);
};
UserDetails.configureRouting = function() {
app.router.route('UserDetails', 'UserDetails',
function() {
layoutmanager.setView('#content',
new UserDetails.Views.Content())
.render();
});
};
return UserDetails;
});
ADMIN:
define([
'modules/baseclass',
'modules/UserDetail'
],
function(BaseClass, UserDetails) {
UserDetail.Views.Content = UserDetail.Views.Content.extend({
render:function(){
console.log("rendering");
UserDetail.Views.Content.prototype.render();
}
});
//create admin model
//admin view
AdminView.Views.Content = Backbone.View.extend({
template: "admin-template",
events: {
},
initialize: function() {
this.model = new AdminModel.Model();
},
render: function(manage) {
return manage(this).render().then(this.pageReady);
},
pageReady: function() {
});
},
AdminView.activate = function() {
app.router.navigate('adminview', true);
};
AdminView.configureRouting = function() {
app.router.route('adminview', 'adminview', function() {
layoutmanager.setView('#content', new AdminView.Views.Content()).render();
layoutmanager.setView('#userDetials', new UserDetials.Views.Content()).render();
});
};
if(app.router && app.router.route) {
AdminView.configureRouting();
}
return AdminView;
});
Now if I have to call the render of the userdetails from admin view, the render method fails as the param is undefined.
I am not well versed with where the para in render is defined as I looked through my code and have not found anything
Either include the script tag for Userview.js before the script tag for AdminView.js, or using a module system like requirejs or browserify where you can specify the two modules as dependencies.
I have created a model like this
define(['backbone', 'text_dictionary'], function(Backbone, Text_Dict) {
var IndexPageModel = Backbone.Model.extend({
defaults:{
val_btn_gotohomepage : Text_Dict.val_btn_gotohomepage,
val_btn_gotologinpage : Text_Dict.val_btn_gotologinpage,
val_btn_gotolistpage : Text_Dict.val_btn_gotolistpage
}
});
return IndexPageModel;
});
and instantiated this model with 'new' in my page code like this
define([ 'page_layout',
'panel_itemview',
'header_itemview',
'content_itemview',
'footer_itemview',
'templates',
'text_dictionary',
'indexpage_model',
'indexpage_logic'],
function( Page,
Panel,
Header,
Content,
Footer,
Templates,
Text_Dict,
IndexPageModel,
IndexPage_BusnLogic) {
console.log("Success..Inside Index Page.");
var Page_Index = {};
Page_Index.page = (function(){
var _pageName = Text_Dict.indexpage_name;
var _pageModel = new IndexPageModel();
return _pageLayout = Page.pageLayout({
name:_pageName,
panelView: Panel.panelView({name:_pageName, pagetemplate: Templates.simple_panel}),
headerView: Header.headerView({name:_pageName, title: Text_Dict.indexpage_header, pagetemplate: Templates.header_with_buttons}),
contentView: Content.contentView({name:_pageName, page_model:_pageModel, pagetemplate:Templates.content_index, busn_logic:IndexPage_BusnLogic.HandleEvents}),
footerView: Footer.footerView({name:_pageName, title: Text_Dict.indexpage_footer, pagetemplate: Templates.simple_footer})
});
})();
return Page_Index;
});
my page gets created using the page layout
define([ 'underscore', 'marionette' ], function( _, Marionette ) {
console.log("Success..Inside Index View.");
var Page = {};
var _ReplaceWithRegion = Marionette.Region.extend({
open: function(view){
//Need this to keep Panel/Header/Content/Footer at the same level for panel to work properly
this.$el.replaceWith(view.el);
}
});
Page.pageLayout = function (opts) {
var _opts = _.extend ({ name: 'noname',
panelView: null,
headerView: null,
contentView: null,
footerView: null,
}, opts);
return new ( Marionette.Layout.extend({
tagName: 'section',
attributes: function() {
return {
'id': 'page_' + _opts.name,
'data-url': 'page_' + _opts.name,
'data-role': 'page',
'data-theme': 'a'
};
},
template: function () {
return "<div region_id='panel'/><div region_id='header'/><div region_id='content'/><div region_id='footer'/>";
},
regions: {
panel: {selector: "[region_id=panel]", regionType: _ReplaceWithRegion},
header: {selector: "[region_id=header]", regionType: _ReplaceWithRegion},
content: {selector: "[region_id=content]", regionType: _ReplaceWithRegion},
footer: {selector: "[region_id=footer]", regionType: _ReplaceWithRegion},
},
initialize: function(){
$('body').append(this.$el);
this.render();
},
onRender: function() {
if (this.options.panelView) {
this.panel.show (this.options.panelView);
};
if (this.options.headerView) {
this.header.show (this.options.headerView);
};
if (this.options.contentView) {
this.content.show(this.options.contentView);
};
if (this.options.footerView) {
this.footer.show (this.options.footerView);
};
},
}))(_opts);
};
return Page;
});
but in my itemview when i am passing model reference like this
define([ 'underscore', 'marionette', 'event_dictionary', 'app' ], function(_,
Marionette, Event_Dict, App) {
console.log("Success..Inside Content Index View.");
var Content = {};
Content.contentView = function(opts) {
return new (Marionette.ItemView.extend({
tagName : 'div',
attributes : function() {
console.log('options name==' + opts.name);
console.log("page model=="+opts.page_model);
return {
'region_id' : 'content',
'id' : 'content_' + opts.name,
'data-role' : 'content'
};
},
initialize : function() {
_.bindAll(this, "template");
},
template : function() {
return opts.pagetemplate;
},
model : function() {
return opts.page_model;
}
}))(opts);
};
return Content;
});
It's giving me error
Uncaught TypeError: Object function () {
return opts.page_model;
} has no method 'toJSON'
The model property of a view cannot be a function. Backbone allows this for some things like url (by way of the _.result helper function), but not in this case. Change your view code to not have a model function and just do this in initialize:
initialize: function (options) {
this.model = this.page_model = options.page_model;
}
UPDATE since you won't just take my word for it, here is the Marionette source that is almost certainly the top of your exception stack trace. Once again: view.model has to be a model object not a function. Fix that and the error will go away.
The accepted answer is correct, but it took a bit of messing about to find out why I had that error coming up, so I'm offering what the solution for my personal use-case was in case it helps anyone else stumbling upon this page in the future.
I had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: app.Entities.Models.meta
template: '#meta-template'
... when I should have had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: new app.Entities.Models.meta()
template: '#meta-template'
It's just a matter of instantiating the function definition.
I am getting Uncaught ReferenceError: _auditNumber is not defined error while trying to bind my model to the view using backbone.js and underscore.js
<script id="searchTemplate" type="text/template">
<div class="span4">
<p>"<%= _auditNumber %>"</p>
</div>
<div class="span4">
<p>"<%= _aic %>"</p>
</script>
Collection
//Collection
var AuditsCollection = Backbone.Collection.extend({
initialize: function() {
this.on('add', this.render);
},
render: function() {
_.each(this.models, function (item) {
var _auditView = new AuditView({
model: item
});
$("#audits").append(_auditView.render().el);
});
},
});
Model
var Audit = Backbone.Model.extend({
url: function () {
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
data.forEach(function (auditItem) {
var auditsCollection = new AuditsCollection();
auditsCollection.add(JSON.stringify(auditItem));
});
}
});
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
template: $("#searchTemplate").html(),
render: function () {
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
I know I am missing something simple, any help is appreciated.
2 problems (at least - you're kind of off in the weeds given how many backbone tutorials there are).
Your model URL is returning a list of results. That's what collections are for. Your model should fetch a single record and the parse method has to return the model's attribute data. If you stick with the tutorials, you won't need a custom url function and you won't need a custom parse function at all.
var Audit = Backbone.Model.extend({
url: function () {
//This needs to be a url like /audits/42 for a single record
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
//this needs to return an object
return data[0];
}
});
You aren't passing a valid data object to your template function.
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
//compile template string into function once
template: _.template($("#searchTemplate").html()),
render: function () {
//render template into unique HTML each time
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
So my issue is that the collection in my function that is being fired from my router vent/event aggregator does not have access to my main collection's fetched models.
My guess is that it's an asynchronous call issue, but how can I make it so the vented function call WAITS until the collection/models are fetched before executing? Or is that even my issue?
Here's is my relevant code. I'm using require.js and backbone to create a modular AMD app. Thank you so much in advance:
main.js
require(['views/app'], function (AppView) {
window.App = {
Vent : _.extend({}, Backbone.Events)
};
new AppView();
router.js
define([
'backbone',
], function(Backbone){
var MainRouter = Backbone.Router.extend({
routes: {
'levelone/:id':'showWork'
},
showWork: function (index){
App.Vent.trigger('addressChange', {
index: index
});
}
});
return MainRouter;
});
App.js
define([
'backbone',
'views/levelone/LevelOneView',
'views/leveltwo/LevelTwoView',
'views/static/StaticView',
'router'
],
function(Backbone, LevelOneView, LevelTwoView, StaticView, MainRouter){
var AppView = Backbone.View.extend({
el: $("body"),
events: {
...
},
initialize: function(){
new LevelOneView();
App.router = new MainRouter();
Backbone.history.start();
},
.............
LevelOneView.js
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
onDataHandler = function(collection) {
self.LevelTwoCollectionGrab();
};
this.collection = new LevelOneCollection([]);
this.collection.fetch({ success : onDataHandler, dataType: "jsonp" });
},
// We grab a Level Two Collection here so we can take the ids from it and add them to our Level One collection.
// This is necessary so we can create links between the two levels.
LevelTwoCollectionGrab: function(){
var self = this;
this.leveltwocollection = new LevelTwoCollectionBase([]);
onDataHandler = function(collection){
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
}
this.leveltwocollection.fetch({success : onDataHandler, dataType: "jsonp"});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
console.log(this.collection.models)
//returns a big fat empty array. WHY?!
}
You could use the jQuery Promises returned by fetch to help you know when both collections are fetched.
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
console.log('should be first');
this.collection = new LevelOneCollection([]);
this.fetchingLevelOne = this.collection.fetch({ dataType: "jsonp" });
this.fetchingLevelTwo = this.leveltwocollection.fetch({ dataType: "jsonp"});
// wait for both collections to be done fetching.
// this one will always be called before the one in addressChange
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be second');
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
var self = this;
// wait for both collections to be done fetching.
// this one will always be called AFTER the one in getCollection
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be third');
console.log(self.collection.models);
});
}
A nice thing about this, if the user is very very fast at typing in the address bar, and several addressChange calls are made, they will all wait until the collections are fetched and will execute in the proper order.
I think I solved it. Basically, I'm now calling the function inside of $.when function--
Like so:
$.when(this.collection.fetch(), this.leveltwocollection.fetch()).done(function(){
$.when(self.render()).done(function(){
_.each(self.collection.models, function(model){
var wpid = model.get('id'),
bbid = model.id;
if (wpid == index){
window.App.InfoPos.pos5 = bbid;
var modelinfo = model.toJSON();
$('.box5').empty();
$('.box5').html(tmplOne(modelinfo));
self.$el.animate({
'opacity': 1
}, 1200);
}
});
});
});
The function launches from inside the when call and then waits until completed before executing anything in the done function. Works now! Thanks for the help all, especially you Paul.