htmlurlbrowsersingle-page-applicationsammy.js

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?


Solution

  • 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.