Getting URL fragment instead of hash in Backbone.js - javascript

I have struggled with this for quite a long time. I'm using Backbone.js along with Require.js. I'm trying to tell Backbone to display a specific view according to the current URL (not hash). My code looks like this:
define(['jquery','underscore','backbone', 'views/manage_pages'], function($, _, Backbone, ManagePages) {
var AppRouter = Backbone.Router.extend({
routes:{
'/new_page': "showPageForm",
'/edit_page': "showPageEditForm",
'*actions': "showPageForm"
},
});
var initialize = function(){
appRouter = new AppRouter;
appRouter.on("route:showPageForm", function(){
console.log('hej');
});
appRouter.on("route:showPageEditForm", function(){
console.log('ho');
});
var page_form = new ManagePages();
Backbone.history.start();
}
return {
initialize: initialize
}
});
So basically when the user goes to http://example.com/new_page it should log <code>hej</code> and when he goes to http://example.com/editpage it should log <code>ho</code>. I don't know how to accomplish this or even if it is possible. Can anyone help me?

You could use Backbone's support for HTML5 push-state, which uses the URL path by default (instead of the hash fragment). Just specify it as an option when calling history.start:
Backbone.history.start({ pushState: true });
(Note that if your application is located in a sub-path under the domain, then you can tell Backbone to use that as the root -- Backbone.history.start({pushState: true, root: "/myapproot/"}) -- although it looks like that's not a concern in your case.)

Related

Backbone.js views will not display on page load, but works with jQuery in console

I am trying to create a Backbone.js front end for a Rails api and I am running into a problem. I have been able to pull the data from the api and put it into a single view and a collection view. The problem is that the data is definitely there and I can run a jQuery command in the console that will append it to the page correctly however, it will not display when the page loads. It seems like maybe the javascript files are not loading in the correct order so I have tried rearranging in application.js with no luck. I have researched a lot and can't find anything that relates directly to this and I am a beginner with Backbone.js, so it's possible I may be going about this the wrong way. Any help would be appreciated. Here is my code:
singleUserModel.js
singleUser = Backbone.Model.extend({
defaults: {
name: null
}
});
userCollection.js
userCollection = Backbone.Collection.extend({
model: singleUser,
url: 'http://localhost:3000/users'
});
singleUserView.js
singleUserView = Backbone.View.extend({
tagName: 'li',
template: _.template("<%= name %>"),
render: function() {
var userTemplate = this.template( this.model.toJSON());
this.$el.html(userTemplate);
return this;
}
});
userCollectionView.js
allUsersView = Backbone.View.extend({
tagName: 'ul',
initialize: function( initialUsers ) {
this.collection = new userCollection();
this.collection.fetch({reset: true});
this.render();
this.listenTo(this.collection, 'add', this.renderUser);
this.listenTo(this.collection, 'reset', this.render)
},
render: function() {
this.collection.each(function(item) {
this.renderUser(item);
}, this);
},
renderUser: function( item ) {
var user = new singleUserView({
model: item
});
this.$el.append(user.render().el);
}
});
main.js
var userGroupView = new allUsersView();
$('#allUsers').html(userGroupView.el);
Using this command in the console will display it on the page correctly.
$('#allUsers').html(userGroupView.el);
The script probably executes sooner than #allUsers exists on page. When you run it inside console, the page is already loaded and therefore this element was rendered on the page.
You should put this function call inside DOMContentLoaded, which can be registered with jquery's constructor shorthand $(function() { /* your code here */ }). This will ensure that the DOM is rendered before you try to query for the #allUsers element. Also note, if you're loading the data asynchronously and the #allUsers exists only after the data is loaded, you should be calling the function after the content is rendered. but if that's the case, you'll have to figure it on your own.
Also, an useful tip: never rely on .js files being loaded in the correct order, or you'll have a bad time.

backbone.js (underscore template) append masonry

I would like to append new views to my existing masonry list.
Why does my view not append? Am I missing something?
Masonry.js
function:
render_masonry_append: function() {
var self = this;
_(this.collection_append.models).each(function(item) {
var view = new Home_thumbnail_view({
model: item.attributes
});
//console.log(view);
$mainListContainer.append(view.render()).masonry("appended", view.render());
});
console.log($mainListContainer);
}
Thanks!
Make sure view.render() returns this, and then change that line to:
$mainListContainer.append(view.render().el).masonry("appended", view.render().el);
I'm not sure what masonry() does, so I added view.render().el there too, but that's your call. Most likely you don't need to be render()ing the view twice, so maybe even better would be:
view.render();
$mainListContainer.append(view.el).masonry("appended", view.el);

backbone-pageable remove() uses invalid url

I'm relatively new to Backbone and I'm trying to use a PageableCollection in my application.
https://github.com/wyuenho/backbone-pageable
Could someone please point me, what am I doing wrong? I'm using backbone 1.0.0
I have a collection and a model defined like this:
var VoteList = Backbone.PageableCollection.extend({
model: Vote,
url: config.service + 'votes'
});
var Vote = Backbone.Model.extend({
url: function () {
return config.service + 'vote/' + this.id;
}
});
Later in the application:
this.collections.voteList = new VoteList([], {mode: "client", state: {pageSize: 12}});
....
this.collections.voteList.remove(options.model);
PageableCollection.remove() method fires a DELETE event which uses the URL of VoteList collection(?) to access a web service which in turn produces me an error 405 "Method not allowed" as a DELETE method is supposed to have an {id}
#DELETE
#Path("/vote/{id}")
#Consumes({MediaType.APPLICATION_JSON})
public void deleteVoting(#PathParam("id") Integer id) {
log.info("deleting " + id.toString());
}
When I remove pagination just by instantiating normal Backbone.Collection
var VoteList = Backbone.Collection.extend({ ... });
everything works as expected, Backbone uses a model url + id when deleting. So my question is how to make the PageableCollection to behave just in the same way?
This morning got a mail from the plugin author, it appears to be a known bug in Backbone 1.0.0. I guess it will gone away with version 1.0.1
Please see this thread: https://github.com/wyuenho/backbone-pageable/issues/70
To solve problem now I used a latest 'master' branch of Backbone which has this issue fixed:
https://raw.github.com/jashkenas/backbone/master/backbone.js
Alternatively, if you can't for some reason use a development branch, a temporary solution like this will also work:
var Vote = Backbone.Model.extend({
initialize: function () {
this.url = function () {
return config.service + 'vote/' + this.get('id')
}
}
});
Upgrading to backbone 1.1.0 seems to resolve this problem.
I ran into the same exact issue with backbone 1.0.0 collections extended by backbone-pageable.
Heads up! Be sure to keep an eye on the 1.1 upgrade notes since backbone 1.1 isn't fully compatible to 1.0 code, such asoptionspassed to views are no longer automatically available as this.options.

Code from multiple view instances causing conflicts

I'm porting a web service into a single-page webapp with Backbone. There is a basic layout consisting on a header, an empty div#content where I'm attaching the views and a footer.
Every route creates the corresponding view and attachtes it to div#content replacing the view that was rendered before with the new one.
I'm using require.js to load the backbone app and it's dependencies.
All Backbone code is pretty small, only one file as I'm only using a router and a view.
This AMD module depends on a util.js file exporting functions that are used in the views.
After a view is created and rendered, It executes the utilities (jquery stuff, ajax, etc) it needs from util.js.
The problem is that when I render a view, it's utilities get called, and when I navigate to another route, and a new view is created, the new view's utilities are called now, but the older view's utilities are still running.
At some point, I have utilities from like five views running altogether, causing conflicts sometimes.
It's clear than my approach is not good enough, as I should have a way to stop/start utilities functions as some kind of services.
I'll paste relevant code that shows my current approach:
require(["utilities"], function(util) {
...
Application.view.template = Backbone.View.extend({
el: "div#content",
initialize: function(){
this.render();
},
render: function(){
var that = this;
// ajax request to html
getTemplate(this.options.template, {
success: function(template) {
var parsedTemplate = _.template( template, that.options.templateOptions || {});
that.$el.html(parsedTemplate);
// execute corresponding utilities
if(that.options.onReady) {
that.options.onReady();
}
},
error: function(template) {
that.$el.html(template);
}
})
}
});
...
Application.router.on('route:requestPayment', function(actions) {
var params = { template: 'request-payment', onReady: util.requestPayment };
var view = new Application.view.template(params);
});
...
});
util.requestPayment consist of a function having all stuff needed to make template work.
I'm confused about how should I handle this issue. I hope I was clear, and any suggestions or help will be appreciated.
EDIT: utilities.js snippet:
...
var textareaCounter = function() {
$('#requestMessage').bind('input propertychange', function() {
var textarea_length = 40 - $(this).val().length;
if(textarea_length === 40 || textarea_length < 0) {
$('#message-counter').addClass('error').removeClass('valid');
$("#submitForm").attr('disabled', 'disabled');
}
else if(textarea_length < 40 && textarea_length > 0) {
$('#message-counter').removeClass('error');
$("#submitForm").removeAttr('disabled');
}
$('#message-counter').text(textarea_length);
});
}
...
var utilities = utilities || {};
...
utilities.requestPayment = function() {
textareaCounter();
initForm();
preventCatching();
requestPaymentCalcFallback();
};
...
return utilities;
...
I would suggest that you should store reference to the currently active view somewhere in your app.
You create a new view here :
var view = new Application.view.template(params);
but you have no access to this variable afterwards. So it exists but you can't stop/delete/get rid of it.
What we normally do is to have a Parent App class which initializes the whole app and manages everything. Your every module in requirejs would be depenedent on it. When a new route is navigating, you ask the Parent App class to change the view. It will delete the old view, create a new one, populate div#content and then store the reference of it.
I think when you delete the old view, all the utilities will stop responding to it.
If you still have the issue with events being called, then you might need to use stopListening event binders before deleting the view reference.

Backbone route on IE

I'm trying use the history backbone root but it doesn't work fine on IE (or other browsers which don't support history api).
My webapp has this map, where each module makes a request, but actions should call a function:
site/moduleA/
site/moduleA/action1/ID
site/moduleB/
site/moduleB/action1/ID
mapping:
var MyRouter = Backbone.Router.extend({
routes: {
"moduleA/": "homeA",
"moduleA/action1/:id": "action1",
// ...
}
}
var app = new MyRouter();
Backbone.history.start({pushState: true});
I'm navigating using this:
app.navigate('moduleA/',{trigger:true});
or
app.navigate('/moduleA/action1/4334',{trigger:true});
(I'm getting links click events and calling navigate(link.href,{trigger:true}) )
Every is working fine on Chr/FF (browsers with history api support), and the url is updated in the browser and the function is call.
However, in IE the url is replaced by this hash format: site/#moduleA/
In order to solve that I've tried set the root in history.start
Backbone.history.start({pushState: true, root:'/moduleA/'});
But, now IE replace the url using this format: site/moduleA/#moduleA/ or site/moduleA/#moduleA/action1/432432.
So, Why IE is repeating the root in the url ?
How can I solve this?
Thanks in advance
By setting root to '/moduleA/' you are telling backbone to use site/moduleA as the root, this is the expected behavior.
Rememeber in backbone
routes: {
"moduleA/": "homeA", // #moduleA/
"moduleA/action1/:id": "action1" // #moduleA//action1/:id
}
is different from
routes: {
"/moduleA/": "homeA", // #/moduleA/
"/moduleA/action1/:id": "action1" // #/moduleA//action1/:id
}
its good to keep this in mind when using app.navigate.

Categories