I've already checked the answers from people that have apparently the same problem but I haven't been able to fix my problem yet.
I have a view (teacher.profile.js) which is calling a collection (levels.js) which has a collection (subject.js), but when I load the page the subject collection is always undefined and said not to be a constructor.
After refreshing so many times, sometimes the second one, the subject collection is there and works.
Could anyone tell me what is wrong, please?
Thanks
subject.js
define(["GB"], function(GB) {
var subjectModel = GB.Model.extend({
idAttribute:"subjectId",
defaults: {
subjectId: '',
name: '',
levelId: '',
selected: false
}
});
return subjectModel;
});
subjects.js
define([
"GB",
"modules/register/teacher/models/subject"],
function (GB, SubjectModel) {
var subjectCollection = GB.Collection.extend({
model: SubjectModel,
url: "webapi/api/Administration/Subject"
});
return subjectCollection;
});
level.js
define(['GB', 'modules/register/teacher/models/subjects'], function (GB, SubjectCollection) {
var levelModel = GB.Model.extend({
defaults: {
levelId: '',
name: '',
iconClass: '',
subjects: ''
},
parse: function(attrs) {
**attrs.subjects = new SubjectCollection(attrs.subjects);** //Here is where the error is thrown. SubjectCollection is not a constructor.
return attrs;
}
});
return levelModel;
});
levels.js
define(["GB", "modules/register/teacher/models/level"], function (GB, LevelModel) {
var levelCollection = GB.Collection.extend({
model: LevelModel,
url: "webapi/api/Administration/Level"
});
return levelCollection;
});
teacher.profile.js view
define([
"GB",
"tpl!modules/register/teacher/teacherProfile", "modules/register/teacher/subject.level"
], function(GB, Template, SubjectLevelView) {
var view = GB.Views.Item.extend({
template: Template,
ui: {
subjects: "#subject-list",
levels: "#level-list",
infoTitle: "#info-title",
subjectsLevels: "#subjects-levels"
},
initialize: function() {
this.userLevels = [];
},
onRender: function() {
var self = this;
this.ui.infoTitle.text(GB.Localise("teacher-data-title"));
var levelsPromise = this.collection.fetch();
$.when(levelsPromise)
.then(function() {
var levelsSubjects = _.map(self.collection.models, function(item) {
if (item.get("subjects").length > 0) {
var view = new SubjectLevelView({ model: item });
self.userLevels.push(item);
return view.render().el;
}
});
self.ui.subjectsLevels.append(levelsSubjects);
});
}
});
return view;
});
main.js
require.config({
map: {
'*': {
'css': 'plugins/require-css/css',
'tpl': 'plugins/require.lodash.template', //this is our templating helper tpl!.html, its brings the template in already underscored, this is faster slightly than text! & subsequent template
'lodash': 'underscore'
}
},
paths: {
'plugins': '../plugins',
'styles':'../css',
'localisation':'core/localisation',
'jquery': '../lib/jquery-2.1.4',
'jquery.browser': '../plugins/jquery.browser',
'jquery.video': '../plugins/vide/jquery.vide',
'waypoints': '../plugins/waypoints/jquery.waypoints',
'backbone': '../lib/backbone',
'marionette': '../lib/backbone.marionette',
'text': '../lib/text',
'underscore': '../lib/lodash.underscore', //yes underscore is now lodash - its a better performer + extra features + no downside :)
'lodash': '../lib/lodash.underscore',
'bootstrap': '../lib/bootstrap',
'bootstrap-dialog': '../plugins/bootstrap-dialog/js/bootstrap-dialog',
'modernizr': '../lib/modernizr-2.8.3',
'backbone.validation': '../plugins/backbone-validation',
'themepunch.tools': '../plugins/rs-plugin/js/jquery.themepunch.tools.min',
'themepunch.rev': '../plugins/rs-plugin/js/jquery.themepunch.revolution.min',
'smoothscroll': '../plugins/SmoothScroll',
'json': '../plugins/requirejs-plugins/json',
'cldr': '../plugins/localisation/cldrjs/cldr',
'cldr-data': '../plugins/localisation/cldr-data',
'globalize': '../plugins/localisation/globalize/globalize',
'localise':'localisation/localise',
'GB': 'app'
},
shim: {
'marionette': {
deps: ['backbone'],
exports: 'Marionette'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
},
'backbone.validation': {
deps: ['backbone', 'underscore']
},
'bootstrap': {
deps: ['jquery'],
},
'bootstrap-dialog': {
deps: ['bootstrap'],
},
'smoothscroll': {
deps: ['jquery.browser']
},
'themepunch.tools': {
deps: ['jquery']
},
'themepunch.rev': {
deps: ['themepunch.tools']
},
'jquery.browser': {
deps: ['jquery']
},
'waypoints': {
deps: ['jquery']
},
'jquery.video': {
deps: ['jquery']
},
'globalize': {
deps: ['cldr']
},
'json': {
deps: ['text']
}
}
});
require([
"GB",
"routes/application.router",
"bootstrap",
"core/validation"],
function (GB, AppRouter) {
GB.routers.application = new AppRouter();
GB.start();
});
app.js
define([
"marionette",
"core/GB.ini",
"globalize",
"localisation/localise",
"bootstrap-dialog",
"json!cldr-data/supplemental/likelySubtags.json",
"json!cldr-data/supplemental/plurals.json",
"json!cldr-data/supplemental/timeData.json",
"json!cldr-data/supplemental/weekData.json",
"json!localisation/messages/es.json",
"globalize/number",
"globalize/message",
"globalize/plural",
"modernizr",
"smoothscroll",
],
function (Marionette, AppIni, Globalize, Localise, Dialog, LikeSubtags, Plurals, TimeData, WeekData, Messages) {
var GB = window.GB = new Marionette.Application();
GB.User = {};
GB.routers = {};
Globalize.load(
LikeSubtags,
Plurals,
TimeData,
WeekData
);
Globalize.loadMessages(Messages);
Globalize.locale("es");
GB.Globalize = Globalize;
GB.Localise = Localise;
GB.Dialog = Dialog;
GB.Model = Backbone.Model.extend({});
GB.Collection = Backbone.Collection.extend({});
GB.Views = {
//if we do expand on these views they should probably get their own file.
Item: Marionette.ItemView.extend({}), //for a single model
Collection: Marionette.CollectionView.extend({}), //for a collection
Composite: Marionette.CompositeView.extend({}), //for a combination of a model and a collection
Layout: Marionette.LayoutView.extend({})
};
GB.session = new GB.Model();
GB.getUrl = function () {
return Backbone.history.location.origin + Backbone.history.location.pathname;
}
GB.getCurrentRoute = function () {
return Backbone.history.fragment;
};
GB.on("before:start", function () {
var RegionContainer = Marionette.LayoutView.extend({
el: "#app-container",
regions: {
header: "#header-wrapper",
main: "#main-region",
footer: "#footer-region",
dialog: "#dialog-region"
}
});
GB.regions = new RegionContainer();
});
GB.on("start", function () {
require(["modules/header/header.module"], function () {
GB.Header.Controllers.Overview.show();
});
require(["modules/footer/footer.module"], function () {
GB.Footer.Controllers.Overview.show();
});
AppIni.start();
Backbone.history.start();
if (GB.getCurrentRoute() === "")
Backbone.history.navigate("#home", { trigger: true });
});
return GB;
});
Foder Structure
enter image description here
Ok, my first guess was that parse method on the levelModel is never being called. But it should always be called on fetched. Moreover, I reread your question and saw that sometimes it did work for you.
So my second guess now is a race condition with requirejs. i.e: sometimes inside levels.js SubjectCollection is a real Backbone Collection and sometimes is undefined.
One of the reason for such a race can be Circular Dependencies. Can you share the full source? or verify that you dont have such a circulation?
Related
I am trying to render a View(customerEditView) using modular BackboneJS but it gives me the uncaught type error.
this is my router.js file:
define([
'jquery',
'underscore',
'backbone',
'views/Customers/CustomerEditView',
'views/Customers/CustomerListView'
], function($, _, Backbone, CustomerEditView, CustomerListView) {
var Router = Backbone.Router.extend({
routes: {
"customers": "customerhome",
"editcustomer/:id": "editcustomer",
"newcustomer": "editcustomer",
},
customerhome: function () {
var customerListView = new CustomerListView();
customerListView.render();
},
editcustomer: function (id) {
var customerEditView = new CustomerEditView();
customerEditView.render({ id: id });
}
});
var initialize = function () {
var router = new Router;
Backbone.history.start();
};
return {
initialize: initialize
};
});
and this is my customerEditView file:
define([
'jquery',
'underscore',
'backbone',
'router',
'models/Customers/Customer',
'helper/Serialize',
'text!template/Customer/CustomerEditTemplate.html'
], function ($, _, Backbone, router, Customer, Router, serializeObject, CustomerEditTemplate) {
var CustomerEditView = Backbone.View.extend({
el: '.page',
events: {
'submit .edit-customer-form': 'saveCustomer',
'click .delete': 'deleteCustomer'
},
saveCustomer: function (ev) {
var customerDetails = $(ev.currentTarget).serializeObject();
var customer = new Customer();
customer.save(customerDetails, {
success: function (customer) {
Backbone.history.navigate('', { trigger: true });
}
});
return false;
},
deleteCustomer: function (ev) {
this.customer.destroy({
success: function () {
console.log('destroyed');
Backbone.history.navigate('', { trigger: true });
}
});
return false;
},
render: function (options) {
var that = this;
if (options.id) {
that.customer = new Customer({ id: options.id });
that.customer.fetch({
success: function (customer) {
var template = _.template(CustomerEditTemplate);
that.$el.html(template({ customer: customer }));
}
});
} else {
var template = _.template(CustomerEditTemplate);
that.$el.html(template({ customer: null }));
}
}
});
return CustomerEditView;
});
I am building an application with Backbone and to go from step 1 to step 2 I make use the of the router.navigate function. Now it will go to the next page and go back etc.
However every step I take will be kept in the history and every page in the history will be visited after each event. This will also show up in the console log.
Now I am using require.js as well
Here is my router:
var OF = OF || {};
OF.SubscribeRouter = Backbone.Router.extend({
routes: {
"step/:stepNumber": "goToStep",
"*other": "defaultRoute"
},
goToStep: function(stepNumber) {
switch(stepNumber) {
case "1":
require(['./views/step1],function(Step1) {
OF.step1 = new OF.Step1;
OF.step1.render();
});
break;
case "2":
require(['./views/step2'],function(Step2) {
OF.step2 = new OF.Step2;
OF.step2.render();
});
break;
case "3":
require(['./views/step3'],function(Step3) {
OF.step3 = new OF.Step3;
OF.step3.render();
});
break;
case "4":
require(['./views/step4'],function(Step4) {
OF.step4 = new OF.Step4;
OF.step4.render();
});
break;
case "5":
require(['./views/step5'],function(Step5) {
OF.step5 = new OF.Step5;
OF.step5.render();
});
break;
case "6":
require(['./views/step6'],function(Step6) {
OF.step6 = new OF.Step6;
OF.step6.render();
});
break;
}
},
defaultRoute: function(other) {
//start loading the welcome page
this.goToStep(1);
}
});
Here is my main file which will start the router:
var OF = OF || {};
require.config({
paths: {
underscore: 'vendor/underscore-min',
jquery: 'vendor/jquery-2.0.3',
json2: 'vendor/json2',
backbone: 'vendor/backbone-min',
handlebars: 'vendor/handlebars',
router: 'routers/router'
},
shim: {
'backbone': {
deps: ['underscore', 'jquery', 'json2'],
exports: 'backbone'
},
'handlebars': {
deps: ['jquery', 'json2'],
exports: 'handlebars'
},
'templateReader': {
deps: ['jquery', 'json2', 'handlebars'],
exports: 'templateReader'
},
'router': {
deps: ['jquery', 'json2', 'backbone'],
exports: ''
}
}
});
require(['router'], function(SubscribeRouter) {
// create the application
'use strict';
OF = {
router: new OF.SubscribeRouter(),
};
//start router
Backbone.history.start();
});
And here is the view which will trigger the event for 'page change':
var OF = OF || {};
OF.Step1 = Backbone.View.extend({
el: '#content',
initialize: function() {
console.log("you're in the address view");
},
render: function() {
//save this in that ;)
var that = this;
OF.template.get('step1-step1', function(data) {
//set the source en precompile the template
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
//fill template with object or ''
var compiled = template(OF);
//now place the completely compiled html into the page
that.$el.html(compiled);
});
},
events: {
"click #next": "nextStep"
},
nextStep: function() {
OF.router.navigate('step/2', {trigger: true});
}
});
So this is what I see in my console log after clicking next:
GET template-step2.html
now going back:
GET template-step1.html
So all seems well right now.
However now that I am back on step1 and click on next I expect to go to step2.
Unfortunately I will go to step3 and this is what I see in my console log:
GET template-step2.html
GET template-step2.html
GET template-step3.html
Is there a way to clear this history of some kind or to prevent this from caching or whatever it is it does?
where can I find a list of options for the router.navigate method?
I found the reason for this 'error' and it is because I use the same ID for the buttons in every template. When I changed this, the problem was solved.
Though I am still figuring out if the cache cannot be cleared so I can still use the same ID's (it is a next page now isn't it?).
I added this to my backbone-extend.js file which resides in the same folder as backbone-min.js...
_.extend(Backbone.View.prototype, {
getFormData: function(form) {
var unindexed_array = form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
});
However, when I call this.getFormData in my view code, I get a method not defined error. What am I missing? Thanks for your help!
Edit: Here is my view. I have to uncomment the getFormData method to make it work. It can't see the getFormData otherwise...
define([
'jquery',
'underscore',
'backbone',
'models/Member',
'text!templates/memberEditTemplate.html'
], function($, _, Backbone, Member, memberEditTemplate) {
var MemberEditView = Backbone.View.extend({
el: $("#page"),
model: 'member',
initialize: function(args) {
this.member = new Member({ id: args.id });
this.member.on('error', this.eventSyncError, this);
this.member.on('sync', this.eventSyncModelLoaded, this);
this.member.fetch();
},
events: {
"click #bttnMemberSave": "bttnClickMemberSave"
},
eventSyncError: function(model,response,options) {
console.log('Sync error='+response.statusText);
$('#server-message').css({'color':'red', 'font-weight':'bold'}).text(response.statusText);
//$('#server-message').text(response.statusText);
},
eventSyncModelLoaded: function(model,response,options) {
this.render();
},
eventSyncModelSaved: function(model,response,options) {
console.log("Member saved!");
$('#server-message').css({'color':'green', 'font-weight':'bold'}).text("Member saved!");
//$('#server-message').text('Member saved!');
var to = setTimeout(function() { Backbone.history.navigate('members', true); }, 2000);
},
bttnClickMemberSave: function() {
var data = this.getFormData($('#member-form').find('form'));
this.member.save(data, { success: this.eventSyncModelSaved });
},
// getFormData: function(form) {
// var unindexed_array = form.serializeArray();
// var indexed_array = {};
// $.map(unindexed_array, function(n, i){
// indexed_array[n['name']] = n['value'];
// });
// return indexed_array;
// },
render: function() {
this.member.toJSON();
var compiledTemplate = _.template( memberEditTemplate, { member: this.member } );
this.$el.html( compiledTemplate );
return this;
}
});
return MemberEditView;
});
Ok, I added backbone-extend.js to the RequireJS required files array in my app.js, now it's working.
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'm trying to integrate firebase login mechanisms into my phonegap application based on backbone.js and require.js.
I successfully set login with facebook and login with twitter and they seem to work properly.
Now i have problem with the last one: login with email and password... i don't know what's happening but is seems login with password doesn't call the callback method inside my authclient definition.
That's my main file i call with require at the beginning of the execution:
require.config({
paths: {
domReady: '../lib/require/domReady',
text: '../lib/require/text-1.0.6',
async: '../lib/require/async',
zepto: '../lib/zepto/zepto',
underscore: '../lib/underscore/underscore-min',
backbone: '../lib/backbone/backbone',
handlebars: '../lib/handlebars/handlebars',
firebase: '../lib/firebase/firebase',
backfire: '../lib/firebase/backfire',
fireauth: '../lib/firebase/firebase-auth-client',
leaflet: '../lib/leaflet/leaflet',
barcodescanner: '../lib/barcodescanner/barcodescanner',
templates: '../templates',
},
shim: {
'zepto': {
exports: '$'
},
'underscore': {
exports: '_'
},
'backbone': {
deps: ['zepto', 'underscore'],
exports: 'Backbone'
},
'handlebars': {
exports: 'Handlebars'
},
'firebase': {
exports: 'Firebase'
},
'backfire': {
deps: ['backbone','firebase'],
exports: 'Backfire'
},
'fireauth': {
deps: ['firebase'],
exports: 'Fireauth'
},
'leaflet': {
exports: 'L'
},
'barcodescanner': {
exports: 'Barcodescanner'
}
}
});
require(['zepto','domReady','underscore','backbone','firebase','fireauth','router'],
function ($,domReady, _,Backbone,Firebase,Fireauth,AppRouter) {
domReady(function () {
document.addEventListener("deviceready", run, false);
});
function run() {
firebaseRef = new Firebase('https://cicero.firebaseio.com');
authClient = new FirebaseAuthClient(firebaseRef, function(error, user) {
if (error) {
alert("error during user login");
} else if (user) {
auth = user;
Backbone.history.navigate("map", {trigger: true});
} else {
auth = undefined;
}
});
new AppRouter();
Backbone.history.start();
}
});
and this is a view in which i call login method:
define(["zepto", "underscore", "backbone", "handlebars","firebase","fireauth","text!templates/loginView.html"],
function ($, _, Backbone, Handlebars,Firebase,Fireauth,template) {
var loginView = Backbone.View.extend({
events: {
"touchstart #login" : "login",
"touchstart #register" : "showRegistration",
"touchstart #guest" : "showMap",
"touchstart #facebook" : "loginFacebook",
"touchstart #twitter" : "loginTwitter"
},
template: Handlebars.compile(template),
initialize: function () {
this.render();
},
render: function (eventName) {
$(this.el).empty();
$(this.el).html(this.template());
return this;
},
showRegistration: function () {
Backbone.history.navigate("register", {trigger: true});
},
showMap: function () {
Backbone.history.navigate("map", {trigger: true});
},
login: function(){
var user_email = $('#email').attr('value');
var user_password = $('#password').attr('value');
authClient.login("password", {
email: user_email,
password: user_password
});
},
loginFacebook: function(){
authClient.login("facebook");
},
loginTwitter: function(){
authClient.login("twitter");
}
});
return loginView;
});
As you can see i use 3 global var (firebaseRef, authCLient and auth) in order to get their references in every part of my application even if i don't know if it's a good way to do that.
If i try to login with facebook and twitter it works and callback function redirect me inside the new page, login with password and email instead makes a refresh of the page but it doesn't change it.