Dylan Beattie
Dylan Beattie

Reputation: 54150

Why doesn't SammyJS handle hashes containing slashes the same as normal hashes?

Inspired by the example in the KnockoutJS SPA tutorial, I'm trying to use Sammy.js to wire up some simple behaviour in a single-page application, using the URL hash to keep track of which API endpoint is being invoked. I'm trying to end up with a page history that looks like this:

In every case, the URL fragment is actually the raw URL that was used to make the API call which rendered the current page.

The problem is, I can't get Sammy's route handling to work properly when the location hash contains a forward-slash character - and I have no idea why. According to RFC3986, "the characters slash ("/") and question mark ("?") are allowed to represent data within the fragment identifier", so it sounds to me like I should just be able to use unescaped slash and question mark characters in the URL fragment.

Here's a complete repro case covering several different scenarios. There's two Sammy routes defined - if my understanding of the syntax is correct, one should capture anything containing a hash and the other should match only the empty route. That's not what's happening, though.

    <!DOCTYPE html>
<html>
<head>
    <title>SammyJS routing demo</title>
    <script type="text/javascript" src="Scripts/jquery-3.1.1.min.js"></script>
    <script type="text/javascript" src="Scripts/sammy-0.7.5.min.js"></script>
</head>
<body>
    <ul>
        <li><a href="#foo">#foo</a> - prints 'foo'</li>
        <li><a href="#/bar">#/bar</a> - prints 'NOPE'</li>
        <li><a href="#!/baz">#!/baz</a> - prints 'NOPE'</li>
        <li><a href="#foo/bar">#foo/bar</a> - prints 'NOPE'</li>
        <li><a href="#foo/bar#bop">#foo/bar#bop</a> - prints 'bop'</li>
        <li><a href="#why?not">#why?not</a> - prints 'why'</li>
        <li><a href="#why?not#zoidberg">#why?not#zoidberg</a> - prints 'why?not#zoidberg'</li>
        <li><a href="#why?not#zoidberg/">#why?not#zoidberg/</a> - prints 'NOPE'</li>
    </ul>
    <script type="text/javascript">
        Sammy(function () {
            this.get('#:url', function () { console.log(this.params.url); });
            this.get('', function () { console.log('NOPE'); });
        }).run();
    </script>
</body>
</html>

Any ideas what I need to do to get Sammy to handle these fragments consistently?

Upvotes: 0

Views: 356

Answers (2)

Smartboy
Smartboy

Reputation: 682

The problem is that Sammy params within a URL cannot contain a slash character. If it did, you could not have URLs like "#/path/:var1/other/:var2". Instead, you'll want to use a regex whenever you match your path, as in the example below (see http://sammyjs.org/docs/routes for more info):

this.get(/\#(.*)/, function() {
    console.log(this.params['splat']);
});

The above will output the URL for each of the following paths:

Unfortunately, you won't be able to get items following a "?" character using this, though. Those are parsed separately into "this.params". So the following URLs will have "this.params.page" set to the appropriate value:

Unfortunately, you won't be able to have multiple hash in a URL, and Sammy only looks at the portion of the URL starting from the last instance of a hash, thus explaining why "#why?not#zoidberg" works.

Upvotes: 1

Tom Parker-Shemilt
Tom Parker-Shemilt

Reputation: 1697

So, Sammy.js as noted on http://sammyjs.org/docs/routes says "It does not work if you want to match strings that contain ‘/’. If you want that you can use Regexp and ‘splat’", and hence why this example fails.

If however you replace the first this.get with this.get('#(.*)', function () { console.log(this.params['splat']); }); then it works pretty much fine (well, the "#why?not" misses out the not, but that's probably further in the query string and "#why?not#zoidberg" isn't that happy, but I'd say you should stick to a single # in a URL)

https://jsfiddle.net/gymt828r/ demonstrates a fixed instance of this.

Upvotes: 2

Related Questions