Create a Mobile App in Visual Studio

To better understand how to implement applications using the DevExtreme SPA Framework and DevExtreme UI widgets, we recommend that you begin by creating a simple application, similar to the one described in the Your First Mobile App in Visual Studio tutorial. Using this step-by-step tutorial, you will create a single-page application that includes two views.

Once you complete the Your First Mobile App in Visual Studio tutorial, you can follow the steps below to learn how to add data to your simple application. You may notice that the order of application implementation is not common - an HTML view representation is developed first, and only then is JavaScript code implemented to provide data and business logic for the views. This concession is made intentionally to allow you to better understand the steps involved. That is not to say, however, that this approach to developing applications does not take place in the real world.

Download Code

Application Map

Before implementing an application, draw an application map to better understand which screens you need and what functionality you wish to include.

Application Map

Create an Application Project

  • From the main menu in Visual Studio, select File | New | Project... to get to the New Project dialog.

    New Project Dialog

  • In the Projects tree view, select DevExtreme. In the Templates list view, select the DevExtreme XX.X Multi-Channel Application template, specify the new solution's name (here Application1) and click OK. A Project Wizard will run.

  • In the first page of the Project Wizard, choose the platforms for which you want to create projects. Here, the Desktop and Mobile projects will be demonstrated for the sake of simplicity of this tutorial. You can learn more information on all the available project templates in the Multi-Channel Application topic.

    Choose Platform

  • In the second page of the Project Wizard, choose a navigation type for your application.

    Choose Layout

  • In the third page of the Project Wizard, you can enter a URL of the OData service that will be used as a data source in the application. In this application, use our sample OData service that we provide for instructional use. Press Discover and all the entities provided by the service will be listed in the page. Check Generate model, but do not check Generate views now, because most of the generated views will not be used in the tutorial's application. Moreover, it is better to implement views manually for the first time so that the next time you can generate views and modify them consciously and correctly.

    Specify OData Service

    If you have a data service of another type, you can skip this step and finish solution generation.

A solution with the following projects will be generated.

  • Desktop
    Used to implement a desktop application specific code.

  • Mobile
    Used to implement a mobile application specific code. This project can be packaged as an iOS, Android, WindowsPhone 8 application or a PhoneGap zip. For details, refer to the Packaging Tools article.

  • Shared
    Used to implement code that is common for Desktop and Mobile projects.

All the generated projects have a similar structure. This is the structure of the Basic DevExtreme project template. You have already learned about this structure in the Your First Mobile App in Visual Studio tutorial.

Multi-Channel Solution Structure

Pay attention to the data folder in the Shared project. In the db.js file, you can see that an ODataContext object is created. This object presents an entire OData service that you specified in the Project Wizard. The ODataContext object creates an ODataStore instance for each entity specified in the configuration object of the ODataContext object.

JavaScript
//See the application1.shared.config.js file
window.Application1 = $.extend(true, window.Application1, {
    "config": {
        "endpoints": {
            "db": {
                "local": "https://sampleservices.devexpress.com/Northwind.svc/",
                "production": "https://sampleservices.devexpress.com/Northwind.svc/"
            }
        },
        "services": {
            "db": {
                "entities": {
                    "Categories": { 
                        "key": "CategoryID", 
                        "keyType": "Int32" 
                    },
                    "Products": { 
                        "key": "ProductID", 
                        "keyType": "Int32" 
                    }
                }
            }
        }
    }
});
JavaScript
//See the data|db.js file
var serviceConfig = $.extend(true, {}, Application1.config.services, {
    db: {
        //Assing the local URL if the application runs locally (on a localhost)
        //or the production URL if the application runs externally
        url: endpointSelector.urlFor("db"),
        errorHandler: handleServiceError
    }
});
Application1.db = new DevExpress.data.ODataContext(serviceConfig.db);

Below, you will use the created ODataContext object and the ODataStore instances it provides to specify a data source for widgets on views.

Run the Mobile or Desktop project in any browser to test the application template. The application will be shown by the simulator - one of the tools that come with the DevExtreme VS extension. The simulator allows you to see an application on different devices using the required landscape and scale.

Run in Simulator

As you can see, the About view that is added by default is loaded to the application. In addition, a navbar is available because the "navbar" layout set is specified for the application (see the application1.config.js file in the Mobile and Desktop projects).

Create List Views

Begin with the "Categories" and "Products" views, which contain lists of categories and products respectively.

Categories View

Implement an HTML template for the "Categories" view.

  • Right-click the views folder of the Shared project in the Solution Explorer and choose Add | NewItem... in the context menu, which will invoke the Add New Item dialog. Within this dialog, choose the View template, specify "categories" as a name and press Add.

    Add View Dialog

  • In the invoked wizard, choose Create an empty view, because it is better now to implement a view manually so that you can then generate a view and modify it correctly.

    View Creation Wizard

  • As a result, you will get automatically generated categories.dxview and categories.js code files. Register these files within the application page - in the index.html file of the Desktop and Mobile projects.

    HTML
    <link rel="dx-template" type="text/html" href="views/categories.dxview"/>
    <script type="text/javascript" src="views/categories.js"></script>
  • Open the categories.dxview file. The View Designer will be invoked. In the simulator to the left, select view elements and remove them.

    NOTE: To see a view from the Shared project in the Designer's simulator, select another project to show the view in its context. For this purpose, use a corresponding combobox in the Device Settings Toolbar.

  • Add the List widget. To do this, drag the List item from the Toolbox to the view in the simulator. In the corresponding HTML code, you will see that this widget is added using List Knockout binding.

  • To add test data to the list, change the use of the dataSource configuration option of the List widget to the items option.
  • Define a template for the list items so that they have the same configuration and behavior. To do this, add a div element with the data-options attribute set to dxTemplate and set the name configuration option to "item". Use text binding to display the list item name.

Here is the code you will get.

HTML
<div data-options="dxView : { name: 'categories', title: 'Categories' } " >
    <div data-options="dxContent : { targetPlaceholder: 'content' } " >
        <div data-bind="dxList: { items: [
                  { id: 1, name: 'Beverages' },
                  { id: 2, name: 'Fruit' }
            ] }">
            <div data-options="dxTemplate : { name: 'item' } ">
                <div data-bind="text: name" ></div>
            </div>
        </div>
    </div>
</div>

The simulator to the left displays a message in the top yellow area: "Design view is out of sync with the Source view. Click here to synchronize the views." Click this area. The simulator will display the view defined in the code.

Categories List View

As you can see, the view's markup is placed to the "content" placeholder of the layout in which this view will be displayed. As a result, the view's HTML template is combined with the layout's HTML markup and the style sheets are applied to the HTML elements (including the List widget). This forms a so-called View from the MVVM (Model-View-ViewModel) pattern. The ViewModel for the "Categories" view will be described below. The Model is already generated as an ODataStore instance that provides access to the "Categories" entity of the OData service used in this application.

NOTE: The "Categories" view will not appear in the application's navigation automatically. You will see how to configure the application's global navigation below in this tutorial.

Products View

Implement an HTML template for the "Products" view.

  • Add the products.dxview and products.js code files in the same way you created the "Categories" view.

  • Register these files within the application page - the index.html file of the Desktop and Mobile projects.

  • Implement an HTML template for the "Products" view in the same manner as you did for the "Categories" view above.

Here is the code that you will get.

HTML
<div data-options="dxView : { name: 'products', title: 'Products' } " >
    <div data-options="dxContent : { targetPlaceholder: 'content' } " >
        <div data-bind="dxList: { items: [
                { id: 1, name: 'Whiskey', category_id: 1 },
                { id: 2, name: 'Cognac', category_id: 1 },
                { id: 3, name: 'Banana', category_id: 2 },
                { id: 4, name: 'Pineapple', category_id: 2 }
            ] }">
            <div data-options="dxTemplate : { name: 'item' } ">
                <div data-bind="text: name" ></div>
            </div>
        </div>
    </div>
</div>

NOTE: The "Products" view will not appear in the application's navigation automatically. You will implement navigation to this view from the "Categories" view below in this tutorial.

Create Detail Views

Implement an HTML template for the "ProductDetails" detail view.

  • Add the "ProductDetails" view in the same manner you added the "Categories" view. The name that is specified for the view in the Add New Item dialog will be used as a name of the view's JavaScript function and the view's markup. Specify the 'product_details' name in the Add New Item dialog since this name is used in the code below for the "ProductDetails" view.

  • Register the product_details.dxview and product_details.js files within the application page - the index.html file of the Desktop and Mobile projects.

  • Open the product_details.dxview file. In the invoked View Designer, add the "Id" and "Name" fields to the view. To help you organize several widgets into a coherent detail view, the framework supplies a set of CSS classes called fieldset. To add a fieldset to the view, drag the dxFieldset item from the Toolbox to the view in the simulator. A div element decorated with the dx-fieldset class will be added. The fieldset contains fields - div elements decorated with the dx-field class. A field's name is represented by a div element decorated with the dx-field-label class. A field's value is represented by a div element decorated with the dx-field-value class.

Here is the code that you will get after modifying the template code to provide the "Id" and "Name" fields.

HTML
<div data-options="dxView : { name: 'product_details', title: 'Product' } " >
      <div data-options="dxContent : { targetPlaceholder: 'content' } " >
            <div class="dx-fieldset">
                  <div class="dx-field">
                        <div class="dx-field-label">Id: </div>
                        <div class="dx-field-value" data-bind="text: 1"></div>
                  </div>
                  <div class="dx-field">
                        <div class="dx-field-label">Name: </div>
                        <div class="dx-field-value" data-bind="text: 'Banana'"></div>
                  </div>
            </div>
      </div>
</div> 

Add Actions

Add the actions that are planned on the application map to your views. Start by modifying the views' HTML markup, so that the actions become available for end-user manipulation. The description of how to define the functions implementing these actions will be given in the Add ViewModels tutorial section.

Handle a List Item Click

Use dxAction binding to navigate to a view by clicking a list item. dxAction binding allows you to add the click handler to an HTML element.

  • Use dxAction binding in the "Categories" view to navigate from the this view to the "Products" view when clicking a list item. Assign a string that specifies the URI to navigate to.

    HTML
    <div data-options="dxTemplate : { name: 'item' }" data-bind="dxAction: '#products/1'">
        <div data-bind="text: name"></div>
    </div>

    Here, the URI navigates to the "Products" view and passes the id of the selected category as the second parameter.

  • The string that is assigned to the dxAction binding must conform to the routing format declared for the application. Open your index.js file and ensure that the default routing format is ":view/:id".

    JavaScript
    Application1.app.router.register(":view/:id", { view: startupView, id: undefined });
  • Use dxAction binding in the "Products" view to navigate from the "Products" view to the "ProductDetails" view when clicking a list item. Use the same technique that you used for navigating from the "Categories" view to the "Products" view.

    HTML
    <div data-options="dxTemplate : { name: 'item' }" data-bind="dxAction: '#product_details/1'">
        <div data-bind="text: name"></div>
    </div>

Add a Search Button

Add a Search button to the "Products" view to search for a product using a combination of letters.

  • Open the products.dxview file. Press Add command on the strip at the bottom of the view's simulator; this will invoke the Add Command dialog. Specify "search" as the identifier of the command. In addition, add the "Search" command to the required command containers of the layouts in which the view will be displayed. For this purpose, set the command mapping properties as they are set in the image below.

    Add View Dialog

    To learn about the layouts used for views within the "Navbar" layout set, refer to the Ready-to-Use Layout Sets topic. To learn about the command containers that are contained in the built-in layouts, refer to the Built-in Layouts topic.

    Press OK. A div element will be added to the root of the view's markup. This element will contain the data-bind attribute set to dxCommand. Specify the following fields of the command's configuration object.

    • icon
      Use the "find" icon from the built-in icon library.
    • onExecute
      Assign unspecified to the onExecute field. Implement a function for this field when you have data bound to the application.

      HTML
      <div data-bind="dxCommand: { id: 'search', title: 'Search', icon: 'find', onExecute: undefined }" ></div>

    Check that command mapping settings specified in the Add Command dialog are set as the command mapping option of the htmlApplication object. See the index.js and application1.config.js files in the Mobile and Desktop projects.

  • Add a textbox for search input using the TextBox widget. To add the dxTextbox widget, drag the TextBox item from the Toolbox to the view in the simulator. A div element with the data-bind attribute set to TextBox will be added. To configure the widget, specify options within the configuration object.

    HTML
    <div data-options="dxView : { name: 'products', title: 'Products' } " >
        <div data-bind="dxCommand: { id: 'search', title: 'Search', icon: 'find', onExecute: undefined }" ></div>
        <div data-options="dxContent : { targetPlaceholder: 'content' } " >
            <div data-bind="dxTextBox: { mode: 'search', value: ''}"></div>
            <div data-bind="dxList: { items: [
                    { id: 1, name: 'Whiskey', category_id: 1 },
                    { id: 2, name: 'Cognac', category_id: 1 },
                    { id: 3, name: 'Banana', category_id: 2 },
                    { id: 4, name: 'Pineapple', category_id: 2 }
                ] }">
                <div data-options="dxTemplate : { name: 'item' }" data-bind="dxAction: '#product_details/1'">
                    <div data-bind="text: name"></div>
                </div>
            </div>
        </div>
    </div>

Back Button

According to the application map, there should be a 'Back' button that returns you to the previous view. You do not have to implement the 'Back' button. It is added automatically to the appropriate position on the current device (if there is no 'Back' button on the device). For details, refer to the Navigate Back in Mobile Apps topic.

Global Navigation

As you can see in the running application, the NavBar layout uses the NavBar widget for the application's global navigation. NavBar items (such as About in the template application) represent commands defined by the navigation object within the application's configuration object. Open the application1.config.js file from the Desktop and Mobile projects and specify the following set of commands.

JavaScript
window.Application1 = $.extend(true, window.Application1, {
    "config": {
        "layoutSet": "navbar",
        "animationSet": "default",
        "navigation": [
            {
                "title": "Categories",
                "onExecute": "#categories",
                "icon": "home"
            },
            {
                "title": "About",
                "onExecute": "#About",
                "icon": "info"
            }
        ]
    }
});

Set the "Categories" view - the first view displayed when the application runs. For this purpose, change the startupView variable to "categories" in the index.js file of each project.

JavaScript
var layoutSet = Application1.config.layoutSet,
    startupView = "categories";
//...
Application1.app = new DevExpress.framework.html.HtmlApplication({
    namespace: Application1,
    layoutSet: DevExpress.framework.html.layoutSets[Application1.config.layoutSet],
    animationSet: DevExpress.framework.html.animationSets[Application1.config.animationSet],
    navigation: Application1.config.navigation
});        
//...
Application1.app.router.register(":view/:id", { view: startupView, id: undefined });
Application1.app.navigate();

Add ViewModels

When Views are completed, the next step is to implement ViewModels. The View Creation Wizard added a JavaScript file for each view. The JavaScript file is currently empty. You have to write a JavaScript function named after the View.

Categories ViewModel

  • To provide data for the list items using a data service, change the use of the items option to the dataSource option in the dxList configuration object. Bind the dataSource option to the dataSource field of the ViewModel object.
  • Open the categories.js file. It contains the categories function. This function has the same name as the view's HTML template, so this function will be found and called when the "Categories" view is displayed. The object that is returned by the categories function is a ViewModel for the "Categories" view.
  • Assign a DataSource object to the dataSource field. Create this object using the ODataStore provided by the ODataContext object (see its description in the Create an Application Project section).
  • In the categories.dxview file, bind the list item text to the CategoryName field of the presented Category object. In addition, specify {CategoryID} as the second navigation parameter in the URI assigned to dxAction. In this instance, the CategoryID field value of the clicked category will be passed to the "Products" view.
HTML
<!-- Changes in the Categories View -->
<div data-bind="dxList: { dataSource: dataSource }">
    <div data-options="dxTemplate : { name: 'item' }" data-bind="dxAction: '#products/{CategoryID}'">
        <div data-bind="text: CategoryName"></div>
    </div>
</div>
JavaScript
Application1.categories = function (params) {
    var viewModel = {
        dataSource: new DevExpress.data.DataSource({
            store: Application1.db.Categories,
            map: function(item) {
                return new Application1.CategoryViewModel(item);
            }
        })
    };
    return viewModel;
};

Products ViewModel

  • Similar to the "Categories" view, use the dataSource option for the List widget. Bind this option to the ViewModel's dataSource field here as well.
  • Open the products.js file. It contains the products function. This function has the same name as the view's HTML template, and this function will be found and called when the "Products" view is displayed. The object that is returned by the products function is a ViewModel for the "Products" view.
  • Assign a DataSource object to the dataSource field. Create this object using the ODataStore provided by the ODataContext object (see its description in the Create an Application Project section). Specify the pageSize configuration option for the DataSource object to load 10 objects per page. In addition, specify the filter configuration option to load only Products of the Category that is passed as a parameter to this view.

    JavaScript
    Application1.products = function (params) {
        var viewModel = {
            dataSource: new DevExpress.data.DataSource({
                store: Application1.db.Products,
                map: function (item) {
                    return new Application1.ProductViewModel(item);
                },
                pageSize: 10,
                filter: ['CategoryID', '=', parseInt(params.id)]
            })
        };        
        return viewModel;
    };
  • In the products.dxview file, bind the list item text to the ProductName field of the displayed Product object. In addition, specify {ProductID} as the second navigation parameter in the URI assigned to dxAction. In this instance, the ProductID field value of the clicked category will be passed to the "Products" view.

  • Specify the visible option of the Search text box by binding it to the ViewModel's showSearch field. Make the showSearch field observable and set it to false by default. This makes the Search text box initially invisible, and provides the capability to make it visible by setting the showSearch field to true when required.
  • Bind the Search command's onExecute option to the ViewModel's find function. In this function, change the text box visibility state and make the text box empty.
  • Bind the Search text box value option to the searchString ViewModel field. Make this field observable and set an empty string to it by default. This field's value will change when the browser raises the 'search', 'change' or 'keyup' events. These events are specified as the text box valueUpdateEvent option value.
  • Update the ViewModel's searchString field to which the text box value is bound with a 500 millisecond delay after an end user stops input. Then, set a searchValue and call the load method for the DataSource object.
  • To specify the field of the Product objects that will be used to search the required object, set the searchExpr configuration option of the DataSource object.
HTML
<div data-options="dxView : { name: 'products', title: 'Products' } " >
    <div data-options="dxContent : { targetPlaceholder: 'content' } " >
        <div data-bind="dxTextBox: { mode: 'search', value: searchString, visible: showSearch, valueChangeEvent: 'search change keyup' }"></div>
        <div data-bind="dxList: { dataSource: dataSource }">
            <div data-options="dxTemplate : { name: 'item' }" data-bind="dxAction: '#product_details/{ProductID}'">
                <div data-bind="text: ProductName"></div>
            </div>
        </div>
    </div>
    <div data-bind="dxCommand: { id: 'search', title: 'Search', placeholder: 'Search...', icon: 'find', onExecute: find }" ></div>
</div> 
JavaScript
Application1.products = function (params) {
    var viewModel = {
        searchString: ko.observable(''),
        find: function () {
            viewModel.showSearch(!viewModel.showSearch());
            viewModel.searchString('');
        },
        showSearch: ko.observable(false),
        dataSource: new DevExpress.data.DataSource({
            store: Application1.db.Products,
            map: function (item) {
                return new Application1.ProductViewModel(item);
            },
            pageSize: 10,
            filter: ['CategoryID', '=', parseInt(params.id)],
            searchExpr: "ProductName"
        })
    };

    ko.computed(function () {
        return viewModel.searchString();
    }).extend({
        throttle: 500
    }).subscribe(function () {
        viewModel.dataSource.searchValue(viewModel.searchString());
        viewModel.dataSource.pageIndex(0);
        viewModel.dataSource.load();
    });        

    return viewModel;
};

ProductDetails ViewModel

  • Open the product_details.js file. It contains the product_details.js function. This function has the same name as the view's HTML template, and will be found and called when the "Product Details" view is displayed. The object that is returned by the product_details function is a ViewModel for the "Product Details" view.
  • Add the product field to the ViewModel object to provide access to the ProductViewModel object generated by the Project Wizard.
  • Bind the Id field in the detail view to the ViewModel's product.ProductID field.
  • Bind the Name field in the detail view to the ViewModel's product.ProductName field.
  • To provide data for the view's data fields, handle the viewShown event adding the viewShown field to the ViewModel object, and assign a handler to it. In the handler, get the required Product object using the byKey method and the ProductID passed as a parameter to the view.
HTML
<div data-options="dxView : { name: 'product_details', title: 'Product' } " >
    <div data-options="dxContent : { targetPlaceholder: 'content' } " >
        <div class="dx-fieldset">
            <div class="dx-field">
                <div class="dx-field-label">Id: </div>
                <div class="dx-field-value" data-bind="text: product.ProductID"></div>
            </div>
            <div class="dx-field">
                <div class="dx-field-label">Name: </div>
                <div class="dx-field-value" data-bind="text: product.ProductName"></div>
            </div>
        </div>
    </div>
</div>
JavaScript
Application1.product_details = function (params) {
    var viewModel = {
        product: new Application1.ProductViewModel(),
        viewShown: function () {
            Application1.db.Products.byKey(params.id).done(function(data) {
                viewModel.product.fromJS(data);
            });
        }
    };
    return viewModel;
};

This is the end of this tutorial, since the views that were planned at the beginning are ready.