Navigation and Routing

Similarly to regular web applications, a PhoneJS application uses URLs to change the current view. The framework tracks the URL fragment identifier. If the URL changes, the framework initiates a transition to the view whose name is encoded in the URL. In addition, the framework keeps track of all the invoked views and caches the view's rendered markup. As a result, when you go back to a previously visited view, it is displayed immediately without rendering or querying data.

Since the PhoneJS application has only one web page, the view name along with the associated parameters must be encoded/decoded from the fragment identifier of the application URL. The process of encoding and decoding URLs is called routing. Routing is governed by routing rules, and each application must provide a set of rules. Once you have defined routing rules, you can use URLs containing view names and their parameters to navigate between views in your application.

Declare a Routing

To declare a rule after instantiating the application object, access your application object's router and invoke the register method. This method takes the following parameters.

  • URL Pattern

    A routing rule's URL pattern specifies the parameters a URL must contain in order to be processed by the rule. Parameter names begin with a colon and are separated by slashes. For example, the following pattern will fit any URL containing three string values separated by slashes (e.g., "order/beverage/108").

    ":view/:category/:id"

    When a routing rule is used for decoding a URL used to navigate to a view, parameters are copied to the ViewModel along with their values. As a result, when navigating to the "order/beverage/108" URL from the previous example, the "order" view will be shown and the remaining parameters will be accessible via the object passed as the "order" function's parameter.

    JavaScript
    order = function(params) {
        var viewModel = {
            message: params.category + '( id: '+ params.id + ')' //returns 'beverage(id: 108)'
        };
        return viewModel;
    };

    In URL patterns, you can use constant values in place of parameters. For example, the ":view/Details" pattern will fit any URL containing two string values, the second of which is "Details".

    Note that even when you do not intend to pass any parameters via a URL, it must still be possible to determine the view to which you wish to navigate. As a result, the URL should contain a view parameter or a default view parameter value must be specified.

  • Default Parameter Values

    You can make a parameter optional by specifying its default value. In this case, even if a URL does not specify the parameter, the rule can still be used to process the URL. Default parameter values are specified as an object whose property names correspond to parameter names. The property values contain default parameter values.

    JavaScript
    { view: "home", category: undefined, id: undefined }
  • Parameter constraints

    A constraint specifies the range of values a parameter can accept. If a URL specifies a parameter value out of a rule's parameter constraint range, the rule will not be used to process the URL.

    The constraints are specified as an object whose property names correspond to parameter names. The property values contain constraint expressions. The value can be either a JavaScript regular expression or a string representation of the expression.

    JavaScript
    { id: /\d+/, category: "\\d+" }

The following code line is a sample declaration of a versatile routing rule.

JavaScript
myApplication.router.register(":view/:id/", { view: "home", id: undefined });

This routing rule can process an empty URL, in which case it will navigate to the "home" view. The rule can also process URLs with a single parameter specifying the view name; or the rule can process a two-parameter URL where the first parameter specifies the view name and the second parameter specifies an object identifier.

You can declare several routing rules in your application. In this instance, when a URL needs to be processed, the rules are evaluated in the order of declaration. If the URL fits a rule, the rule is used to process the URL. Otherwise, the remaining routing rules are evaluated. In the following code snippet, the first rule is used to process Product view URLs with one or two additional parameters. The second rule is used for all of the remaining views.

JavaScript
myApplication.router.register("Product/:categoryId/:id", { view: "Product", categoryId: "Beverages", id: undefined });
myApplication.router.register(":view/:id", { view: "home", id: undefined });

Navigate to a View

Once you have defined routing rules, you can use URLs containing view names and their parameters to navigate between views in your application. You can do this either by using navigation actions or by invoking the navigate method of your application's object. Navigation actions are defined by a URI string according to the registered routing rule, or by an object whose properties represent parameters of the registered routing rule. You can assign a navigation action to a command, widget or an HTML element. For details, refer to the Actions section.

HTML
<div data-bind="dxList: { dataSource: dataSource }">
    <!--Approach 1 - Assign a uri-->
    <div data-options="dxTemplate : { name: 'item' }" 
         data-bind="text: name, dxAction: '#product-details/{id}'" >
    </div>
    <!--Approach 2 - Assign an object
        <div data-options="dxTemplate : { name: 'item' }" 
             data-bind="text: name, dxAction: {view: 'product-details', id: id}" >
        </div>
    -->
</div>

In the code above, a list item click forces the URL to be formatted. The {id} string in the URL is replaced with the id field value of the current ViewModel. In the current context, the id field is set to the clicked item's id. When the URI changes, the framework navigates to it, displaying the specified view.

You can pass an object as a parameter when navigating to a view. For instance, this will allow you to combine several parameters in one parameter, and not list all of the parameters when registering a routing rule. To pass an object as a parameter, use the second approach - define an object whose fields represent uri parameters.

HTML
<div data-bind="dxList: { dataSource: dataSource }">
    <!--Approach 2 - Assign an object -->
    <div data-options="dxTemplate : { name: 'item' }" 
            data-bind="text: name, dxAction: {view: 'product-details', settings: {id: id, name: 'Ann'}}" >
    </div>
</div>

As an alternative to navigation actions, the navigate function of the application object can be used. This function ensures that the application is properly initialized and uses the navigation manager to change the current view. The following code snippet demonstrates a sample function that uses the navigate function to invoke an OrderItems view for a specified order identifier.

JavaScript
function goToOrder(orderId) {
    //Approach 1 - Assign a uri
    myApplication.navigate("OrderItems/" + orderId);
    //Approach 2 - Assign an object
    //myApplication.navigate({view: "OrderItems", id: orderId});
}

As you can see, the parameter of the navigate function can either be a string or an object. The object is useful when you need to pass an object as a parameter to the navigated view.

JavaScript
function goToOrder(orderId) {
    //Approach 2 - Assign an object
    myApplication.navigate({view: "OrderItems", settings: {id: orderId, name: 'Ann'}});
}

Note: When passing an object as a parameter, make sure that it is a small object that does not contain references to other objects, and that it can be serialized using the JSON.stringify function.

The navigate function can accept a second parameter specifying additional options. To learn more, refer to the Navigation History topic in this section.

Navigate Back

You don't have to add the "navigate back to the previous view" functionality in PhoneJS applications. The framework supports it out-of-the-box. First, there can be a hardware 'Back' button on the device or a web browser's 'Back' button, if the application is not run as a native package. This button forces the browser to navigate to the previous view. In response to a change in the browser's URL, the corresponding view is displayed. Second, the framework automatically adds the Back command to a view if there is a way to navigate to the previous view. The presence, type and place for the widget that displays the 'Back' command is determined by the current view's layout. For instance, the predefined iOS NavBar layout displays the 'Back' command as a toolbar item, and the predefined Android NavBar layout does not have any place for the 'Back' button since it has the 'Back' button built into the hardware.

JavaScript
DX.framework.CommandMapping.defaultMapping = {
    //...
    "ios-header-toolbar": {
        defaults: { showIcon: false, showText: true, location: "right" },
        commands: [
            //...
           { id: "back", location: "left" },
            //...
        ]
    },
    //...
}

To replace the auto-generated Back command with a custom one, define a command and set its id markup option to 'back'. If such a command is found, the framework does not generate the 'Back' button.

HTML
<div data-bind="dxCommand: { id: 'back', title: 'Categories', location: 'back', type: 'back', action: '#_back' }" ></div>

In the code above, the custom Back command uses the '#_back' registered function as an action. Generally, you can assign this function as an action to any command to perform the "navigate back to the previous view" functionality. You can see an example in the Your First Application demo.

Navigation History

When providing a navigation control for your application, you have the views to which end users can navigate from any view using this navigation control's items (read Add a Global navigation). These views are so-called root views. The first view invoked in the application is also made root. When you navigate to another view from a root view, and then navigate to one more view from the invoked view, and so on, the navigation history is saved to a navigation stack.

Navigation Stack

Several such navigation stacks can be created.

Several Navigation Stacks

A new navigation stack is created not only when clicking a navigation control item, but also when you call the navigate method of the HtmlApplication object passing true as the root parameter.

JavaScript
Application1.app.navigate('View1', { root: true });

Navigate to Root View from Stack

In addition to the root parameter, you can specify the target parameter to explicitly set a place for the invoked view in the navigation stack. The following values are supported for the target parameter:

  • 'blank' - adds the invoked view to the navigation history (the default value);
    Blank Target Navigation

  • 'current' - replaces the current view in the navigation stack with the invoked view;
    Current Target Navigation

The following code snippet demonstrates a sample function that uses the navigate function with two parameters to invoke an OrderItems view for a specified order identifier. The invoked view will replace the current view in the navigation history.

JavaScript
function goToOrder(orderId) {
    myApplication.navigate('OrderItems/' + orderId, { target: 'current' });
}

The Back button is available in each view in a stack except for the root view. This button displays the previous view in the stack.

Back in Navigation Stack

The state of each invoked view in a stack is saved. For instance, the values entered into edit boxes are saved, and the position in a scrolled list is also saved. So, when you press the Back button, the previous view in the stack is displayed in the last state.

If you return back to a previous view and invoke a new view from it, the history saved before will be removed from the cache.

New Branch from Navigation Stack

When pressing a navigation control item, the corresponding root view is displayed. If this view has been displayed before, it is restored from the cache. You can change this behavior and display the view that was displayed last in the stack based on this root view. For this purpose, set the navigateToRootViewMode option of the HtmlApplication object to keepHistory.