DevExtreme jQuery/JS - Navigation and Routing

Similarly to regular web applications, a DevExtreme 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 a DevExtreme 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. The following example demonstrates a URL for navigating to the "product-details" view. In this URL, the id parameter is passed to the navigated view, according to pre-defined routing rules.

JavaScript
"#product-details/{id}"

As you can see, you can use format items within the strings that specify a URL. When formatting these strings, the format items will be replaced with the corresponding fields of the current ViewModel. In the example above, it is implied that the current ViewModel includes the id field.

JavaScript
home = function() {
    var viewModel = {
        id: 123 
    };
    return viewModel;
}

A URL can be defined by an object, in which case, that object's fields must present the values of the application's routing rule parameters. The following object is equivalent to the string above. In this object, a ViewModel field value is passed as a parameter.

JavaScript
{view: 'product-details', id: id}

Defining a URL by using an object is useful when you need to pass several parameters to the navigated view. You can combine several parameters to an object and register a single parameter in a routing rule instead of several ones.

JavaScript
{view: 'product-details', settings: {id: id, name: name}}
NOTE
When specifying 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 following list details where you can specify a URL (a string or an object) to navigate to a view.

  • Commands

    When defining a command, assign a URL (a string or an object) to the onExecute option. The view specified by the URL will be rendered when executing the command.

    HTML
    <div data-options="dxView: { name: 'home', title: 'Home' }">
        <!--Approach 1 - Assign a string-->
        <div data-bind="dxCommand: { id: 'myCommand', title: 'Show Contacts', location: 'create', onExecute: '#product-details/{id}'}"></div>
        <!--Approach 2 - Assign a object-->
        <div data-bind="dxCommand: { id: 'myCommand', title: 'Show Contacts', location: 'create', onExecute: {view: 'product-details', id: id}}"></div>
    </div>
  • Widgets

    When defining a widget, specify a URL (a string or an object) as a handler for a widget event (assign the URL to the corresponding option or pass it as a parameter to the on method). For a better UI design, navigate to views using the events that fire as a result of clicking a widget or a widget element.

    HTML
    <div data-options="dxView: { name: 'home', title: 'Home' }">
        <!--Approach 1 - Assign a string-->
        <div data-bind="dxButton: { text: 'Show Contacts', onClick: '#product-details/{id}' }"></div>
        <!--Approach 2 - Assign an object
            <div data-bind="dxButton: { text: 'Show Contacts', onClick: {view: 'product-details', id: id} }"></div>
        -->
    </div>
  • dxAction Binding

    You can navigate to a view when clicking any HTML element. For this purpose, use dxAction binding for that element and assign a URL (a string or an object) to that binding.

    HTML
    <div data-bind="dxList: { dataSource: dataSource }">
        <!--Approach 1 - Assign a string-->
        <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>

Whatever method you chose to navigate to a view with, the navigate function of the HtmlApplication object will be called internally. This function ensures that the application is properly initialized and uses the navigation manager to change the current view. You can call this function directly to navigate to a view from any place in your application. The following code snippet demonstrates a sample function that uses the navigate function to render the "product-details" view for a specified product identifier.

JavaScript
function goToProduct(id) {
    //Approach 1 - Pass a string
    myApplication.navigate("product-details/" + id);
    //Approach 2 - Pass an object
    //myApplication.navigate({view: "product-details", id: id});
}

The navigate function can accept a second parameter specifying additional options.

To learn how to build a navigation strategy in your application, refer to the Navigation History in Mobile Apps and Navigation in Web Apps topics in this section.

Navigate Back in Mobile Apps

Since the framework supports the "navigate back to the previous view" functionality in DevExtreme mobile applications out-of-the-box, you do not need to add it manually.

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.

JavaScript
DevExpress.framework.CommandMapping.defaultMapping = {
    //...
    "ios-header-toolbar": {
        defaults: { showIcon: false, showText: true, location: "after" },
        commands: [
            //...
           { id: "back", location: "before" },
            //...
        ]
    },
    //...
}
NOTE
If you run a non-packed DevExtreme application on Windows Phone 8 in a browser, the Back button will not navigate to the previous view. This is the Windows Phone browser's peculiarity. It can't navigate back within one page using 'hash-based' navigation. However, if you pack this application, everything will work properly on the device.

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: 'Back', location: 'before', type: 'back', onExecute: '#_back' }" ></div>

In the code above, the custom Back command uses the '#_back' registered function to navigate back to the previous view. Generally, you can assign this function to any command to perform the "navigate back to the previous view" functionality.

Navigation History in Mobile Apps

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.

Navigation in Web Apps

By default, the application object is configured so that the built-in navigation system treats the application as a mobile application. It creates navigation stacks based on "root" views and performs backwards navigation within the current stack. In web applications, the navigation system must behave in another manner. Web applications run in web browsers. So browser history should be used by the navigation system instead of building navigation stacks. To switch to this mode of the navigation system, set the mode option of the application object to 'webSite'.

JavaScript
window.MyApp = {};
MyApp.$(function() {
    MyApp.app = new DevExpress.framework.html.HtmlApplication({
        namespace: MyApp,
        mode: 'webSite'
    });
});

In this mode, each navigated view is added next to the previous view in the navigation history. However, you can change this behavior and replace the previous view by the navigated view in the navigation history. For this purpose, call the navigate() method passing an object with the target field set to 'current' as the second method parameter.

JavaScript
myApplication.navigate("OrderItems/", {target: 'current');

The browser's Back button should be the only possible way of navigating backwards in the browser's history. There should not be a Back button in a view. However, you may still need to perform a backwards navigation when clicking some view element. For this purpose, use the application's back() method. It will call the browser's Back method, which will navigate to the previous view in the navigation history.