This is driving me crazy right now. I cannot get a simple router to work...
jQuery ->
class MyRouter extends Backbone.Router
routes:
"" :"index"
"/list" :"showList"
"/item/:id" :"showItem"
index: =>
alert "index"
showList: =>
alert "get the lists"
showItem: (id)=>
alert "the item #{id}"
#app = window ? {}
#app = window.app ? {}
#app.myRouter = MyRouter
Backbone.history.start()
I always get this error:
index.js:50Uncaught TypeError: Cannot call method 'start' of undefined
and I saw this: Cannot call 'start' of undefined when starting backbone.js history.
but it did not help =(...
I am sure this is an easy one but I am some sort of stuck here ...
please help...
You didn't create an instance of a Backbone.Router, so Backbone.history.start() will fail.
#app.myRouter = new MyRouter()
The link you posted tells you exactly what the problem is:
TypeError: Cannot call method 'start' of undefined**
Hmm, for some reason Backbone.history is undefined, and so there is no start method on it. It turns out that Backbone.js creates an
instance of Backbone.History (upper case ‘H’) called Backbone.history
(lower case ‘h’) once a controller has been created that has at
least one route specified on it. This makes sense, as history
management is only required if there are routes to respond to.
Related
I'm trying to listen to a MessageEvent sent with postMessage in my Angular 2 component.
My first attempt was simply doing:
window.addEventListener("message", this.handlePostMessage.bind(this));
And then in ngOnDestroy:
window.removeEventListener("message", this.handlePostMessage.bind(this));
However this didn't work as expected. If I navigated to another route and back, there would be two event listeners registered.
So instead I've been trying to decorate the method with HostListener, but I can't get this working when using prerendering (Angular Universal with .NET Core using the asp-prerender-module).
#HostListener('window:message', ['$event'])
private handlePostMessage(msg: MessageEvent) {
...
}
That gives me the following error on page load:
Exception: Call to Node module failed with error: Prerendering failed because of error: ReferenceError: MessageEvent is not defined
Is there a workaround for this?
You're getting this error because MessageEvent is not defined. You must import whatever file defines this.
My #HostListeners look like this:
#HostListener("window:savePDF", ["$event"]) savePDF(event) {
this.savePDFButtonPushed();
}
and you can read more about them here:
https://angular.io/guide/attribute-directives
However, I'm currently experiencing the same issue you are -- that if I navigate to another route and back, I now receive two events. And that is using #HostListener. :-( However I haven't upgraded Angular in a while (currently using 4.4.6), so maybe they've fixed it since that release.
**Edit: Just upgraded to Angular 5.1.0. The 'duplicate events' #HostListener issue remains. :-(
Edit #2: I tried also using window.addEventListener like you tried, and also had the same issue, despite using window.removeEventListener in ngOnDestroy().
This lead me to dig a little deeper, where I found some code I had added to listen to messages from a child iFrame. Any chance you have something similar in your code?
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen to messages from child window ("unsign" and "savePDF") and pass those along as events to Angular can pick them up in its context
eventer(messageEvent,function(e) {
window.dispatchEvent( new Event( e.data ) );
},false);
This had been in my page's constructor. I protected it so it only executed the first time the page was constructor, and now all is well.
I have multiple routers in my app, in general way it looks like this:
// Start backbone.js
if (!Backbone.History.started) {
Backbone.history.start({pushState: true, hashChange: false});
}
// Perform some RPC requests ...
// Depending on user role, received from the server should be created suitable router:
var router;
if (typeof app.user.role === 'manager') {
router = new routers.manager();
} else {
router = new routers.guest();
}
Problem is that after page is loaded and script is executed routers do not do. anything. They do not load route for current url automatically. So, i had to fix it this way (i am not sure that it is a right way):
routers.guest.initialize = routers.manager.initialize = function() {
var defaultRoute = 'default';
if (typeof this.routes[Backbone.history.fragment] !== 'undefined') {
this[this.routes[Backbone.history.fragment]]();
} else {
this.navigate(defaultRoute, true);
}
};
It is working fine, except one bug: when i use route with params, for example /reset-password-confirm/:code - it is unable to find in in routes property. I could write some more code to fix it, but i suppose that i am doing something wrong, if i have to write such things - as i understand router should handle routes just after it was created.
So, questions:
Why my router(s) does not handle routes for current url after it is being created? Perhaps i need to start backbone history later? (but this bug will happen again later then)
How it is possible to make routes with params like /user/:id work there?
Perhaps it is bad idea to re-create routers? Perhaps it is better to create all of them one time?
P.S. I've tried to create both routers and keep them, also i've trie to call backbone history start method after all routers were created.. but this didn't help :/
Assuming you route is declared as the following:
routes : {
'/user/:id' : 'user'
}
Your initialize code is not working because when you initialize your router with a url such as: /user/1234. Backbone.history.fragment will be /user/1234 (not /user/:id). Since the this.routes object doesn't have a key of /user/1234, your else clause calls the default route.
If you first instantiate your router then call Backbone.history.start(), you will be able to remove your router initialize code. When you navigate to a url as /user/1234 your router will match the /user/:id route and call the user function.
The following should work for you without adding your initialize code:
var router = (app.user.role === 'manager') ? new routers.manager()
: new routers.guest();
Backbone.history.start({pushState: true, hashChange: false});
Looking at the code, seems like you're starting the backbone history before initializing any routes. That's most likely not goning to work.
The correct way of doing this type of seperation is by creating all the routes based on the role received from the server and then start the backbone history. Here's an SO thread that talks about it with code samples as well : How to protect routes for different user groups
I try to study and use Backbone/Marionette in my project. Now I stuck with Router navigation which work not as I though it should.
class MyApp.Router extends Marionette.AppRouter
appRoutes :
'info/:place/(:what)' : 'places_page'
MyApp.Controller = ->
places_page: (place,what)->
console.log 'Triggered places_page'
MyApp.addInitializer( ->
controller = new MyApp.Controller()
new MyApp.Router
controller: controller
Backbone.history.start( pushState: false )
)
MyApp.vent.on('do:search', ->
console.log 'triggered do:search'
place = 'Moscow'
what = 'Пицца'
info_model.set place: place, item:what
new_url = 'info/'+where+'/'+what
if new_url != decodeURIComponent(Backbone.history.fragment)
Backbone.history.navigate(new_url, {trigger: false})
On initial load of site.com/#info/Budapest/Vine page or reload it, I get Triggered places_page message as I expect.
But when I fire do:search event which update url to site.com/#info/Moscow/Пицца, I get Triggered places_page again! So it reload all my views from scratch instead of just change url and re-render one model.
What I can do wrong here?
Update 2:
Found strange thing. If I use latin letters in new url, everything work like it should.
But if I use cyrillic in new url path, it will trigger route function.
Backbone: 1.0, Marionette:v1.0.3, jquery: 1.9.1
Mystery solved!
That happens because of non-latin symbols in url.
Correct code:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
if new_url != Backbone.history.fragment
Backbone.history.navigate(new_url, {trigger: false})
Because Backboune.navigate don't execute navigate if url didn't change and trigger is false by default, I can write it simple like that:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
Backbone.history.navigate(new_url)
Backbone.history takes an object when starting. Try this syntax in CoffeeScript:
Backbone.history.start
pushState: false
In addition, pushState is false by default, so you can just have Backbone.history.start()
Does this solve your issue?
Proper URL encoding is required. I couldn't find this in the docs of Backbone related to the router functionality.
I had the same issue, alas with spaces in the query part, i.e.:
#app/terms?filter=java ee
Together with the encodeURIComponent solution as described in your answer, I also found the following lines of comments in backbone.js (1.0.0), pertaining to the navigate function:
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
+im playing arround with backbone and coffeescript, trying to get the router up and running. executing the following code, the init function works, but when browsing localhost/#world/3 nothing happens, although it should log something....
App =
start: ->
new App.TestRouter
Backbone.history.start
App.TestRouter = Backbone.Router.extend
routes:
"world/:id": "testView"
initialize: ->
new App.TestView
console.log "Router init"
testView: (id) ->
console.log "testing! #{id}"
any advice here? am i blind?
Backbone.history.start() is a function, so you need the () to execute it. Otherwise, you're just getting a reference to the function itself.
App =
start: ->
new App.TestRouter
Backbone.history.start()
See this live jsFiddle:
http://jsfiddle.net/edwardmsmith/6pNLv/8/
I have a success callback for a model fetch, and everything works fine on chrome, but on firefox the event does not fire. The request gets completed though, according to the console.
Code Sample:
Parent Class Function:
DownloadUserPromotions: (callback) ->
self = #
#model = new app.models.client({ id: JSON.parse($.cookie('jsondata')).id })
lm = ->
console.log "4"
window.USER = self.model
if typeof callback == 'function' then callback.call()
#model.fetch
success: lm
data:
relationships: 'client_promotions'
console.log "3"
View Function:
render: ->
self = #
self.ReadUserInfo()
console.log "1"
renderTemplate = ->
console.log "5"
#Below Issue is wierd.......#TODO
#USER = JSON.parse(JSON.stringify(#USER))
$(self.el).html clientsPromotionsTemplate
promos: USER.client_promotions
$('.spinner#load').hide()
self.FadeIn()
$('.spinner#load').show()
console.log "2"
#DownloadUserPromotions renderTemplate
#
Side Note: The marked TODO is a different issue. Bonus thank yous for helping me figure out why JSON works only in that convoluted manner.
First of all you need to get your head the difference between => and -> for defining functions in coffeescript.
=> binds this inside the function to what this was when the function was defined.
-> binds this inside the function to what this was when the function is called
self = this
is code smell sign in coffeescript that you don't understand how to use the above correctly as you are trying to capture this in order to solve the problem that => solves.
You render function re-written could be
render: ->
#ReadUserInfo()
console.log "1"
renderTemplate = =>
console.log "5"
#Below Issue is wierd.......#TODO
#USER = JSON.parse(JSON.stringify(#USER))
$(#.el).html clientsPromotionsTemplate
promos: USER.client_promotions
$('.spinner#load').hide()
#.FadeIn()
$('.spinner#load').show()
console.log "2"
#DownloadUserPromotions renderTemplate
#
And probably will fix some of your wierd issues. Previously I'm pretty sure
that the line you had
#USER = JSON.parse(JSON.stringify(#USER))
would never have worked as you expected as #USER would be expanded to this.USER and
whatever this might be when the callback is invoked can be pretty random depending
on your framework and browser.
Since my success callback was part of a JSON object, firefox couldn't find whatever default it was looking for and therefore didn't fire anything. Specifying dataType:'json' when fetching solves this problem, because firefox knows where to look for the success call back.
Chrome is apparently reads my mind...