Question about setting up component based pages with routes

I am working on a single page web application using canjs and requirejs, this is the first app of this scale for me. I am wondering on the best way to create a ‘page system’, meaning binding a page to a certain route. So when the route changes I load the correct page. Each of my pages is a can.Component.

My biggest issue with the way that I have set this up is that I will be limited by the current ‘route model’ of being ‘:page/:subpage/:id’’. I would end up running into issues if I needed my route url to be lets say five backslashes long like ‘tire/inventory/quote/new/123’.

I’m thinking maybe, instead of having the ‘:page/:subpage/:id’, have all the possible pages hard codded, like each specific route is linked to a specific ‘component page’. I think that this would most likely be the most flexible and easy to work with idea.

I just feel like what I am doing is wrong and I would love to have some feed back. Thank you!

define([
    'jquery',
    'can/util/library',
    'can/component',
    'can/view',
    'can/route',
    'can/map/delegate',
    'scope',
    'pages/error/404',
    'pages/fork/inventory',
    'pages/fork/form',
    'pages/attachment/inventory',
    'pages/attachment/form',
    'pages/tire/inventory',
    'pages/tire/form'
], function( $, can, Component, view, route, delegate, Scope ){

    var renderPage = function( element, template ){
        var renderer = can.stache( template );
        var fragment = renderer({
            route: can.route.attr(),
            scope: Scope.getScope()
        });
        element.find( '.app-bench' ).empty().html( fragment );
    };

    var loadPage = function( element ){
        var template = '';
        var page = can.route.attr( 'page' );
        var subpage = can.route.attr( 'subpage' );
        var pageFound = false;
		switch( page ){
			case 'fork':
				switch( subpage ){
					case 'inventory':
						template = '<page-fork-inventory></page-fork-inventory>';
						pageFound = true;
						break;
					case 'add':
					case 'edit':
						template = '<page-fork-form></page-fork-form>';
						pageFound = true;
						break;
				}
				break;
			case 'attachment':
				switch( subpage ){
					case 'inventory':
						template = '<page-attachment-inventory></page-attachment-inventory>';
						pageFound = true;
						break;
					case 'add':
					case 'edit':
						template = '<page-attachment-form></page-attachment-form>';
						pageFound = true;
						break;
				}
				break;
			case 'tire':
				switch( subpage ){
					case 'inventory':
						template = '<page-tire-inventory></page-tire-inventory>';
						pageFound = true;
						break;
					case 'add':
					case 'edit':
						template = '<page-tire-form></page-tire-form>';
						pageFound = true;
						break;
				}
				break;
		}
		if( !pageFound ) {
			template = '<page-error-404></page-error-404>';
		}
		renderPage( element, template );
    };

    return Component({
        tag: 'app-bench',
        template: can.view( 'app/views/bench.stache' ),
        events: {
            destroy: function(){

            },
            inserted: function( element, event ){
                loadPage( element );
                can.route.delegate( 'page,subpage,id', 'set', function( ev,newVal,oldVal,from ){
                    loadPage( element, element );
                });
                can.route.delegate( 'page,subpage', 'set', function( ev,newVal,oldVal,from ){
                    if( !can.route.attr( 'id' ) ){
                        loadPage( element, element );
                    }
                });
            }
        }
    });

});

Have you looked into using DoneJS? AFAIK, DoneJS wires this all together for you and provides a bunch of niceties like app and component generators. I’m new to this stuff as well, so maybe I’m off base, but with DoneJS you would have an app.js like:

import DefineMap from "can-define/map/";
import route from "can-route";
import "can-route-pushstate";

const AppViewModel = DefineMap.extend({
  route: "string",
  page: "string",
  title: {
    value: 'my-app',
    serialize: false
  }
});

route('/:page', { page: 'home' });

export default AppViewModel;

And an index.stache:

<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
    <can-import from="./app" export-as="viewModel" />
    <can-import from="./loading.component" />

    {{#switch page}}
      {{#case "home"}}
        <can-import from="./app/home/home" can-tag="app-loading">
          <app-home/>
        </can-import>
      {{/case}}
      {{#case "photo"}}
        <can-import from="./app/photos/photos" can-tag="app-loading">
          <app-photos/>
        </can-import>
      {{/case}}
    {{/switch}}

    {{#switch env.NODE_ENV}}
      {{#case "production"}}
        <script src="{{joinBase 'node_modules/steal/steal.production.js'}}"  main="my-app/index.stache!done-autorender"></script>
      {{/case}}
      {{#default}}
        <script src="/node_modules/steal/steal.js"></script>
      {{/default}}
    {{/switch}}
  </body>
</html>

Then when you visit / or /home or /photos the respective component is selectively loaded. Perhaps you are avoiding DoneJS for learning purposes or just want to wire it up with CanJS yourself. Either way hopefully this helps give some insight into how you might accomplish your SPA.

And here are the routings of the two major examples:

Place My Order:

Which gets as complex as http://place-my-order.herokuapp.com/restaurants/brunch-place/order

BitBalls:

Which gets as complex as http://bitballs.herokuapp.com/tournaments/2

1 Like

Ok, thank you. It looks like I am heading in the right direction. :slight_smile:

If anyone has any other methods of doing so, please post!

I didn’t see this earlier reply, It looks like you can do this exact thing in CanJS. Thanks!

Yep, you can. CanJS is a part of DoneJS.

No worries and it’s not your fault; my first reply was put into moderation by the spam filter :disappointed_relieved: