background image

Appcelerator Mvc : Routing

Now that we have went over the basics of my Appcelerator Mvc implementation, we can move onto more advanced features. For this post I will focus on routing. The post should be fairly short since the routing is fairly straight forward. As I mentioned in the first post on this framework, I borrowed heavily from the routing model used in Asp.Net. Although I don’t implement all the routing principles used in it’s model, I think what I have implemented should be more than enough for any Appcelerator application. So lets start with a default route:

	Mvc.mapRoute(
		"Default_Route",
		"{controller}.{action}.{text}", // could also be {controller}/{action}/{id}
		{
			controller : "Home",
			action : "Default",
			text : "Default Text"
		});

The first parameter here is the name of the route. I use this only for keeping track of various routes incase you wish to override a previously defined route. The next parameter is the important part of the route. This is used to define what variables can be passed to an action. Anything that resides inside curly braces is considered a ‘variable’. The last parameter is an object which defines the default values to be used with the specified. So if we are targeting the default route and we do something like this (Note: Mvc.start is normally only called once in app.js but we can use it to see the various actions being hit):

	Mvc.start(); // resolves to "Home.Default" passing "Default Text" to the 'text' parameter of the action
	Mvc.start("Default"); // resolves to "Home.Default" passing "Default Text" to the 'text' parameter of the action
	Mvc.start("Accounts"); // resolves to "Accounts.Default" passing "Default Text" to the 'text' parameter of the action
	Mvc.start("Home.Foo"); // resolves to "Home.Foo" passing "Default Text" to the text parameter of the action
	Mvc.start("Accounts.Bar.Value"); // resolves to "Accounts.Bar" passing "Value" to the text parameter of the action
	Mvc.start("Accounts", "Foo", 2); // resolves to "Accounts.Default" passing "Foo" to the first parameter and 2 to the second parameter

After seeing that example you should have a basic idea of how the routing works. In general you should make a “default” catch-all route to make things easier to work with. Now lets add two more routes to make things interesting (Note: these routes need to be defined before the “Default_Route” as we will check for a match in them first).

	Mvc.mapRoute(
		"Specific_Route",
		"DeleteAccount.{id}",
		{
			controller : "Account",
			action : "Delete"
		});

	Mvc.mapRoute(
		"Account_Route",
		"Account.{action}.{id}",
		{
			controller : "Account",
			action : "Default",
		});

You can see that we have two entirely different routes that we have added. The first one is very specific and only has one variable. The “DeleteAccount” portion does not map to a controller. In this case we take advantage of the ‘default’ parameters listed in the third argument. In the next route, you can see that it is similar to the default route except we do not set the ‘controller’ as a variable. Here are some examples of these routes in action:

	Mvc.start("DeleteAccount.1"); // resolves to "Account.Delete" passing 1 to the 'id' parameter
	Mvc.start("DeleteAccount", 1); // resolves to "Account.Delete" passing 1 to the 'id' parameter
	Mvc.start("Account.Foo.1"); // resolves to "Account.Foo" passing 1 to the 'id' parameter
	Mvc.start("Account.Bar", 1); // resolves to "Account.Bar" passing 1 to the 'id' parameter

So that is the routing portion of my framework in a nutshell. Like I said, it might be overkill but I am really enjoying how easy it is to get from window to window. In my next post I will show a “real world” example of using my framework. It will include joli, jsAutomapper, and some databinding. It basically shows how I set up my project and use the framework. Anyway, leave your comments/suggestions.



17 views shared on this article. Join in...

  1. daniel says:

    Thanks for writing this very cool framework!

    I’m a bit confused on how you actually call a controller. Can you provide an example of invoking a controller? For example, on a button click event, how would I invoke “User.Edit.123”?

    Thanks.

    • John Kalberer says:

      Yeah, I am currently in the middle of writing a much more in-depth post on using the framework with a much better explanation on how to use it. I am running into some iOS issues and I am working them out with another developer since I do not have a Mac to test on. If I can solve the problems I will post it today or tomorrow.

      To answer your question, you would call:

      this.action(“User.Edit.123”); or this.action(“User.Edit”, 123);

      Where “this” is referencing your View. For a button click you would do something like:

      var btn = Ti.UI.createButton({
      title : “Click”
      });
      var viewReference = this;
      bnt.addEventListener(“click”, function(e) {
      viewReference.action(“User.Edit”, 123);
      });

  2. Hi John,

    Thanks for your work so far. Hopefully you can enlighten me with an answer to what I hope is not a stupid question.

    I have a login controller with a single view containing a form.

    Upon submission, I’d like to process the login within a Login action of the same controller. Upon succesfull authentication, I’d like to redirect the user to a view rendered by an action of a different controller.

    Given that I do the authentication within the LoginController.js file, how do I load an action of a different controller from within the LoginController’s action code?

    Routing via self.action works perfectly from within a view’s code. But how does it work from within a controller ?

    Thanks for your answer!

    PS. Any way to tip you for your excellent work so far?

    • Hi

      Yes, this is a very easy thing to do.


      if(authenticated) {
      return this.View("Foo.Action"); // or 'Foo/Action' where Foo is the name of your controller
      }
      return this.View();

      Thanks for the compliments. No tip, but you can retweet or give me a review on the Appcelerator marketplace.

      John

      • Wouter Van den Bosch says:

        Cheers John,

        that is easy enough indeed.

        Kind regards,
        Wouter

      • Wouter Van den Bosch says:

        Hi again John,

        I must be doing something wrong here. Hopefully you have some time to point out what exactly.

        My two controllers are Login and Home. Login should eventually lead to a view in Home. So the default action leads to the Login controller, showing a nice login form.

        Both controllers are included in the app.js file as required and so far I have only one catch-all route defined, taking {controller}.{action}

        In Login, I have just one action so far ‘StartScreen’ which happily alerts a ‘Welcome’ string for this exercise.

        Now, when I connect a simple listener to the login screen’s login button I can succesfully call the StartScreen action from within the view code using :

        self.action('Home.StartScreen') // This successfully alerts the 'Welcome' string

        But when I move my logics back to the Login controller with the button calling a ‘Login’ action within the login controller things don’t work as I’d like.

        The Login action within the login controller will contain the user authentication (commented out for now) and call the Home.StartScreen action when successfull. With the Login action just containing the following line:

        return this.view("Home.StartScreen");

        I get the following warning in the console :

        [WARN] Exception in event callback. View Controller Home does not exist in Views collection

        Any clue as to where I might have done something wrong? The action is executed just fine through self.action, but failt to execute through return this.view.

        Once again thanks for having a look at this, in the middle of your crunch.

        • Hmmm, from the exception you show, the controller “Home” is not being resolved correctly. Can you step into the code when you call ‘return this.view(“Home.StartScreen”)’ and check the ‘routeData’ property of the variable ‘r’ on line 164 of mvc.js?

          If the ‘r.routeData.controller’ is not being populated correctly then I might have another javascript issue with iOS. I just attempted to navigate to another view from an Action in the Android emulator and it worked fine. Could you try using the base Mvc code and change the AccountsController.Default Action to return “Home.Default”? That would tell me if it is iOS.

          Anyway, the exception you are seeing being thrown is on line 170 of mvc.js. For some reason Mvc.find is working in one instance and not in another.

          I’ll try to test this on a Mac as soon as I can but I can’t promise anything.

          John

          • Wouter Van den Bosch says:

            Hi John,

            I’m getting the same error in my Android simulator, so from your response I gathered the problem is most-likely related to my own code. Especially because the routing works just fine from within the base MVC code (Should’ve checked that before posting my question here).

            In any case, I’ll post what i did wrong here for future reference.

          • Wouter Van den Bosch says:

            Hi again John,

            I’ve hit what I think is either an issue in either the framework as it is, or either a misconception in my understanding of it (The latter being most likely).

            I went back to the base MVC code to test whether I could call the default Home controller’s action from within the Accounts controller code.

            And indeed, when I change line #20 from:

            return this.view("Default", model);

            to

            return this.view("Home.Default", model);

            the application opens with the Home.Default view loaded.

            Now based on your previous answer I made some assumptions. As you said :


            if(authenticated) {
            return this.View("Foo.Action"); // or 'Foo/Action' where Foo is the name of your controller
            }
            return this.View();

            I figured indeed that we were talking actions here. I assume that return this.view("Home.Default", model); would call Home’s Default action and thus render the Default view. But it does not call the action. It renders the view called ‘Default’, skipping the action code alltogether.

            In my own implementation, the action I was calling, just had an alert(‘Hello’) so far, without calling for a view (I was going to fill that in as soon as I got the Hello message). Hence things not working in my implementation, and things seemingly working in the base code.

            Now a number of things are possible :

            a) I slightly misunderstood the way the framework works and it is indeed very normal that the view is called, and not the action. In that case I just need to start working with this in mind, passing on the correct models and such from within the originating controller code.

            or

            b) The framework is indeed not behaving as desirable and you consider this an ‘issue’ from which, at some point, when you have the time, a solution could be found.

            or

            c) None of the above makes sense and I probably tested things in the wrong way (could be!)

            What do you think. Are we talking an a) , b) or a c) here?

          • Yes, that is how it is supposed to work although I should have a way to call an Action from an Action.

            Go ahead and post the issue on github and I will take care of it — things are starting to clear up for me.

            In the meantime you can use this version of the framework mvc.js and call this.action(“Home.Foo”, model) from inside another action. (I didn’t have time to test so let me know if it doesn’t work)

          • Wouter Van den Bosch says:

            Hi John,

            I can confirm your revised mvc.js works as advertised. You should have spotted the issue already over at Github by now.

            A very big thanks for the swift reply. You are raking in Belgian beer credits at an impressive speed.

  3. How can we seperate the views for IPhone, Android, Tablet seperately? Should we do this at the app.js level or should it be at the view level?

    • I never thought about doing something like this when I created the framework. The best solution I can give you is to do it inside of the controller files. Create subfolders under the views for each type. When you create the view file you still follow the same pattern — Do not create a IPhone/Android/Tablet object. It still works like:

      (function(v) {
      // Create the ‘Home’ object if it doesn’t already exist
      if(!v.Home) {
      v.Home = {};
      }

      /*
      * Render the default page
      */
      v.Home.Default = function(model) {

      };
      }(Mvc.Views));

      Use whatever conditional you have and load the views like this. Here is how you should reference the files in your controller file:

      if(IPhone) {
      Ti.include(“/views/IPhone/Home/Default.js”);
      } else if(Android) {
      Ti.include(“/views/Android/Accounts/Default.js”);
      } else if(Tablet) {
      Ti.include(“/views/Tablet/Accounts/Default.js”);
      }

      John



Pings to this post

  1. […] John Kalberer add some more to his MVC framework – this time talking about routing. […]

  2. […] John Kalberer add some more to his MVC framework – this time talking about routing. […]


Leave a Reply

Your email address will not be published. Required fields are marked *

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>