Backbone.js routes are not being triggered - ruby-on-rails

I have a Rails 3.2.3 app with Backbone.js and I'm using pushState on my Backbone.history.
The Problem
When I click on a link which goes to say '/foo' to show appointment with ID: 1, then Backbone router gets to that first, which I can quickly see before Rails router takes over and complains that there is no route for /foo.
My Backbone.js code
Here is my backbone router.
window.AppointmentApp = new (Backbone.Router.extend({
routes: {
"": "index",
"foo": "foo",
"appointments": "index",
"appointments/:id": "show"
},
foo: function(){
$("#app").append("foo<br />");
},
initialize: function() {
this.appointments = new Appointments();
this.appointmentListView = new AppointmentListView({ collection: this.appointments });
this.appointmentListView.render();
},
start: function() {
Backbone.history.start({pushState: true});
},
index: function() {
$("#app").html(this.appointmentListView.el);
this.appointments.fetch();
},
show: function(id) {
console.log("Enter show");
}
}));
It should stay on the same page and attach a 'foo' to the end of the #app div, but it never does.
Backbone index viewer
window.AppointmentListView = Backbone.View.extend({
template: JST["appointments/index"],
events: {
"click .foo": function(){Backbone.history.navigate("foo");},
},
comparator: function(appointment){
return appointment.get('topic');
},
initialize: function(){
this.collection.on('reset', this.addAll, this);
},
render: function(){
this.$el.html(this.template);
this.addAll();
return this;
},
addAll: function() {
this.collection.forEach(this.addOne, this);
},
addOne: function(appointment){
var appointmentView = new AppointmentView({model: appointment});
this.$el.append(appointmentView.render().el);
}
});
app/assets/templates/appointments/Index.jst.ejs
<h1>Appointments</h1>
Say Foo
<a href=appointments/add>Add</a>
<div id="app"></div>
I was using pushState as it allows me to keep a history and the Back button functionality.
The Backbone.history.navigate doesn't call my Backbone route, it calls the Rails route instead. How do I go about fixing this?
Should I be trying to setup Backbone to accept routes such as 'appointments/1' and taking control or do I have to use a click event with a Backbone.history.navigate call like above?

You need to return false from your click .foo event handler, otherwise the browser will continue as if you'd clicked the link normally and request the actual /foo page from the server.
I think you've also got the call to Backbone.history.navigate("foo"); wrong - Backbone.history doesn't have a navigate function as far as I can see from the documentation. You should actually be calling .navigate on your Backbone.Router instance, and passing in the trigger option to cause it to call trigger the route. For example:
window.AppointmentApp.navigate("foo", { trigger : true } );
You may already know this but if you're planning on using pushState then you should really update your server side to support all the URLs that your client side does. Otherwise if a user decides to copy & paste the URL into another tab, they will just run into rails complaining that there is no route.

Related

JQuery-ui Tabs - reload page with completely new content not working

I'm loading in a report and displaying it with jquery-ui in tab format. The report is returned by an ajax call in json, and a function is formatting it into HTML. Example code below:
<div id="reportdiv">
</div>
<script>
function displayreport(objectid)
{
$( "#reportdiv" ).hide();
$( "#reportdiv" ).html("");
$.ajax({
type: "GET",
headers: { 'authtoken': getToken() },
url:'/reportservice/v1/report/'+objectid.id,
success: function(data){
if(data == null)
{
alert("That report does not exist.");
}
else
{
var retHTML = dataToTabHTML(data.config);
$("#reportdiv").html(retHTML).fadeIn(500);
$(function() {
tabs = $( "#reportdiv" ).tabs();
tabs.find( ".ui-tabs-nav" ).sortable({
axis: "x",
stop: function() {
tabs.tabs( "refresh" );
}
});
});
}
}
});
}
</script>
This works fine the first time displayreport is called. However, if the user enters another value and runs displayreport again, the "tabs" format is completely lost (the tabs are displayed as links above my sections, and clicking on a link takes you to that section further down the page).
I figured completely re-setting the reportdiv html at the beginning of the function would bring me back to original state and allow it to work normally every time. Any suggestions?
After more testing, found that destroy was the way to go. If I've set up tabs already, run the destroy, otherwise, skip the destroy (http://jsfiddle.net/scmxyras/1/) :
if(tabs!=undefined)$( "#reportdiv" ).tabs("destroy");

Add integer to Backbone model attributes

I am attempting to allow for a button to be clicked on a form and its number of votes be +=1. You'd think I could do something like idea.set({"votes": +=1}) but it doesn't seem to like that. I made an event to listen for a click on my upvote button, now I'm stuck. Can anyone help?
IdeaVoter.Views.IdeasIndex = Backbone.View.extend(
template: HandlebarsTemplates['ideas/index'],
initialize: function(){
this.collection.on('reset',this.render, this)
this.collection.on('add',this.render, this);
},
events: {
"submit #new_idea ": "createIdea",
"click #upvote": "upvote"
},
render: function(){
$(this.el).html(this.template())
this.collection.each(this.addIdea)
this.collection.each(this.upvote)
return this;
},
addIdea: function(idea){
view = new IdeaVoter.Views.Idea({model: idea})
$('#ideas').append(view.render().el)
},
upvote:function(idea){
idea.save()
}
});
I think you're looking for a simple
idea.set('votes', idea.get('votes') + 1);
You probably want to add a method to your model to hide that behind a simple idea.upvote() or the like.

Rails + Backbone - Backbone routes not working

I have push state enabled,
Backbone.history.start({
pushState: true
});
When i try to click on this link,
All
Its redirecting to the URL, but backbone routes is not working.
routes: {
'aspect/:id':'aspect'
},
Am i missing anything?
Update:
I tried to add it in events but still its not working,
Template:
All
View:
events: {
'click .user_aspects': 'aspects_list'
},
aspects_list: function(){
alert(2)
}
Do i need to write it in jQuery?
You need to prevent clicking and execute navigate method manually.
For example:
$('a').on('click', function (e) {
e.preventDefault();
router.navigate(e.currentTarget.getAttribute('href'), true);
})
Please have a look:
https://github.com/tbranyen/backbone-boilerplate/blob/04cd6354b0e0276442a1ddc9cdbc889924489745/app/main.js#L22

jQuery Ajax Form Submit Fails

I am developing an MVC4 mobile app that uses several forms which are loaded into a section on the layout via ajax. I've got jQuery mobile set with Ajax turned off so I can manage the Ajax myself. Most of the forms work fine, the load and submit via ajax as they should. However, so far there is one form that refuses to fire the form submit and submit the form via ajax like the rest. First, the form is loaded when a user clicks to add a contact and this works fine:
// Handle the add contact button click
$('#btnAddNewContact').on('click', function (e) {
e.preventDefault();
// Make sure a location was selected first.
var locationID = $('#cboLocation').val();
if (locationID.length === 0) {
//$('#alertTitle').text('REQUIRED');
$('#alertMsg').html("<p>A Contact must be associated with a Location.</p><p>Please select or add a Location first.</p>");
$('#alertDialogDisplay').click();
} else {
SaveOpportunityFormState();
$.cookie('cmdLocationId', locationID, { path: '/' });
$.mobile.loading('show');
$.ajax({
url: '/Contact/Add',
type: 'GET',
cache: false,
success: function (response, status, XMLHttpRequest) {
$('section.ui-content-Override').html(response);
// Refresh the page to apply jQuery Mobile styles.
$('section.ui-content-Override').trigger('create');
// Force client side validation.
$.validator.unobtrusive.parse($('section.ui-content-Override'));
},
complete: function () {
$.cookie('cmdPreviousPage', '/Opportunity/Add', { path: '/' });
AddContactLoad();
ShowSearchHeader(false);
$.mobile.loading('hide');
},
error: function (xhr, status, error) {
// TODO - See if we need to handle errors here.
}
});
}
return false;
});
Notice that after successfully loading the form the AddContactLoad() function is fired. This works fine and here is that code:
function AddContactLoad() {
$('#contactVM_Phone').mask('(999) 999-9999? x99999');
$('#frmAddContact').on('submit', function (e) {
e.preventDefault();
if ($(this).valid()) {
$.mobile.loading('show');
$.ajax({
url: '/Contact/Add',
type: 'POST',
cache: false,
data: $(this).serialize(),
success: function (response, status, XMLHttpRequest) {
if (!response) { // Success
ReturnToAddOpportunity();
} else { // Invalid Form
$('section.ui-content-Override').html(response);
// Force jQuery Mobile to apply styles.
$('section.ui-content-Override').trigger('create');
// Force client side validation.
$.validator.unobtrusive.parse($('section.ui-content-Override'));
AddContactLoad();
$.mobile.loading('hide');
}
},
complete: function () {
},
error: function (xhr, status, error) {
// TODO - See if we need to handle errors here.
}
});
}
return false;
});
$('#btnCancel').on('click', function (e) {
e.preventDefault();
// See where add contact was called from.
var previousPage = $.cookie('cmdPreviousPage');
if (previousPage.indexOf("Detail") >= 0) {
ReturnToOpportunityDetails();
} else {
ReturnToAddOpportunity();
}
return false;
});
}
If I click the cancel button, that code is fired so I know this is working too. Here is my form code:
#using (Html.BeginForm("Add", "Contact", FormMethod.Post, new { #id = "frmAddContact" }))
{
#Html.ValidationSummary(true)
#Html.AntiForgeryToken()
-- Form Fields Here --
<div class="savecancel" >
<input type="submit" value="Save" data-mini="true", data-theme="b", data-inline="true" />
Cancel
</div>
}
As you can see the form is named frmAddContact and that is what the AddContactLoad() function is attaching the submit event to. To save my sole I cannot figure out why the form does not submit via the ajax post like every other form in the app. Am I missing some kind of initialization, I just don't know. If anyone can please help I'd really appreciate it!!
As it turns out, I had created a custom unobtrusive Ajax validator for a phone number then copied and pasted it to do the same with a zip code. Unfortunately in the process I forgot to rename a variable and thus an error was occurring in the validation script which caused the problem. In the mean time, if you're reading this, you might take a note of the code here and how to inject HTML into a page via Ajax and jQuery mobile. I've never found this in a book or on the web and it contains some very useful methodology and syntax. On the form submit the reason I'm checking for the empty response is I just return null from the controller to validate the form was valid and the save worked in which case I send them to a different HTML injection i.e. that page they originally came from. If null is not returned I inject that page with the HTML containing the original form and error markup so the user can make corrections then resubmit. I'm also calling a form load method that attaches handlers to the HTML once it's injected into the main page. Hope this helps somebody!

Backbone.js and jQuery Mobile, function everytime a view is loaded

I have a 4 page jquery mobile/backbone.js app. I want to run a function to populate some inputs with an ajax call everytime a certain page is loaded. I know I can do the call on render but that is only when the app or page initially loads and won't run again unless it is refreshed.
The view looks something like this:
define([
'jquery',
'underscore',
'backbone',
'text!templates/default/parent.html'
], function($, _, Backbone, parentTemplate) {
var defaultView = Backbone.View.extend({
initialize: function() {
$(this.el).html(parentTemplate);
this.render();
},
events: {
'click #busNext': 'showTarget'
},
render: function() {
this.setValidator();
return this;
}
});
return new defaultView;
});
Here render calls onces,
If you wanted to call them again based on some condition then,
You have to call render method. Like.
this.render();
It will execute render code again and again as you wish.

Resources