I have the following backbone.js controller:
App.Controllers.PlanMembers = Backbone.Controller.extend({
routes: {
"message/:messageType": "sendMessage",
"": "index"
},
sendMessage: function (messageType) {
alert(messageType);
},
index: function () {
alert('should not get here');
}
});
I want the index to action to be executed when the page loads for the first time which it does, I also have another route which is sent to the sendMessage action and requests are routed fine from links like the one below:
<a class="sms" href="#message/sms" ><img src="/img/buttons/transmit_blue.gif" /></a>
The problem is that after it executes the sendMessage action, it then goes onto fire the index action again which is not what I require.
Can anyone tell me how to ensure that only the sendMessage Route is fired?
It turns out this is a known issue
This fixed the problem.
I honestly thought I was going insane!
The code that you have added works for me, but I don't know if there is something else in your code that could be causing this. Are you perhaps routing to this action from a view based upon the receipt of a DOM event? If so, the absence of a preventDefault statement can sometimes cause this sort of double-rendering (routing) behavior. So perhaps add some additional context/detail to your question.
Related
Recently I've stumbled upon a very strange code in production that is seemingly using the fact that under some conditions Angular may fire the $locationChangeStart event upon the initial page load. Moreover the next parameter value will be equal to the current value. That seems very odd to me.
I didn't find any relevant documentation for that but here is the fiddle that shows such a situation http://jsfiddle.net/tJSPt/327/
Probably the only difference is that in production we are using the manual Angular bootstrap.
Can anyone explain or point to the trustful sources of information on why is that event triggered upon the page load? Is that something we have to expect or that is just the particularity of the current Angular implementation or our way of using it?
I have experienced this recently but the reason it happened was because I'm using ui-router and the controllerAs syntax. Perhaps you are too?
I stumbled upon this link that helped me out: History should not be changed until after route resolvers have completed
I listened to the $locationChangeStart broadcast but it hit the breakpoint when I entered the state instead of when exciting.
I fixed mine by doing the following:
I listened to $stateChangeStart instead.
I had to move the code above var vm = this;
Here's my code look like after:
// ...
$scope.$on('$stateChangeStart', function (event) {
if (vm.myForm!= null && vm.myForm.$dirty) {
if (!confirm("Are you sure you want to leave this page?")) {
event.preventDefault();
}
}
});
var vm = this;
// vm.xxx = xxxx; .etc ...
I have a site that has a user page. On that page, there are several links that let you explore the user's profile. I'd like to make it so that, when one of those links is clicked on, the url changes, but the top third of the page containing the user's banner doesn't reload.
I'm using Backbone.js
I have a feeling that I'm in one of those situation where I have such a poor understanding of the problem I'm dealing with that I'm asking the wrong question, so please let me know if that appears to be the case
My mistake was assuming that there was a special, built-in way of doing this in backbone. There isn't.
Simply running the following line of code
window.history.pushState('object or string', 'Title', '/new-url');
will cause your browser's URL to change without reloading the page. You can open up the javascript console in your browser right now and try it with this page. This article explains how it works in more detail (as noted in this SO post).
Now I've just bound the following event to the document object (I'm running a single page site):
bindEvents: () ->
$(document).on('click', 'a', #pushstateClick)
pushstateClick: (e) ->
href = e.target.href || $(e.target).parents('a')[0].href
if MyApp.isOutsideLink(href) == false
if e.metaKey
#don't do anything if the user is holding down ctrl or cmd;
#let the link open up in a new tab
else
e.preventDefault()
window.history.pushState('', '', href);
Backbone.history.checkUrl()
See this post for more info.
Note that you CAN pass the option pushstate: true to your call to Backbone.history.start(), but this merely makes it so that navigating directly to a certain page (e.g. example.com/exampleuser/followers) will trigger a backbone route rather than simply leading to nowhere.
Routers are your friend in this situation. Basically, create a router that has several different routes. Your routes will call different views. These views will just affect the portions of the page that you define. I'm not sure if this video will help, but it may give you some idea of how routers interact with the page: http://www.youtube.com/watch?v=T4iPnh-qago
Here's a rudimentary example:
myapp.Router = Backbone.Router.extend({
routes: {
'link1': 'dosomething1',
'link2': 'dosomething2',
'link3': 'dosomething3'
},
dosomething1: function() {
new myapp.MyView();
},
dosomething2: function() {
new myapp.MyView2();
},
dosomething3: function() {
new myapp.MyView3();
}
});
Then your url will look like this: www.mydomain.com/#link1.
Also, because <a href=''></a> tags will automatically call a page refresh, make sure you are calling .preventDefault(); on them if you don't want the page to refresh.
I am doing form validation in a function when a user wants to preview an invoice which is called by a route:
routes: {
"new" : "newInvoice",
"new/:assignmentid" : "newInvoiceAssignment",
"edit/:invoiceid" : "editInvoice",
"preview/:invoiceid" : "previewInvoice",
"preview" : "preview",
"delete/:invoiceid" : "deleteInvoiceModal",
"whyCant" : "whyCant",
"whatsThis" : "whatsThis"
},
And here is my two buttons (actually, a button and an href) on the form:
<div class="span8 alignRight">
<button id="saveInvoiceDraft" type="submit" class="btn btn-warning">Save Draft</button>
<a id="previewInvoice" class="btn btn-primary">Preview & Send</a>
</div>
When this invoice is created, the URL for the tag is set with:
var url = '#preview';
$('#previewInvoice').attr('href',url);
And finally, when I click on the "Preview & Send" button, the previewInvoice(invoiceid) function below runs, properly catches the one form field missing and displays the error message. At that point even if I populate the form field, that button is dead and no longer responds. However, the "Save Draft" button works perfectly and mimic's the same code as in the previewInvoice() function.
I know there is probably a better way to do this, but I was following that way it was done in another section of the app I inherited. Actually, as I am typeing this I am wondering since the sendDraft() function works and its a button and the previewInvoice() function does not, the fact that it is a href might have something to do with it.
function previewInvoice(invoiceid) {
var invoice = new Invoice({"invoiceid": invoiceid});
invoice.set({"invoiceid": invoiceid,"invoicestatus": "draft"});
formGetter(invoice);
validateInvoiceForm(invoice);
if (window.errors.length == 0) {
//business logic here
if (window.panel == undefined) {
// business logic here
}
else {
//save business logic here
}
}
else {
showInvoiceErrors();
}
}
Any ideas why the button no longer responds? I am not seeing any error's in the console. I added a consol.log inside the function to display the value of a different form element, and it displays the first time in the console, but if I change the data and click the button again, that log does not update, which to me is another clue that it is just not firing.
Backbone.History listens to hashchange events to trigger navigation to routes. When you first click the previewInvoice button, the URL hash fragment is set to #preview, and the matching route is triggered.
When you click the same button the second time, the hash doesn't actually change, and therefore the router doesn't catch it.
I'm having a hard time recommending a good solution to this problem. Normally I would recommend catching the click event and calling router.navigate("preview", {trigger:true}); manually. However, based on your code sample it looks like your application is built around the Router, and there isn't a View layer for DOM event handling as you would expect in most Backbone applications.
On the Router level this is a bit trickier to solve. You could use router.navigate to set a dummy hash after the preview route has been executed. This would cause the link to trigger a hashchange on the second time as well. Unfortunately this would mean that the preview page would not be bookmarkable, and since you're not using pushState, would leave an extraneous history entry.
I'm afraid this issue will have to either be solved with a hacky fix (as outlined above) or a major refactoring.
I have this same problem, my solution is to put in a handler that does a fake "in-between" route that is hidden from history, so that Backbone.history will register the navigation as a change and trigger the action.
Put a class on links that you need the route action to trigger regardless if the URL is the same:
do it
In your Backbone view, put in an event handler:
events: {
"click .js-ensureNav": "_ensureNav",
},
Actual handler:
_ensureNav: function (event) {
var route_name = $(event.target).attr('href').slice(1);
Backbone.history.navigate("fake");
Backbone.history.navigate(route_name, {trigger: true, replace: true});
},
Instead of
var url = '#preview';
$('#previewInvoice').attr('href',url);
Try this
var date = new Date();
var url = '#preview?date:' + date ;
$('#previewInvoice').attr('href',url);
So Every time new request will be generated and this will solve your problem.
I am using backbone.js to create a single page app. I am new to backbone, so please forgive any wrong semantics.
My Problem is when rendering the views.
Initially, I have a javascript in my index.html that executes the some dom manipulation(image slider).
The JS is wrapped in $(window).load() so all is fine on initiation.
The code obviously doesn't execute unless the page is loaded from url. the code will not run from backbone views or router. So the page loads without the dom manipulation.
I have tried to insert my code into the render and initialize function in the view, but to no avail. Should I add this code to the router? that seems to be a bit of a hack.
Where should I include the "dom ready" code?
and / or is there a better way to manage views and their dom elements on load in backbone?
the code:
home.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
this.startOrbits();
return this;
},
startOrbits:function(){
$(window).load(function() {
$('#orbit-main').orbit({ fluid: '16x6', swipe:true });
$('#orbit-news').orbit({ fluid: '8x6', bullets: true, directionalNav:false, captions:true, advanceSpeed: 9000});
});
},
});
But when I go to another view, then back, the code obviously doesn't
excite
I'm not quite sure what that means. Leaving the "excite" part aside, you don't "go to" views; views are just ways of adding elements to the page, or adding logic to existing elements.
If I had to guess though, I'd imagine that you're using the Backbone router to move between virtual "pages" (and you use views to make those pages). If that's the case, you need to look at the Backbone router events:
http://documentcloud.github.com/backbone/#Router
http://documentcloud.github.com/backbone/#FAQ-events
Specifically, I think you want to bind an event handler (on your router) to "route:nameOfYourRoute", or just :route" (if you want to trigger your logic on every virtual page load).
Hope that helps, and if my guesses are wrong please edit your question to clarify.
I was able to find a solution.
After commenting out the if statement in my router function, things went smoothly.
home: function () {
// if (!this.homeView) {
this.homeView = new HomeView();
// }
$('#main-content').html(this.homeView.el);
this.homeView.startOrbits();
this.headerView.selectMenuItem('home');
},
I do realize that this means I create a new view on every rout trigger.
Please feel free to offer more optimal solutions.
My single page application loads a home page and I want to display a series of ideas. Each of the ideas is displayed in an animated flash container, with animations displayed to cycle between the ideas.
Ideas are loaded using $http:
$scope.flash = new FlashInterface scope:$scope,location:$location
$http.get("/competition.json")
.success (data) ->
$scope.flash._init data
However, to benefit from history navigation and UX I wish to update the address bar to display the correct url for each idea using $location:
$location.path "/i/#{idea.code}"
$scope.$apply()
I am calling $apply here because this event comes from outwith the AngularJS context ie Flash. I would like for the current controller/view to remain and for the view to not reload. This is very bad because reloading the view results in the whole flash object being thrown away and the preloader cycle beginning again.
I've tried listening for $routeChangeStart to do a preventDefault:
$scope.$on "$routeChangeStart", (ev,next,current) ->
ev.preventDefault()
$scope.$on "$routeChangeSuccess", (ev,current) ->
ev.preventDefault()
but to no avail. The whole thing would be hunky dory if I could figure out a way of overriding the view reload when I change the $location.path.
I'm still very much feeling my way around AngularJS so I'd be glad of any pointers on how to structure the app to achieve my goal!
Instead of updating the path, just update query param with a page number.
set your route to ignore query param changes:
....
$routeProvider.when('/foo', {..., reloadOnSearch: false})
....
and in your app update $location with:
...
$location.search('page', pageNumber);
...
From this blog post:
by default all location changes go through the routing process, which
updates the angular view.
There’s a simple way to short-circuit this, however. Angular watches
for a location change (whether it’s accomplished through typing in the
location bar, clicking a link or setting the location through
$location.path()). When it senses this change, it broadcasts an
event, $locationChangeSuccess, and begins the routing process. What
we do is capture the event and reset the route to what it was
previously.
function MyCtrl($route, $scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
$route.current = lastRoute;
});
}
My solution was to use the $routeChangeStart because that gives you the "next" and "last" routes, you can compare them without the need of an extra variable like on $locationChangeSuccess.
The benefit is being able to access the "params" property on both "next" and "last" routes like next.params.yourproperty when you are using the "/property/value" URL style and of course use $location.url or $location.path to change the URL instead of $location.search() that depends on "?property=value" URL style.
In my case I used it not only for that but also to prevent the route to change is the controller did not change:
$scope.$on('$routeChangeStart',function(e,next,last){
if(next.$$route.controller === last.$$route.controller){
e.preventDefault();
$route.current = last.$$route;
//do whatever you want in here!
}
});
Personally I feel like AngularJS should provide a way to control it, right now they assume that whenever you change the browser's location you want to change the route.
You should be loading $location via Dependency Injection and using the following:
$scope.apply(function () {
$location.path("yourPath");
}
Keep in mind that you should not use hashtags(#) while using $location.path. This is for compability for HTML5 mode.
The $locationChangeSuccess event is a bit of a brute force approach, but I found that checking the path allows us to avoid page reloads when the route path template is unchanged, but reloads the page when switching to a different route template:
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
$route.current = lastRoute;
}
});
Adding that code to a particular controller makes the reloading more intelligent.
Edit: While this makes it a bit easier, I ultimately didn't like the complexity of the code I was writing to keep friendly looking URL's. In the end, I just switched to a search parameter and angular handles it much better.
I needed to do this but after fussing around trying to get the $locationChange~ events to get it to work I learned that you can actually do this on the route using resolve.
$routeProvider.when(
'/page',
{
templateUrl : 'partial.html',
controller : 'PageCtrl',
resolve : {
load : ['$q', function($q) {
var defer = $q.defer();
if (/*you only changed the idea thingo*/)
//dont reload the view
defer.reject('');
//otherwise, load the view
else
defer.resolve();
return defer.promise;
}]
}
}
);
With AngularJS V1.7.1, $route adds support for the reloadOnUrl configuration option.
If route /foo/:id has reloadOnUrl = false set, then moving from /foo/id1 to /foo/id2 only broadcasts a $routeUpdate event, and does not reload the view and re-instantiate the controller.