Backbone on route not giving me the route in the callback - backbone.js

I am trying to call the router.on('route', callback) function, but the callback is not provided with the route ...
My router/app:
var Backbone = require('backbone')
Backbone.$ = $
var _ = require('underscore')
var App = Backbone.Router.extend({
// nothing here yet
}, {
instance: null,
getInstance: function () {
App.instance = App.instance || new App
return App.instance
}
})
module.exports = App
Some module using the router:
var IndexView = require('./views/IndexView')
var App = require('../../AppModule/js/main')
var _ = require('underscore')
var Backbone = require('backbone')
Backbone.$ = $
function IndexModule(opts) {
this.app = App.getInstance()
this.indexView = new IndexView({
view: opts.view
})
this.init = function() {
this.app.route('', _.bind(this.indexRoute, this))
this.app.route('testroute#:id', function(id) {
console.log(id)
})
this.app.on('route', _.bind(this.routeChanged, this))
}
this.indexRoute = function() {
this.indexView.render()
}
this.routeChanged = function() {
console.log(arguments)
}
}
module.exports = IndexModule
Somewhere form $(document).ready():
var indexModule = new IndexModule({
view: "someview.html"
})
indexModule.init()
Backbone.history.start({root: '/'});
If I go to testroute/2 I get this result:
["", ["2"]]
Shouldn't the route (testroute) come up in an argument as a string??
Thanks..

Just realized that you have setup the route as testroute/:id. Since id is the only variable here, only that is passed as an argument to the handler. You should try something like :routeName/:id This way, both the route name and id will be passed as arguments.

Related

How to mock $window.Notification

I am still learning the ropes when it comes to unit testing with angular. I have an angular service that I use to create HTML5 notifications. Code is similar to the following:
(function() {
'use strict';
angular
.module('blah')
.factory('OffPageNotification', offPageNotificationFactory);
function offPageNotificationFactory($window) {
//Request permission for HTML5 notifications as soon as we can
if($window.Notification && $window.Notification.permission !== 'denied') {
$window.Notification.requestPermission(function (status) { });
}
function OffPageNotification () {
var self = Object.create(OffPageNotification.prototype);
self.visibleNotification = null;
return self;
}
OffPageNotification.prototype.startNotification = function (options) {
var self = this;
self.options = options;
if(self.options.showHtml5Notification && (!self.options.onlyShowIfPageIsHidden || $window.document.hidden)) {
if($window.Notification && $window.Notification.permission !== 'denied') {
self.visibleNotification = new $window.Notification('Notification', {
body: self.options.notificationText,
icon: self.options.notificationIcon
});
}
}
};
.
.
.
return new OffPageNotification();
}
})();
I am attempting to write unit tests for this but am unsure how to mock $window.Notification so it can be used as both a constructor...
self.visibleNotification = new $window.Notification(....)
and also contain properties
if($window.Notification && $window.Notification.permission !== 'denied')
and methods....
$window.Notification.requestPermission(
An example of something I have tried is:
describe('startNotification', function() {
beforeEach(function() {
var mockNotification = function (title, options) {
this.title = title;
this.options = options;
this.requestPermission = sinon.stub();
};
mockNotification.prototype.permission = 'granted';
mockWindow = {
Notification: new mockNotification('blah', {}),
document: {hidden: true}
};
inject(function (_OffPageNotification_) {
OffPageNotification = _OffPageNotification_;
});
});
it('should display a html5 notification if the relevant value is true in the options, and permission has been granted', function(){
var options = {
showHtml5Notification: true,
onlyShowIfPageIsHidden: true
};
OffPageNotification.startNotification(options);
});
});
I get an error saying '$window.Notification is not a constructor' with this setup and I understand why (I am passing in an instantiated version of the mockNotification). But if I set mockWindow.Notification = mockNotification then I get an error when it calls requestPermission since this is undefined.
Any help is appreciated
Notification should be a constructor. And it should have static properties and methods.
All of the relevant properties of mockNotification are instance properties, while they should be static:
function MockNotification() {}
MockNotification.title = title;
MockNotification.options = options;
MockNotification.requestPermission = sinon.stub();
mockWindow = {
Notification: MockNotification,
document: {hidden: true}
};

Two $firebaseArrays on one page & one ctrl

I would like to use two different $firebaseArrays on one view with one controller. But only one of them works and the other only works if i put him in his own controller.
from my factory file:
.factory("AlphaFactory", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref('alpha/');
return $firebaseArray(ref);
}
])
.factory("BetaFactory", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref('beta/');
return $firebaseArray(ref);
}
])
and my controller:
.controller('DemoCtrl', function($scope, AlphaFactory, BetaFactory) {
$scope.alphaJobs = AlphaFactory;
$scope.addalphaJob = function() {
$scope.alphaJobs.$add({
Testentry: $scope.loremipsum,
timestamp: Date()
});
$scope.alphaJob = "";
};
$scope.betaJobs = BetaFactory;
$scope.addbetaJob = function() {
$scope.betaJobs.$add({
Testentry2: $scope.dolorest,
timestamp: Date()
});
$scope.betaJob = "";
};
)}
Are you sure it is not a simple matter of a promise has not finished?
var alphaJobs = AlphaFactory;
alphaJobs.$loaded().then(function() {
// Do something with data if needed
$scope.alphaJobs = alphaJobs;
});
var betaJobs = BetaFactory;
betaJobs.$loaded().then(function() {
// Do something with data if needed
$scope.betaJobs = betaJobs;
});

Marionette router does nothing even if backbone history is started

I work on an app with Marionette and browserify.
When I start my application, any routes is matched.
My app code :
app.js :
var App = require('./App/SaApp.js');
App.start();
SaApp.js :
var Mn = require('backbone.marionette');
var DashboardRouter = require('./modules/DashboardRouter.js');
var DashboardController = require('./modules/DashboardController.js');
var DashboardMainView = require('./modules/Dashboard/layout/DashboardMainView.js');
var Backbone = require('backbone');
var app = new Mn.Application();
app.addInitializer(function () {
console.log ('addInitializer');
var dashboardMainView = new DashboardMainView({
el: '#main-view'
});
var dashboardRouter = new DashboardRouter({
controller: new DashboardController({
mainView: dashboardMainView
})
});
dashboardMainView.render();
});
app.on("start", function(){
Backbone.history.start();
});
module.exports = app;
DashBoardRouter.js file :
var Mn = require('backbone.marionette');
var DashboardRouter = Mn.AppRouter.extend({
initialize : function () {
console.log ('router')
},
appRoutes: {
"": "indexAction",
"databases/:name":'databaseAction'
},
});
module.exports = DashboardRouter;
And DashBoardController.js :
var Mn = require('backbone.marionette');
var _ = require('underscore');
var LeftPanelView = require('./Dashboard/views/LeftPanelView.js');
var MainPanelView = require('./Dashboard/views/MainPanelView.js');
var TablePanelView = require('./TableBoard/views/TablePanelView.js');
var TableCollection = require('./Dashboard/collection/DatabaseCollection.js');
var DatabaseModel = require('./Dashboard/model/DatabaseModel.js');
var DashboardController = Mn.Controller.extend({
initialize : function(options) {
this.mainView = options.mainView;
console.log ('init')
},
indexAction : function() {
var collection = new TableCollection();
console.log ('fetch')
collection.fetch().done(_.bind(function () {
this.mainView.leftPanel.show(new LeftPanelView({
tableCollection : collection
}));
this.mainView.mainPanel.show(new MainPanelView());
}, this));
},
databaseAction : function (tableName) {
var databaseModel = new DatabaseModel();
databaseModel.urlRoot = 'databases/'+tableName;
databaseModel.fetch().done(_.bind(function() {
var tablePanelViews = new TablePanelView({
model : databaseModel
});
this.mainView.mainPanel.show(tablePanelViews);
}, this));
},
onRoute : function() {
var collection = new TableCollection();
console.log ('on route')
collection.fetch().done(_.bind(function () {
this.mainView.leftPanel.show(new LeftPanelView({
tableCollection : collection
}));
}, this));
}
});
module.exports = DashboardController;
When I start my app, all console.log are displayed in good order but indexAction is never run.
When I build code by browserify command, all is correct.
Thank you for your help.
I'm not sure, but i think you are just missing the controller (controller: myController) in the Approuter. As marionette documentation says:
var MyRouter = new Marionette.AppRouter({
controller: myController,
appRoutes: {
"foo": "doFoo",
"bar/:id": "doBar"
}
});
I know i shoudn't post links on stackoverflow, but anyway it might help you. I have done recently a basic setup with marionette and require. There you might can understand better why is not working in your case:
https://github.com/LucaMele/skeleton-marionette-require-gulp
You should find the info in the file:
https://github.com/LucaMele/skeleton-marionette-require-gulp/blob/master/modules/app.js
row 37 ( controller:new App.Router.Controller() )

How to refactor some function inside a Protractor Page Object or spec file into a separate file?

For PageObject, we have buttons/links in the bottom of the page. We have to scroll down to the bottom so as to click.
var ContactAddPage = function() {
...
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
scrollToBottom();
this.btnAdd.click();
};
function scrollToBottom(){
browser.executeScript('window.scrollTo(0,10000);');
}
};
Since scrollToBottom() will be used in many other page object, I assume we could refactor it out into a separator file and somehow require() it into the page object. But how to do that?
As for the spec files, there would be common functions like log in with different persona in beforeAll() and log out in afterAll(). And checking url is also common to each page:
function loginAs(role) {
var loginPage = new LoginPage();
loginPage.login(role);
}
function logOut() {
var lnkLogOff = element(by.linkText('Log off'));
if (element(by.linkText('Log off')).isPresent()) lnkLogOff.click();
}
function expectRouteToBe(expectUrl) {
expect(browser.getCurrentUrl()).toBe(browser.baseUrl + expectUrl);
}
The same question here: what is the correct way to move them out and require() them back?
You can extend modules by using exports and prototype inheritances... then you can have methods available in any or all of your pages. Eg. something like:
basePage.js:
var BasePage = function() {
this.loginAs = function(role) {
this.login(role);
};
...
};
module.exports = new BasePage();
And then extend any number of pages thusly:
var basePage = require('./basePage.js');
var MainPage = function() {
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
this.scrollToBottom();
this.btnAdd.click();
};
this.scrollToBottom = function() {
browser.executeScript('window.scrollTo(0,10000);');
};
};
MainPage.prototype = basePage;
module.exports = new MainPage();
And then in your spec, you can:
var mainPage = require("../pages/mainPage.js");
...
it('should login and click add', function() {
mainPage.loginAs(role);
mainPage.btnAdd.click()
...
});
Hope that helps...
We've had something similar in our set of protractor e2e tests. We've created a set of helper libraries for the reusable functionality used in tests.
Example:
create helpers.js file:
var Helpers = function() {
this.scrollToBottom = function () {
browser.executeScript('window.scrollTo(0,10000);');
};
};
module.exports = new Helpers();
require and use it in your Page Object:
var helpers = require("./helpers.js");
var ContactAddPage = function() {
this.btnAdd = element(by.id('btnAdd'));
this.add = function() {
helpers.scrollToBottom();
this.btnAdd.click();
};
};
module.exports = ContactAddPage;

Marrionette.js NoMethodError: Method 'xxx' was not found on the controller but it is there

I'm getting the error: NoMethodError: Method 'go_start' was not found on the controller
The function go_start is only in two places in my project (I searched):
StartRouter.js
define(['marionette'], function(Marionette) {
'use strict';
var StartRouter = {};
StartRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
"start": "go_start"
}
});
return StartRouter;
});
And StartController.js
define(['underscore', 'marionette', 'app/vent',
'text!templates/StartLayout.html', 'views/InspectorStartView','views/InspectorStartView'],
function(_, Marionette, vent, layout, InspectorStartView, PlayerStartView) {
'use strict';
// public module API
var Controller = {};
// private
var Layout = Marionette.Layout.extend({
template: _.template(layout),
regions: {
inspector: "#inspector_choice",
player: "#player_choice"
}
});
// private
var _initializeLayout = function() {
console.log('initializeLayout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("layout:rendered");
});
vent.trigger('app:show', Controller.layout);
};
// controller attach a sub view/ search View
vent.on("layout:rendered", function() {
console.log('layout:rendered =>StartController');
// render views for the existing HTML in the template, and attach it to the layout (i.e. don't double render)
var inspectorStartView = new InspectorStartView();
Controller.layout.inspector.attachView(inspectorStartView);
var playerStartView = new PlayerStartView();
Controller.layout.player.attachView(playerStartView);
});
// controller show inspector in the layout / subview
vent.on('show:inspector', function(inspectorStartView) {
// console.log('show books event');
Controller.layout.inspector.show(inspectorStartView);
});
// controller show inspector in the layout / subview
vent.on('show:player', function(playerStartView) {
// console.log('show books event');
Controller.layout.inspector.show(playerStartView);
});
// public API
Controller.go_start = function(term) { **//<-- function go_start**
_initializeLayout();
//vent.trigger("search:term", term);
};
return Controller;
The really strange part is that Crome shows the error happening on line 77 of App.js which is:
app.addInitializer(function(options) {
// configure for loading templates stored externally...
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function(templateId) {
// Marionette expects "templateId" to be the ID of a DOM element.
// But with RequireJS, templateId is actually the full text of the template.
var template = templateId;
// Make sure we have a template before trying to compile it
if (!template || template.length === 0) {
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
return template;
};
// Connect controllers to its router via options
// init router's router/controller
new options.router.Router({
controller: options.homeController
});
// init loginRouter's router/controller
new options.loginRouter.Router({
controller: options.loginController
});
// init helpRouter's router/controller
new options.helpRouter.Router({
controller: options.helpController //<-- Line 77
});
// init startRouter's router/controller
new options.startRouter.Router({
controller: options.startController
});
// init inspectorRouter's router/controller
new options.inspectorController.Router({
controller: options.inspectorController
});
// init playerRouter's router/controller
new options.playerRouter.Router({
controller: options.playerController
});
});
// export the app
return app;
});
The Help router and controller:
// HelpRouter.js
define(['marionette'], function(Marionette) {
'use strict';
var HelpRouter = {};
HelpRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
"help": "go_help"
}
});
return HelpRouter;
});
<!-- routes/HelpController.js -->
define(['underscore', 'marionette', 'app/vent', 'text!templates/HelpLayout.html'],
function (_, Marionette, vent, layout) {
'use strict';
// public module API
var Controller = {};
// private
var Layout = Marionette.Layout.extend({
template: _.template(layout),
regions: {
faq: "#"
}
});
// private
var _initializeLayout = function () {
console.log('initializeLayout...');
Controller.layout = new Layout();
Controller.layout.on("show", function () {
vent.trigger("layout:rendered");
});
vent.trigger('app:show', Controller.layout);
};
// public API
Controller.go_help = function () {
_initializeLayout();
};
return Controller;
});
Anyone see what I'm doing wrong?
Here is something that I didn't think was supposed to happen:
// Includes Desktop Specific JavaScript files here (or inside of your Desktop router)
require(["app/App",
"routers/HomeController", "routers/StartController", "routers/LoginController",
"routers/InspectorController", "routers/PlayerController", "routers/HelpController",
"routers/DesktopRouter", "routers/LoginRouter", "routers/StartRouter",
"routers/InspectorController", "routers/PlayerController", "routers/HelpRouter" ],
function(App,
HomeController, StartController, LoginController, InspectorController, PlayerController, HelpController,
DesktopRouter, LoginRouter, HelpRouter, StartRouter, InspectorRouter, PlayerRouter) {
var options = {
homeController: HomeController,
startController: StartController,
loginController: LoginController,
helpController: HelpController,
inspectorController: InspectorController,
playerController: PlayerController,
router: DesktopRouter,
startRouter: StartRouter,
loginRouter: LoginRouter,
helpRouter: HelpRouter,
inspectorRouter: InspectorRouter,
playerRouter: PlayerRouter
};
App.start(options);
});
So I am requiring all my routers and controllers but when I run that code in the debugger I have some items that are undefined:
options: Object
helpController: Object
helpRouter: Object
homeController: Object
go_home: function () {
__proto__: Object
inspectorController: undefined
inspectorRouter: undefined
loginController: Object
loginRouter: Object
playerController: undefined
playerRouter: Object
router: Object
Router: function (){ parent.apply(this, arguments); }
__proto__: Object
startController: Object
go_start: function (term) {
__proto__: Object
startRouter: undefined
__proto__: Object
HomeController: Object
StartController: Object
StartRouter: undefined
DesktopRouter: Object
InspectorController: undefined
Why are some undefined? The StartRouter and StartController have the go_start function. The HelpRouter/Controller doesn't and shouldn't have go_start but it is throwing an error
All suggestions welcome.
Andrew
Looks like your RequireJS imports are out of order. The HelpRouter variable maps to the "routers/StartRouter" module, and all subsequent modules are off-by-one.
The AMD import format can easily lead to this types of simple errors that can take ages to debug, because that's somehow the place you never think to look. That's why I prefer the simplified CommonJS wrapper syntax provided by RequireJS:
define(function(require) {
var HomeController = require('routers/HomeController'),
StartController = require('routers/StartController'),
//etc...
});
For those of you just tuning in here is the working solution as proposed by fencliff.
// Includes Desktop Specific JavaScript files here (or inside of your Desktop router)
define(function(require) {
var App = require("app/App"),
// Routers with its controller
DesktopRouter = require("routers/DesktopRouter"),
HomeController = require("routers/HomeController"),
StartRouter = require("routers/StartRouter"),
StartController = require("routers/StartController"),
LoginRouter = require("routers/LoginRouter"),
LoginController = require("routers/LoginController"),
InspectorRouter = require("routers/InspectorRouter"),
InspectorController = require("routers/InspectorController"),
PlayerRouter = require("routers/PlayerRouter"),
PlayerController = require("routers/PlayerController"),
HelpRouter = require("routers/HelpRouter"),
HelpController = require("routers/HelpController");
var options = {
homeController: HomeController,
router: DesktopRouter,
startController: StartController,
startRouter: StartRouter,
loginController: LoginController,
loginRouter: LoginRouter,
inspectorController: InspectorController,
inspectorRouter: InspectorRouter,
playerController: PlayerController,
playerRouter: PlayerRouter,
helpController: HelpController,
helpRouter: HelpRouter
}
App.start(options);
return function() {};
});
I was a bit uncertain about returning an empty function but that is the only example I saw and it works. :-D

Resources