javascript namespace issue in socket.io + sencha - javascript

This could be a very basic Javascript I know but I just can't get it..
Ext.regController('Chat', {
initSocketConnection: function() {
this.chatStore = new App.Store.Chat();
...
this.socket = io.connect(settings.get('server'), {port: 8080});
this.socket.on(
'message',
this.addMessageToChatStore
);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this); << console shows the 'this' has become SocketNameSpace
this.chatStore.add(message); << this line error with "Undefined" chatStore
this.send(message);
},
Console print out shows that the "this" in the addMessageToChatStore function is "SocketNamespace"
How to I get rid of the error?
To generalize the problem. I think it's better to describe it as function chain calling dilemma.
A class has some local var that's instance of another class. When this var listens on certain events, it calls the parent's class's method. The problem is when this method is called, it's under the context of the other class and hence the scope has changed and the access to the original parent's class methods are denied.

In javascript, the this variable is determined by the caller. You could use a self executing function to ensure you're referencing the correct entity rather than using this:
Ext.regController('Chat', (function() {
var self = {
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
...
self.socket = io.connect(settings.get('server'), {port: 8080});
self.socket.on(
'message',
this.addMessageToChatStore
);
},
addMessageToChatStore: function(message) {
console.log(message);
self.chatStore.add(message);
self.send(message);
}
};
return self;
}()));
Update
Based on the full snippet, I'm not sure where the render function is defined, if render is a global function then self.viewChat = render({xtype : 'App.View.Chat'}); will suffice, otherwise it may be defined on this (the this that is defined for the call to index) so the following may suffice self.viewChat = this.render({xtype : 'App.View.Chat'});.
If I were a betting man, I'd go for the latter, so the code would be (with commented out code removed):
Ext.regController('Chat', (function() {
var self = {
index: function() {
if (!self.socket) {
self.initSocketConnection();
}
self.showChat();
},
/**
* init the socket connection to the node.js server
* using user settings
*
*/
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
self.chatStore.add({
user: "Figo",
message: "Welcome!"
});
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
self.socket = io.connect(settings.get('server'), {
port: 8080
});
// Event Listener
self.socket.on('connect', self.registerUser);
self.socket.on('message', self.addMessageToChatStore);
App.on('newMsg', self.sendMessageToServer);
},
sendMessageToServer: function(msg) {
self.socket.send(msg);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this);
console.log(this.parent);
self.chatStore.add(message);
self.socket.send(message);
},
registerUser: function() {
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
var user = {
nickname: settings.get('nickname'),
gravatar: settings.get('gravatar')
};
console.log(user);
self.socket.send(user);
},
/**
* Show chat view
*/
showChat: function() {
if (!self.viewChat) {
self.viewChat = this.render({
xtype: 'App.View.Chat'
});
self.viewChat.query('#settingsButton')[0].on('tap', self.showConfig, self);
}
self.application.viewport.setActiveItem(
self.viewChat, {
type: 'slide',
direction: 'left'
});
},
/**
* Show config View
*/
showConfig: function() {
Ext.dispatch({
controller: 'Viewport',
action: 'showConfig'
});
}
};
return self;
}()));

For #Rich.okelly
Here's the error (highlighted in code)
Uncaught TypeError: Object #<Object> has no method 'render' app.all.js:438
self.showChat app.all.js:438
self.index app.all.js:351
Ext.util.Dispatcher.Ext.extend.dispatch sencha-touch-debug.js:10630
Ext.dispatch sencha-touch-debug.js:10667
Ext.regController.showChat app.all.js:293
fire sencha-touch-debug.js:979
Ext.util.Observable.Ext.extend.fireEvent sencha-touch-debug.js:595
And here's the full code (other code within the same project not shown):
Ext.regController('Chat',(function() {
var self = {
/**
* Index action
*
* #return {void}
*/
index: function() {
if (!self.socket) {
self.initSocketConnection();
}
self.showChat();
},
/**
* init the socket connection to the node.js server
* using user settings
*
*/
initSocketConnection: function() {
self.chatStore = new App.Store.Chat();
self.chatStore.add({user: "Figo", message: "Welcome!"});
self.configStore = Ext.StoreMgr.get('ConfigStore');
//this.configStore = new App.Store.Config()
var settings = self.configStore.getAt(0);
//this.socket = new App.util.Socketio(settings.get('server'), {port: 4000});
//this.socket.connect();
self.socket = io.connect(settings.get('server'), {port: 8080});
/*
this.socket.on('message', function (data) {
console.log(data);
this.parent.addMessageToChatStore(data);
this.emit('message', data);
});
this.socket.on('message',
this.addMessageToChatStore
);
*/
// Event Listener
self.socket.on(
'connect',
self.registerUser
);
self.socket.on(
'message',
self.addMessageToChatStore
);
App.on(
'newMsg',
self.sendMessageToServer
);
},
sendMessageToServer: function(msg){
self.socket.send(msg);
},
addMessageToChatStore: function(message) {
console.log(message);
console.log(this);
console.log(this.parent);
//if (!this.chatStore)
//this.chatStore = new App.Store.Chat();
self.chatStore.add(message);
//App.Controller.Chat.chatStore.add(message);
self.socket.send(message);
},
registerUser: function() {
self.configStore = Ext.StoreMgr.get('ConfigStore');
var settings = self.configStore.getAt(0);
var user = {
nickname: settings.get('nickname'),
gravatar: settings.get('gravatar')
};
console.log(user);
self.socket.send(user);
},
/**
* Show chat view
*/
showChat: function() {
if (!self.viewChat) {
self.viewChat = self.render({ << fails over here
xtype: 'App.View.Chat'
});
self.viewChat.query('#settingsButton')[0].on(
'tap',
self.showConfig,
self
);
}
self.application.viewport.setActiveItem(
self.viewChat,
{
type: 'slide',
direction: 'left'
}
);
},
/**
* Show config View
*/
showConfig: function() {
Ext.dispatch({
controller: 'Viewport',
action : 'showConfig'
});
}
};
return self;
}()));

Related

Accessing Api with Javascript using Oauth

Having issues accessing data from my api once i have created a access token with a simple javascript client.
Here is my Js app object, as you can see i return a new access_token from my api - this works fine up to this point. I store the access_token into app.AccessToken for me to use in any other api calls i make throughout the app. But for some reason when i request anything the response is always the login page, so basically i am getting redirected when i try access anything even though i passing over a working in-date access_token.
var app = (function(){
/**
* Api
* #type Object
*/
var api = {
AccessToken : null,
views: {},
models: {},
collections: {},
content: null,
router: null,
documents: null,
init: function() {
this.content = $("#content");
this.documents = new api.collections.Documents();
Backbone.history.start();
return this;
},
changeContent: function(el) {
this.content.empty().append(el);
return this;
},
title: function(str) {
// set page title
}
};
/**
* ViewFactory
* #type Object
*/
var ViewFactory = {
documents: function() {
this.documentsView = new api.views.documents({
model: api.documents
});
return this.documentsView;
}
};
/**
* AppRouter
* #type Object
*/
var Router = Backbone.Router.extend({
routes: {
'' : 'documents'
},
documents: function() {
var view = ViewFactory.documents();
api.changeContent(view.$el);
view.render();
}
});
/**
* OAuth
* #type Object
* #return string
*/
var OAuth = {
title : 'Js Client',
clientId : 'NTUxNTY4YWE1NWUxMzI4',
username : 'john#globallcoach.com',
password : 'password',
init: function() {
var provision = OAuth.provision();
if(provision.hasOwnProperty('success')) {
var authenticate = OAuth.authenticate();
if(authenticate.hasOwnProperty('access_token')) {
api.AccessToken = authenticate['access_token'];
}
}
},
provision: function() {
var response;
$.ajax({
async: false,
url : 'http://customer-server-2.dev/oauth/provision/.json',
type : 'get',
data : {
title : OAuth.title,
client_id : OAuth.clientId
},
success:function(data) {
response = jQuery.parseJSON(data);
},
});
return response;
},
authenticate: function() {
var response;
$.ajax({
async: false,
url : 'http://customer-server-2.dev/oauth/token.json',
type : 'get',
data : {
'grant_type' : 'password',
'username' : OAuth.username,
'password' : OAuth.password,
'client_id' : OAuth.clientId,
},
success:function(data) {
response = data;
}
});
return response;
},
}
/**
* Exercute & return
*/
api.router = new Router();
OAuth.init();
return api;
})();
Solved! I needed to make sure that on the Rest AppController i needed to defined $this->Auth->allow() on the actions within the api scope.

How can i call function on view on the click event of Id, function is written on controller in backbone.js

My controller code is here.
spine.module("communityApp", function (communityApp, App, Backbone, Marionette, $, _) {
"use strict";
communityApp.Controllers.pforumController = Marionette.Controller.extend(
{
init: function(){
var func = _.bind(this._getpforum, this);
var request = App.request('alerts1:entities' , {origin:'pforum'});
$.when(request).then(func)
},
_getpforum:function(data){
var pforumCollectionView = new communityApp.CollectionViews.pforumCollectionViews({
collection: data
});
communityApp.activeTabView = pforumCollectionView;
// Populating the data
communityApp.activeTabLayout.pforum.show(pforumCollectionView);
},
});
});
view code is here
spine.module("communityApp", function (communityApp, App, Backbone, Marionette, $, _) {
// Load template
var a;
var pforumTemplateHtml = App.renderTemplate("pforumTemplate", {}, "communityModule/tabContainer/pforum");
// Define view(s)
communityApp.Views.pforumView = Marionette.ItemView.extend({
template: Handlebars.compile($(pforumTemplateHtml).html()),
tagName: "li",
onRender: function () {
this.object = this.model.toJSON();
},
events: {
"click #postcomment" : "alrt",
"click #recent-btn": "recent",
"click #my-posts": "myposts",
"click #popular-btn": "popular",
"click #follow-btn": "follow",
"click #my-posts": "LeftLinks",
"click #popular-btn": "LeftLinks",
"click #follow-btn": "LeftLinks"
},
postcomments : function ()
{
$("#recent-post-main-container").hide();
$("#recent-post-main-container2").show();
},
alrt : function ()
{
alert ("In Progress ......");
},
showCommentEiditor : function (){
$(".comment-popup-container").show();
$(".comment-txt-area").val('');
},
showPforumTab : function ()
{
$("#recent-post-main-container2").show();
$("#recent-post-main-container").hide();
},
showComments : function(){
$("#loading").show();
$(".tab-pane").hide();
$(".left-content").hide();
$("#recent-post-main-container2").show();
//$(".left-content-commentEditor").show();
$(".comm-tab-content-container").css('height','200px');
$(".comment-txt-area").val('');
$(".left-content-comment").show();
},
hideCommentPopup : function ()
{
$("#public-question-comment").hide();
},
// Show Loading sign
showLoading : function () {
$('#loading').show();
},
// UnLoading Function
hideLoading : function (){
$('#loading').hide();
},
// Add New Event Function
addEvent : function()
{
//$("#name").val(getBackResponse.user.FullName);
//$("#phone").val(getBackResponse.user.CellPhone);
//$("#email").val(getBackResponse.user.UserName);
$(".overly.addevent").show();
$('#lang').val(lat);
$('#long').val(long);
document.getElementById("my-gllpMap").style.width = "100%";
var my_gllpMap = document.getElementById("my-gllpMap");
google.maps.event.trigger( my_gllpMap, "resize" );
},
setValues : function(key,value)
{
window.localStorage.setItem(key,value);
},
getValues : function (key)
{
return window.localStorage.getItem(key);
},
closeAddEvent:function ()
{
$(".overly.addevent").hide();
},
// Show Over lay
showOverly:function ()
{
$('.overly-right-tab').show();
},
// Hide Loading sign
hideOverly : function()
{
$('.overly-right-tab').hide();
},
LeftLinks: function (e) {
var elem = $(e.target).closest('a');
var elem = $(e.target).closest('a');
var event = elem.attr('name');
switch (event) {
case "myposts":
var _this = $.extend({}, this, true);
_this.event = 'myposts';
this.LinkUrl.call(_this);
//$("#my-posts").addClass('active');
//$("#public-fourm-top-tab").addClass('TabbedPanelsTabSelected');
//$(".types").removeClass('active');
break;
case "recents":
var _this = $.extend({}, this, true);
_this.event = 'recents';
this.LinkUrl.call(_this);
$(".types").removeClass('active');
$("#recent-btn").addClass('active')
//$("#pforum").removeClass('active');
// $("#recent").addClass('active');
break;
case "populars":
var _this = $.extend({}, this, true);
_this.event = 'populars';
this.LinkUrl.call(_this);
$(".types").removeClass('active');
$("#popular-btn").addClass('active')
// $("#pforum").removeClass('active');
//$("#popular").addClass('active');
break;
case "follows":
var _this = $.extend({}, this, true);
_this.event = 'follows';
this.LinkUrl.call(_this);
$(".types").removeClass('active');
$("#follow-btn").addClass('active')
break;
}
},
LinkUrl: function (modalThis) {
communityApp.activeTabView.collection = []; // currently empty data
communityApp.activeTabView.render();
className: 'comm-main-container'
// uncomment these lines when getting data fro web service route, it will repopulate the data
var func = _.bind(function (data) {
communityApp.activeTabView.collection = data;
communityApp.activeTabView.render();
}, this);
switch (this.event) {
case "myposts":
$.when(App.request('alertLinks:entities', {
origin: 'pforum',
event: this.event
})).then(func);
break;
case "recents":
$.when(App.request('alertLinks:entities', {
origin: 'pforum',
event: this.event
})).then(func);
break;
case "populars":
$.when(App.request('alertLinks:entities', {
origin: 'pforum',
origin1:'popular',
event: this.event
})).then(func);
break;
case "follows":
$.when(App.request('alertLinks:entities', {
origin: 'pforum',
event: this.event
})).then(func);
break;
}
return true;
}
});
// define collection views to hold many communityAppView:
communityApp.CollectionViews.pforumCollectionViews = Marionette.CollectionView.extend({
tagName: "ul",
itemView: communityApp.Views.pforumView
});
});
Whenever I need to share an event between a view and controller I usually wire up the listeners within the module that instantiates the controller. This example is a bit contrived, but it gets the point across. The full working code is in this codepen. The relevant bit is copied here. Notice the line this.listenTo(view, 'itemview:selected', this.itemSelected); where the view's event triggers a method on the controller.
App.module("SampleModule", function(Mod, App, Backbone, Marionette, $, _) {
// Define a controller to run this module
// --------------------------------------
var Controller = Marionette.Controller.extend({
initialize: function(options){
this.region = options.region
},
itemSelected: function (view) {
var logView = new LogView();
$('#log').append(logView.render('selected:' + view.cid).el);
},
show: function(){
var collection = new Backbone.Collection(window.testData);
var view = new CollectionView({
collection: collection
});
this.listenTo(view, 'itemview:selected', this.itemSelected);
this.region.show(view);
}
});
// Initialize this module when the app starts
// ------------------------------------------
Mod.addInitializer(function(){
Mod.controller = new Controller({
region: App.mainRegion
});
Mod.controller.show();
});
});
The other way to accomplish this, if you cannot wire it all up within the same module, is to use Marionette's messaging infrastructure. For example, you can use the application's event aggregator to pass events around.

Failed to load routed module requirejs? durandal bug?

I created an Asp.Net MVC and used nuget to add HotTowel (V2.0.1 of 9/11/2013). I created a couple of ViewModel, Models. However, I got the following error.
"Failed to load routed module (viewmodels/myVM). Details: Load timeout for modules: durandal/plugins/router\nhttp://requirejs.org/docs/errors.html#timeout"
Is it the problem of durandal/plugins/router? Or it can be caused by some code I added?
The error occurred at Scripts/durandal/system.js.
var logError = function(error) {
if(error instanceof Error){
throw error;
}
throw new Error(error);
};
The following is the VM code.
define(['services/datacontext', 'durandal/plugins/router', 'services/logger'],
// Remove the durandal/plugins/router and the functions will get rid of the error.
function (datacontext, router, logger) {
var title = 'Event';
var vm = {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
return vm;
//#region Internal Methods
var events = ko.observableArray();
function activate() {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
var deactivate = function () {
events([]);
};
var refresh = function () {
return datacontext.getEventPartials(events, true);
};
//#endregion
});
The following is the call stack
logError [system.js] Line 92 Script
Anonymous function [router.js] Line 359 Script
[External Code]
Anonymous function [system.js] Line 260 Script
[External Code]
[Async Call]
....
Code at router.js,
isProcessing(true);
router.activeInstruction(instruction);
if (canReuseCurrentActivation(instruction)) {
ensureActivation(activator.create(), currentActivation, instruction);
} else {
system.acquire(instruction.config.moduleId).then(function(module) {
var instance = system.resolveObject(module);
ensureActivation(activeItem, instance, instruction);
}).fail(function(err){
system.error('Failed to load routed module (' + instruction.config.moduleId + '). Details: ' + err.message);
});
}
}
And previous one in system.js.
acquire: function() {
var modules,
first = arguments[0],
arrayRequest = false;
if(system.isArray(first)){
modules = first;
arrayRequest = true;
}else{
modules = slice.call(arguments, 0);
}
return this.defer(function(dfd) {
require(modules, function() {
var args = arguments;
setTimeout(function() {
if(args.length > 1 || arrayRequest){
dfd.resolve(slice.call(args, 0));
}else{
dfd.resolve(args[0]);
}
}, 1);
}, function(err){
dfd.reject(err);
});
}).promise();
},
Based on the comments I'd recommend to modify the vm code slightly, so that all variables that are returned via vm are defined before use. In addition 'plugins/router' is used instead of 'durandal/plugins/router'.
define(['services/datacontext', 'plugins/router', 'services/logger'],
// Remove the durandal/plugins/router and the functions will get rid of the error.
function (datacontext, router, logger) {
var title = 'Event';
var events = ko.observableArray();
var deactivate = function () {
events([]);
};
var refresh = function () {
return datacontext.getEventPartials(events, true);
};
var vm = {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
//#endregion
});
BTW the name Internals methods is misleading as everything in that region is returned via vm. I prefer to work with named function instead, which get created before the return statement if they are returned and place them below the return statement in a Internal methods region if they are not returned.
define(['services/datacontext', 'plugins/router', 'services/logger'],
function( datacontext, router, logger ) {
var title = 'Event';
var events = ko.observableArray();
function deactivate () {
events([]);
}
function refresh () {
return datacontext.getEventPartials(events, true);
}
function activate () {
logger.log(title + ' View Activated', null, title, true);
return datacontext.getEventPartials(events);
}
return {
activate: activate,
deactivate: deactivate,
refresh: refresh,
events: events,
title: title
};
//#region Internal Methods
//#endregion
});

angular-ui/bootstrap: Testing a controller that uses a dialog

I've a controller that uses a Dialog from angular-ui/bootstrap:
function ClientFeatureController($dialog, $scope, ClientFeature, Country, FeatureService) {
//Get list of client features for selected client (that is set in ClientController)
$scope.clientFeatures = ClientFeature.query({clientId: $scope.selected.client.id}, function () {
console.log('getting clientfeatures for clientid: ' + $scope.selected.client.id);
console.log($scope.clientFeatures);
});
//Selected ClientFeature
$scope.selectedClientFeature = {};
/**
* Edit selected clientFeature.
* #param clientFeature
*/
$scope.editClientFeature = function (clientFeature) {
//set selectedClientFeature for data binding
$scope.selectedClientFeature = clientFeature;
var dialogOpts = {
templateUrl: 'partials/clients/dialogs/clientfeature-edit.html',
controller: 'EditClientFeatureController',
resolve: {selectedClientFeature: function () {
return clientFeature;
} }
};
//open dialog box
$dialog.dialog(dialogOpts).open().then(function (result) {
if (result) {
$scope.selectedClientFeature = result;
$scope.selectedClientFeature.$save({clientId: $scope.selectedClientFeature.client.id}, function (data, headers) {
console.log('saved.');
}, null);
}
});
};
});
I'm almost completely new to testing, and figured that maybe I need to test two things:
That a dialog opens when $scope.editClientFeature() is called
That $save is called successfully after a dialog is closed and returns a 'result'
My really messed up test now looks like this:
describe('ClientFeatureController', function () {
var scope, $dialog, provider;
beforeEach(function () {
inject(function ($controller, $httpBackend, $rootScope, _$dialog_) {
scope = $rootScope;
$dialog = _$dialog_;
//mock client
scope.selected = {};
scope.selected.client = {
id: 23805
};
$httpBackend.whenGET('http://localhost:3001/client/' + scope.selected.client.id + '/clientfeatures').respond(mockClientFeatures);
$controller('ClientFeatureController', {$scope: scope});
$httpBackend.flush();
});
});
it('should inject dialog service from angular-ui-bootstrap module', function () {
expect($dialog).toBeDefined();
console.log($dialog); //{}
});
var dialog;
var createDialog = function (opts) {
dialog = $dialog.dialog(opts);
};
describe('when editing a clientfeature', function () {
createDialog({});
console.log(dialog); //undefined
// var res;
// var d;
// beforeEach(function () {
// var dialogOpts = {
// template: '<div>dummy template</div>'
// };
// console.log(dialog);
// d = $dialog.dialog(dialogOpts);
// d.open();
// });
//
// it('should open a dialog when editing a client feature', function () {
// expect(d.isOpen()).toBe(true);
// });
});
});
The immediate problem now is that I'm unable to create/open a dialog. I get the following error:
Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED
TypeError: Cannot call method 'dialog' of undefined
It would be great if someone has already written a test for a similar use case and can provide me with an example as I'm pretty lost.
Thanks,
Shaun
I was struggling with the same problem until right now, after trolling the the github repo i found the dialog tests and used that as a starting point :
var $dialog,$scope,$httpBackend;
beforeEach(module('ui.bootstrap.dialog'));
beforeEach(function(){
inject(function (_$dialog_, _$httpBackend_, $controller){
$dialog = _$dialog_;
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/appServer/list')
.respond([{
id:1,
name:'test1'
},
{
id:2,
name:'test2'
},
{
id:3,
name:'test3'
}]);
//setup controller scope
scope = {};
ServerCtrl = $controller('ServerCtrl', {
$scope: scope,
$dialog:$dialog
});
});
});
I also prefer a proper mock. When it is not available, i patch the service
To test this:
$dialog.messageBox(title, msg, btns)
.open()
.then(function (result) {
if (result == 'ok') {
// block executed if user click OK
}
});
You can patch $dialog like this:
$dialog.messageBox = function (title, msg, btns) {
return {
open: function () {
return {
then: function (callback) {
callback('ok'); // 'ok' will be set to param result
}
}
}
}
};
Personally I try to mock all services out. If the ui-bootstrap project does not provide a $dialog mock, you should open a bug ticket there and ask them for one. However creating one is as easy.
The mock service should have fake methods that do nothing but return promises. It should also give you a method to flush all asynchronous methods to make it easier to do synchronous testing.
I find it clearest to write my own mock of the dialog. Here's an example of mocking out a dialog to simulate "yes" being chosen.
Code under test
.controller('AdminListingCtrl', function AdminListingController($scope, $dialog, houseRepository) {
$scope.houses = houseRepository.query();
$scope.remove = function (house) {
var dlg = $dialog.messageBox('Delete house', 'Are you sure?', [
{label: 'Yep', result: 'yes'},
{label: 'Nope', result: 'no'}
]);
dlg.open().then(function (result) {
if (result == 'yes') {
houseRepository.remove(house.id);
$scope.houses = houseRepository.query();
}
});
};
}
Tests
describe('when deleting a house', function () {
var fakeDialog = {
open: function()
{
return {
then: function(callback) {
callback("yes");
}
};
}
};
beforeEach(inject(function($dialog) {
spyOn($dialog, 'messageBox').andReturn(fakeDialog);
}));
it('should call the remove method on the houseRepository', function () {
scope.remove({id: 99});
expect(houseRepository.remove).toHaveBeenCalledWith(99);
});
// etc
});

Cannot set property 'new_form' of undefined

I'm having trouble with a Backbone.js tutorial from Treehouse. Here's my code:
var NotesApp = (function () {
var App = {
stores: {}
}
App.stores.notes = new Store('notes');
// Note Model
var Note = Backbone.Model.extend({
//Local Storage
localStorage: App.stores.notes,
initialize: function () {
if (!this.get('title')) {
this.set({
title: "Note at " + Date()
})
};
if (!this.get('body')) {
this.set({
body: "No Body"
})
};
}
})
//Views
var NewFormView = Backbone.View.extend({
events: {
"submit form": "createNote"
},
createNote: function (e) {
var attrs = this.getAttributes(),
note = new Note();
note.set(attrs);
note.save();
},
getAttributes: function () {
return {
title: this.$('form [name=title]').val(),
body: this.$('form [name=body]').val()
}
}
});
window.Note = Note;
$(document).ready(function () {
App.views.new_form = new NewFormView({
el: $('#new')
});
})
return App
})();
And I get the error: Cannot set property 'new_form' of undefined
I've tried to go back and copy the code as close as possible, but I still couldn't get it to work. Any suggestions?
After stores: {} add ,
views: {}.
You need an object to attach your view to - JavaScript has no vivification

Categories

Resources