Routing in knockoutjs looks bad: How can I roll my own?

3,420 views
Skip to first unread message

Nuno Job

unread,
Sep 21, 2011, 11:08:24 AM9/21/11
to KnockoutJS
Hi guys,

Everything about knockout.js looks awesome. The tutorial is great and
I'm really thankful for it!
However the client side routing part was not up to what I need and
made me feel like I needed to use something that gives me more control
over it, something like sugarskull[1].

Anyone has any experience integrating sugarskull/sammy into knockout
while perserving data bindings? In other words is there any
documentation for `knockout.address.js`. Where can I find the file so
I can examine the source code? (checked github and wasn't clear where
the file was).

On a broader topic it made me think on how coupled the framework is.
How easy is it to make arbitrary bindings with other libraries? Anyone
has any examples?

Thanks guys! And good job!
Nuno

[1]: https://github.com/hij1nx/SugarSkull

rpn

unread,
Sep 21, 2011, 2:57:12 PM9/21/11
to knock...@googlegroups.com
This sounds fun.  If I get a chance, I will see if I can make an attempt at it tonight.

I think that the basic idea is that you would write a small API that would glue this together.  This would be my thought process:
-You would basically pass in an observable and a key (like the name of the observable).  
-You would set up a route to ensure that when the URL is changed, that the observable is updated properly.
-You would also setup a subscription to the observable and whenever it changed you would need to ensure that the URL reflected the state of the observable

It is kind of like writing a custom binding, except you are binding against the URL and not an element.


Nuno Job

unread,
Sep 21, 2011, 4:12:25 PM9/21/11
to knock...@googlegroups.com
I'm also trying fyi :)

Because I couldn't get standalone code from learn.knockoutjs.com I
created this repo to get started:
https://github.com/dscape/knockout.js.samples

I'm going to fix number 3 so it actually does the routes, e.g. if you
hit refresh you loose the current message and tab.

Then I'll try sugarskull.

If you manage to get sugarskull before I get to it please do let me know :)

Nuno

Nuno Job

unread,
Sep 21, 2011, 5:41:27 PM9/21/11
to knock...@googlegroups.com
Care to explain the advantages in binding objects to routes?
Of the top of my head I can't think of any.

Nuno

Scott Messinger

unread,
Sep 21, 2011, 6:31:29 PM9/21/11
to knock...@googlegroups.com
I've had a great success using the Backbone.js router. I just ripped it out of Backbone and used it in my app.

Here's a gist of it:

I also use amplify.js to do the ajax stuff. I included the amplify requests so you could see the entire thing.

I highly, highly recommend using backbone's router. It's simple, works cross-browser, supports push state, and works great.

-Scott

Stacey Thornton

unread,
Sep 21, 2011, 6:43:58 PM9/21/11
to knock...@googlegroups.com

So now we are ripping out backbones... Are we programmers, surgeons, or aliens?

In seriousness this stuff looks awesome. Can't wait to try it when I get home.

Nuno Job

unread,
Sep 21, 2011, 6:46:32 PM9/21/11
to knock...@googlegroups.com
Scott that looks awesome.

Do you have a sample demo you can show? Or maybe this is part of one
of your open source projects?

Would be really interesting to see a working example

Nuno

mcoolin

unread,
Sep 21, 2011, 7:20:20 PM9/21/11
to knock...@googlegroups.com
On the client side I use bens plugin http://benalman.com/projects/jquery-hashchange-plugin/ which creates a nice event. I then implement a controller

  @controller= ->
    #document.title = location.hash  
    hash = location.hash
    hash = hash.replace /^#!/,''
    ### 
    time to parse the arguments
    Pattern
    /action/lang/id/saction/focus/debug/createdb
    ###
    params = [action,lang,docid,saction,focus,debug,createdb]=hash.split('/')
    switch action.toLowerCase()
      when 'doc' 
        self.clearPage(['appTitle','topMenu','footer','tabArea'])
         self.add2colStructure()
         self.addDoc docid
      when 'login'
        self.clearPage()  
        self.add2colStructure()
        self.addToPage 'login'
      when 'splash'
        self.clearPage()
        self.addToPage 'splash'
      else
        self.clearPage()
        self.addToPage 'splash'
    return

Nuno Job

unread,
Sep 21, 2011, 8:41:43 PM9/21/11
to knock...@googlegroups.com
For those interested I just finished the sugarskull demo.

sugarskull is used exclusively for routing, nothing else
ko is used for everything else, but no crazy bindings

check: https://github.com/dscape/knockout.js.samples/tree/master/03-single-page-apps

now I might go and work on actually making page refresh work

nuno

Nuno Job

unread,
Sep 21, 2011, 9:13:01 PM9/21/11
to knock...@googlegroups.com
Now working with full page refresh:

https://github.com/dscape/knockout.js.samples/commit/9b64dc6806ecff459e5753edc68e00086b22326f

I add to add a method to sugarskull to pull it off but I'll submit a
pull request there so no one gets puzzled why it doesnt work in your
computer (check the differ to figure out where)

Nuno

Scott Messinger

unread,
Sep 21, 2011, 11:17:37 PM9/21/11
to knock...@googlegroups.com
Hey Nuno, 

Thanks for the feedback! I really like the solution. All the other hashchangey plugins were either overly complex for my needs or generated plain ugly urls. I'm all about pretty urls. 

If you'd like to see it in action, you can check out my site: commoncurriculum.com. Sign up is in the upper right hand corner. We haven't publicly launched yet, but, as the knockout.js crowd isn't our intended audience, I'm not terribly worried.

-Scott

rpn

unread,
Sep 22, 2011, 4:27:26 AM9/22/11
to knock...@googlegroups.com
Hi Nuno-
It looks like you got to where you wanted to go.  I did not get a chance to do any coding last night (have been out of town and trying to catch up on some things), so I didn't get to try it yet.

Just to explain why I think that it is interesting/preferable to link the routing/URL segments to observables:  In a Knockout/MVVM app the view model is the central source of truth.  Ideally, the view model can stand-alone and does not depend on the view at all.   When someone changes the state of the view model, then the UI reacts to it based on the bindings.  I think that the same can be true for the URL.  

If segments of the URL are "bound" to parts of the view model, then there is far less chance that the two are not in sync.  Furthermore, the view does not need to understand the routing / URL structure.  It can be changed/tweaked at any time without breaking the view.  So, it would work in both directions.  If someone changes the URL, then the view model would get updated and the UI would react.  If an action results in the view model changing programmatically, then the URL would automatically be updated and obviously the UI would update as well.

I think that what you are doing is not far off and I am sure that it would work fine/well for what you want to do.  However, your view needs knowledge of the routes like:

<tr data-bind="click: function() { mailViewModel.go('/'+folder+'/'+id); mailViewModel.selectedMailId(id); }">

If it was bound, then you would only need to do mailViewModel.selectedMailId(id); and your routes could be changed without breaking your views.



Stacey Thornton

unread,
Sep 22, 2011, 4:44:46 AM9/22/11
to knock...@googlegroups.com

Honestly, this whole routing thing has me very lost. I loved the results of the knockout address plugin, but I never did fully understand what was going on with it. RPN has explained a little bit.

I am a novice at best, and know nothing of these other frameworks, but it looks to me that avoiding more magic strings would yield a more modular design that can be maintained easier. One of the things I hate about javascript is the amount of string concatenation that is often required. (I guess this is very hypocritical of me since knockout uses string literals for binding, and I am a huge preponent of the asp.net mvc html helpers, which are really just string extensions that accept parameters... But I digress.)

It looks like the declarative routing would become obtuse very easily. Is there a particular reason you want to use this pattern?

Nuno Job

unread,
Sep 22, 2011, 4:49:38 AM9/22/11
to knock...@googlegroups.com
Hi Ryan,

I see your point. That does sounds like something you would want but
let's look at the downside of the decoupling:

- Routers behavior is driven by KO, instead of being declarative like
the one I wrote
- Complex logic in the routing system will be become harder to manage
the more variables and binds you have
- That would be a decision bound on (As you said) a single point of
truth. That point is not the router, so people user to developing apps
that center about routes would find that unsatisfying
- It mostly forces you to write bad and cryptic routing rules.
- Page refreshes will include code that is far more complicated than
the one I wrote. Plus the possibility of bugs in that code will
probably be much higher given the amount of variables to control. In
my case it goes route to vars, and works fine I think?

I'm sure some of this can be leveraged, and I'm really happy that KO
was flexible enough to allow me that solution of only keeping one
variable (the route) that I need to observe. That fits me quite nicely
and I think I can still leverage the fact that I can now introduce
bugs by being out of sync. However I feel like my aproach is actually
more decoupled, allowing you to leverage whatever routing system you
like with KO. That's really good, at least for someone like me who
enjoys minimalism and having small pieces of code doing small parts.

If you think about my approach you will also see that I'm basically
created a js href kind-of-thing which it won't be foreign to a web
developer at all.

Thanks you all for your input, ideas, and so on. Hope to have
contributed something back with examples, working code and some
thoughts :)

Nuno

Nuno Job

unread,
Sep 22, 2011, 4:57:49 AM9/22/11
to knock...@googlegroups.com
Hi Stacey,

Sorry I'm not a microsoft guy, so I don't understand MVC.NET (I understand MVCC, sorry geek joke)


> It looks like the declarative routing would become obtuse very easily. Is
> there a particular reason you want to use this pattern?

All the reasons I said above. Plus it is common practice in Ruby on Rails, XQuery and JavaScript to say the least. I would think that driving routes by bindings to vars is an exception, not the rule. Or using your own words:


"Honestly, this whole routing thing has me very lost. I loved the results of the knockout address plugin, but I never did fully understand what was going on with it."

Can you understand the declarative rules I've written? If not for anything else that would be of some value :)

Here is the full routing login for your convenience (using sugarskull):


ko.link_observable_to_ss_route = function (v) {
  var routes = { '/:folder' : { on: update_bindings }
                       , '/:folder/:email' : { on: update_bindings }
                       }
    , router = Router(routes)
    , folder = v.selectedFolder

    , email = v.selectedMailId
    , go = v.go
    ;
  
  function update_bindings() {
    var current_route = router.currentRoute();
    if(folder && folder() !== current_route[0]) { folder(current_route[0]); }
    if(email && email() !== current_route[1]) { email(current_route[1]); }
  }

  router.init("#/");

  return go.subscribe( function (value) { return router.setRoute(value); });
};

This code could still be improved. update_bindings() is one function but should be too. I was too lazy to do it so I patched that code in. Feel free to try out the full project if you like.

Nuno

rpn

unread,
Sep 22, 2011, 5:04:03 AM9/22/11
to knock...@googlegroups.com
Hi Nuno-
There are definitely many ways that you could do it and never one answer to any problem.  I am glad that your way is working in the way that you expect.  

I am not sure that I quite see the downsides in a "bound" method.  Let me explain a bit further.  In my mind, the developer would not manage the routes at all or be in charge of writing complex/cryptic routing rules.  The developer would indicate each observable that they want to bind against and what the segment of the URL should be called.  The rest of the magic would happen in the API.  This could be done against any routing library that at least exposes an event when the URL changes.

In the end, everything is driven from the view model, just like the UI and the view model has no dependency or knowledge directly on the routing library.




Nuno Job

unread,
Sep 22, 2011, 5:20:56 AM9/22/11
to knock...@googlegroups.com
Hi Ryan,

That approach works easily if you to everything as:

#/?alice=foo&bob=bar&eve=baz

And accept that routes are side effects of what the program produces, and as long as you can retrieve those methods you can operate on them. In this example I gave, which is the way the demo works, it is trivial to restore state and create urls.

However if you follow a resource oriented approach (like rest, even thought we are talking hashbangs here) you are going to start down a path of strangeness. You have two choices, either you  1) build a lib that is tightly coupled to ko and makes it look like routes are easy but has loads of bloated magic in the background to do regexps, or 2) you write all that logic in the routes you are trying to write. 1) is actually just the re-factored version of 2). 

If you follow an approach where you bind variable to constructing your route in the viewModel (or simply do the concat thing I did) and then subscribe to the route var you you can use any routing lib, effectively decoupling the creation of urls and retrieval of bound variables for KO.

I'm an absolute noob in ko (started yesterday). I'm often wrong and that makes it even more likely :) But that doesn't make my argument less sound.

Thanks for the input and help,

Nuno

rpn

unread,
Sep 22, 2011, 5:37:15 AM9/22/11
to knock...@googlegroups.com
I can definitely see your point and perspective.

I think that I almost always look for solutions that are "pure" Knockout.  I like the view model to be the source of truth and Knockout to drive the entire app.  I can understand though that many people may want to use KO in targeted situations and not necessarily be the central control point.  I am not sure that I quite agree with the "bloated magic" comments.  I see it more as doing some of the hard part in a generic way to make it easier on the consumer of the plugin to maintain a separation of concerns.  No reason why it needs to get bloated necessarily.  It is really just programmatically generating the routes.

I can respect the simplicity of your approach though.

Hope to hear some more of your ideas/thoughts as you get more into Knockout.


Scott Messinger

unread,
Sep 22, 2011, 10:21:24 AM9/22/11
to knock...@googlegroups.com
I'm completely in agreement with Nuno about the need to have a resource style routing. A resource style routing is important in my app for a number of reasons:

a) allows urls to be typed in directly by users
b) human readable 
c) SEO (http://www.yourseoplan.com/human-readable-semantic-urls-will-help-your-seo/) (not that I'm into SEO, but it's a consideration).
d) maps nicely between the server and the client--the same routes will work if I send the request to the server as if I handle the request client side.

-Scott

rpn

unread,
Sep 22, 2011, 10:35:10 AM9/22/11
to knock...@googlegroups.com
Makes sense.  I am playing around with some ideas for creating resource style routes, but still doing some two-way binding with observables.  I think that it is a very interesting topic.

Geert Pasteels

unread,
Sep 22, 2011, 11:29:12 AM9/22/11
to knock...@googlegroups.com
I did some testing with https://github.com/millermedeiros/crossroads.js library a few weeks back and it worked pretty decent. Using routes with Knockout.js isn't always straight forward.

var ViewModel = function(){
  this.visible = ko.observable();
}

To turn visible to false with only ko I would most likely create a hide method and then bind it to click on some element. With routes instead of binding a viewmodel method you would visit a route and in the related function you would change the observable. 

The problem I had was that you have to decide what you link to routes and what you link with bindings. I decided to wait with routes until I have a working application and then I am going to add some routes to key states of my application. 

Also checked out commoncurriculum.com and it looks really neat Scott, well done!

 

Nuno Job

unread,
Sep 22, 2011, 11:54:31 AM9/22/11
to knock...@googlegroups.com
Just case this ain't clear: Working code of the stuff I did IS available :)


Just doesn't look fancy cause I pretty much ignored the existence of css :)
Nuno

Erick Thompson

unread,
Sep 22, 2011, 6:04:10 PM9/22/11
to KnockoutJS
I am trying to go from the technical aspect to the usage question. How
would client routing be used with knockout (or in general). Is the
overall purpose to have a controller in the page so that you can have
a more complex application within a single page? If so, are the routes
based on hash tags (i.e., URI#tag)?

I understand routing from a server perspective (mapping Uris to
actions/resources), but not on the client side.

Thanks,
Erick



On Sep 22, 8:54 am, Nuno Job <nunojobpi...@gmail.com> wrote:
> Just case this ain't clear: Working code of the stuff I did IS available :)
>
> https://github.com/dscape/knockout.js.samples
>
> Just doesn't look fancy cause I pretty much ignored the existence of css :)
> Nuno
>
> On Thu, Sep 22, 2011 at 4:29 PM, Geert Pasteels <geert.paste...@gmail.com>wrote:
>
>
>
> > I did some testing withhttps://github.com/millermedeiros/crossroads.js<https://github.com/millermedeiros/crossroads.js/wiki/Examples> library
> > a few weeks back and it worked pretty decent. Using routes with Knockout.js
> > isn't always straight forward.
>
> > var ViewModel = function(){
> >   this.visible = ko.observable();
> > }
>
> > To turn visible to false with only ko I would most likely create a hide
> > method and then bind it to click on some element. With routes instead of
> > binding a viewmodel method you would visit a route and in the related
> > function you would change the observable.
>
> > The problem I had was that you have to decide what you link to routes and
> > what you link with bindings. I decided to wait with routes until I have a
> > working application and then I am going to add some routes to key states of
> > my application.
>
> > Also checked out commoncurriculum.com and it looks really neat Scott, well
> > done!- Hide quoted text -
>
> - Show quoted text -

Scott Messinger

unread,
Sep 22, 2011, 9:15:09 PM9/22/11
to knock...@googlegroups.com
Yes to both. Hash or Hash-bang routing is useful when you're trying to create complex single page applications.

mcoolin

unread,
Sep 22, 2011, 10:06:24 PM9/22/11
to knock...@googlegroups.com
Oh what fun, they'd have to be bilingual here. :( Twice the fun!!!

Geert Pasteels

unread,
Sep 23, 2011, 5:59:38 AM9/23/11
to knock...@googlegroups.com
You can see a route as a shortcut to a certain state of your application.

Erick Thompson

unread,
Sep 23, 2011, 11:05:13 AM9/23/11
to KnockoutJS
Thanks Scott, that does make sense.

It's a whole new world. :)

On Sep 22, 6:15 pm, Scott Messinger <scottmessin...@gmail.com> wrote:
> Yes to both. Hash or Hash-bang routing is useful when you're trying to
> create complex single page applications.
>
> > > - Show quoted text -- Hide quoted text -

Greg T

unread,
Oct 5, 2011, 4:21:21 PM10/5/11
to KnockoutJS
Awesome to see so much constructive banter on here

BTW, if you don't understand what routing is or what purpose it
serves, then you (your code) probably don't need it! :-)

justinon...@gmail.com

unread,
Mar 31, 2014, 3:58:48 AM3/31/14
to knock...@googlegroups.com, nunojo...@gmail.com
I know this is a pretty old post.. but checkout pager.js 

gaffe

unread,
Apr 2, 2014, 12:55:37 PM4/2/14
to knock...@googlegroups.com, nunojo...@gmail.com, justinon...@gmail.com
Why reinvent the wheel and roll your own when you can use something like this where it has been done for you:

Message has been deleted

John Farrar

unread,
Apr 2, 2014, 3:13:10 PM4/2/14
to knock...@googlegroups.com, nunojo...@gmail.com, justinon...@gmail.com
They will be removing the KnockoutJS templating from the next version. This is good as it looks more like they will be using Handlbar style templates.
Message has been deleted

Gunnar Liljas

unread,
Apr 3, 2014, 4:33:31 AM4/3/14
to knock...@googlegroups.com
They are "getting rid" of Knockout, and to ease the migration, they're using the observable plugin and punches.


2014-04-02 22:51 GMT+02:00 Enis Pilavdzic <en...@dzic.com>:
This just says they are going to use the observable plugin and punches plugin by default, they are not getting rid of knockout, I thought?

 


On Wed, Apr 2, 2014 at 3:10 PM, John Farrar <johnf...@sosapps.com> wrote:
NOPE... they are ditching Knockout for the next gen version of Druandal.



On Wednesday, April 2, 2014 12:55:37 PM UTC-4, gaffe wrote:
Why reinvent the wheel and roll your own when you can use something like this where it has been done for you:

--
You received this message because you are subscribed to a topic in the Google Groups "KnockoutJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/knockoutjs/mnrStEiFz1o/unsubscribe.
To unsubscribe from this group and all its topics, send an email to knockoutjs+...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "KnockoutJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to knockoutjs+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Miguel Ludert

unread,
Apr 10, 2014, 10:49:22 AM4/10/14
to knock...@googlegroups.com, nunojo...@gmail.com
Her is my standard routing pattern using Sammy and the knockout.amd.helpers:


   
// set up the amd modules and templates
    ko
.bindingHandlers.module.baseDir = "modules";
    ko
.amdTemplateEngine.defaultPath = "modules";
    ko
.amdTemplateEngine.defaultSuffix = ".html";


    $
(function () {
       
// fire up Sammy
       
Sammy('#content', function () {


           
var route = function(method,path,content) {
               
this[method](path, function () {
                   
var result;
                   
if (_.isString(content)) {
                        result
= {
                            name
: content,
                            data
: this.params
                       
};
                   
} else if (_.isPlainObject(content)) {
                       
// get the properties of the sammy url parameters, jquery won't do a deep merge otherwise
                       
var params = _.pick(this.params, _.keys(this.params));


                       
// do a deep merge on the content and params so the child 'data' properties merge together
                        result
= $.extend(true, {}, content, { data: params });
                   
}
                    context
.content(result);
               
});
           
}.bind(this);


           
// set up specific routes

            route
('get', '#/foo/:id/edit', { name: "foo/info/module", template: "foo/info/edit", data : { edit : true } }); // foo edit info page
            route
('get', '#/foo/:id', { name: "foo/info/module", template: "foo/info/view" }); // foo view info page
            route
('get', '#/foo', "foo/search/module"); // foo search page
            route
('get', '#/bar/:id', "bar/module"); // bar search page
            route
('get', '#/bar', "bar/module"); // bar search page


           
// generic routes
           
// route for module with id
           
this.get("#/:module/:id", function () {
                context
.content({
                    name
: this.params.module + "/module",
                    data
: this.params
               
});
           
});


           
// route for list module
           
this.get("#/:module", function () {
                context
.content(this.params.module + "/module");
           
});
       
}).run();


       
// only apply bindings after everything's been loaded
        ko
.applyBindings(context);
   
});

Reply all
Reply to author
Forward
0 new messages