Rchristiani
Rchristiani

Reputation: 444

Backbone Routes with pushState on are not working when you refresh page

I have a simple router:

Erin.Router = Backbone.Router.extend({
    initialize: function() {
        Backbone.history.start({pushState: true});
    },
    routes: {
        '' : 'index',
        'project/:img' :'project',
    },
    index: function() {
        var galleryView = new Erin.GalleryView();
    },
    project: function(img) {
        console.log(img);
    }
}); 

The template for the Erin.GalleryView is(thinking there might be an issue there):

<script type="text/template" id="gallery-grid">
        <a href="/project/<%= id %>">
            <img src="<%= thumbnail %>" />
            <span class="desc">
                <div class="desc-wrap">
                    <p class="title"><%= title %></p>
                    <p class="client"><%= client %></p>
                </div>
            </span>
        </a>
    </script>

The GalleryView and the GalleryItem code.

Erin.GalleryItem = Backbone.View.extend({
    tagName: 'div',
    className: 'project-container',
    //Grab the template html
    template: _.template($('#gallery-grid').html()),
    //Set up the render function
    render: function() {
        //What is the $el in this case?
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

Erin.GalleryView = Backbone.View.extend({
    el: '#projects',
    initialize: function() {
        //create new collection
        this.col = new Erin.Gallery();
        //Listen to all events on collection
        //Call Render on it
        this.listenTo(this.col,'all',this.render);
        //Fetch data
        this.col.fetch();
    },
    render: function() {
        //Get reference to the view object
        var that = this;
        //Empty div
        this.$el.empty();
        //For each model in the collection
        _.each(this.col.models, function(model){
            //call the renderItem method
            that.renderItem(model);
        },this);
    },
    renderItem: function(model) {
        //create a new single item view
        var itemView = new Erin.GalleryItem({
            model:model
        });
        //Append items to to element, in this case "#projects"
        this.$el.append(itemView.render().el);  
    }
});

Then I have a document ready

$(function() {
    var router = new Erin.Router();
    $('#projects').on('click', 'a[href ^="/"]', function(e){
        e.preventDefault();
        router.navigate($(this).attr('href'),{trigger: true});
    });
});

When you load the page and click one of the links in the #project section, everything behaves as it should, if you refresh that page however, I get an error that breaks the page.

From the console:

Uncaught SyntaxError: Unexpected token < js/jquery.js:1
Uncaught SyntaxError: Unexpected token < js/underscore-min.js:1
Uncaught SyntaxError: Unexpected token < js/backbone.js:1
Uncaught SyntaxError: Unexpected token < erin.js:1

It also states stuff like:

Resource interpreted as Script but transferred with MIME type text/html: "http://localhost:8888/project/js/backbone.js". 

For all the links and scripts in the head of the document.

Which all seem to point at the first line of the index.html file. So if I click a link, it will console the img id I am looking for from my data, if I refresh the page OR type that link in, I get the errors above. Am I correct in thinking I should be able to save the links domain.com/project/coolthing and have that work when someone comes to the page. Have I missed something? Implemented something weird? A nudge in the right direction would be much appreciated.

Thanks.

Upvotes: 3

Views: 1599

Answers (3)

Mohit
Mohit

Reputation: 2249

Both thibauts' and providencemac's assessments are right. Either don't use pushState and go with hash links or put a rewrite rule in your server to send the statics.

initially when you load you load this url -

http://localhost:8888

like wise url of your statics is along this

http://localhost:8888/js/backbone.js

the actually problem is when you use pushState and visit a link. Your browser url becomes. something like this -

http://localhost:8888/project/some_img_id

Now when you try to reload the page, the index file is taken from this url -

http://localhost:8888/project/your_default.file   [index.html]

Which your server fails to find and defaults to index file at

http://localhost:8888/your_default.file

And when the browser tries to access the statics it again defaults to index file

http://localhost:8888/project/js/backbone.js

thats why the error. as it finds '<' character which is invalid toekn in javascript.

Uncaught SyntaxError: Unexpected token < js/jquery.js:1

this error cause the file its trying to parse is backbone.js a javascript but its gets your index.html

Resource interpreted as Script but transferred with MIME type text/html: "http://localhost:8888/project/js/backbone.js".

Upvotes: 1

thibauts
thibauts

Reputation: 1648

You have a problem server-side. You're probably already half-way to the solution.

It looks like you have correctly configured your server to send your app HTML on any hit, except for your assets (js, css) which have precedence.

The problem lies in you main HTML, where your JS files are referenced with a relative path. When you reload after pushState has updated the URL to /project/1 the base URL becomes /project/.

ie. you have

<script src="js/backbone.js"></script>

when you should have

<script src="/js/backbone.js"></script>

As a result, when the browser tries to load backbone.js it doesn't find it (/project/js/backbone.js doesn't exist), and hits the server catchall that delivers the app HTML (hence the parsing error that chokes on <).

My explaination may not be crystal clear, it's pretty late over here, but I'm sure you'll understand !

Upvotes: 8

providencemac
providencemac

Reputation: 622

The problem is in the href you are using in the template. The Backbone Router is watching for changes to the hash portion of the site URL, which is the content after the # symbol.

So your href should be: <a href="/#project/<%= id %>"> (notice the # before "project")

In that fashion, you won't even need a click handler at all, the Router will automatically capture the hash change event and route accordingly.

Upvotes: 1

Related Questions