Create a Mobile App in Visual Studio

To better understand how DevExtreme application implementation works, 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, along with simple actions used to navigate between these views.

Once you complete the Your First Mobile App in Visual Studio tutorial, you can utilize 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 invoke 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 ("Application1" in this example) and click OK. The Project Wizard will run.

  • On the first page of the Project Wizard, choose the platforms for which you want to create projects. In this example, the Desktop and Mobile projects will be demonstrated for the sake of simplicity for this tutorial. You can obtain more information on all of the available project templates in the Create a Multi-Channel Solution article.

    Choose Platform

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

    Choose Layout

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

    Specify OData Service

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

A solution with the following projects will be generated.

  • Desktop
    Used to implement desktop application-specific code.

  • Mobile
    Used to implement mobile application-specific code. This project can be packaged as an iOS, an Android, a WindowsPhone 8, a Tizen application or a PhoneGap zip. For details, refer to the Packaging 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 the 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": "http://sampleservices.devexpress.com/Northwind.svc/",
                "production": "http://sampleservices.devexpress.com/Northwind.svc/"
            }
        },
        "services": {
            "db": {
                "entities": {
                    "Categories": { 
                        "key": "CategoryID", 
                        "keyType": "Int32" 
                    },
                    "Products": { 
                        "key": "ProductID", 
                        "keyType": "Int32" 
                    }
                }
            }
        }
    }
});
//See the data|db.js file
var serviceConfig = $.extend(true, {}, Application1.config.services, {
    db: {
        //Assign 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 included in the DevExtreme VS extension. The simulator allows you to see an application on different devices with 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" navigation strategy 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 | New Item... in the context menu, which will invoke the Add New Item dialog. In this dialog, choose the View template, specify "categories" as the name and click 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.

  • Add the dxList widget. To do this, drag the dxList item from the Toolbox to the view in the simulator. In the corresponding HTML code, you will see that this widget is added using dxList Knockout binding.
  • To add test data to the list, use the items option of the widget's configuration object.
  • 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>

Now, 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 in 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 dxList 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.

Products View

Implement an HTML template for the "Products" view.

  • Add the products.dxview and products.js code files in the same manner you as you did for the "Categories" view above.

  • 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.

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>

Create Detail Views

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

  • Add the "ProductDetails" view in the same way you added the "Categories" view. The name that is specified for the view in the Add New Item dialog will be used as the name of the view's JavaScript function and markup. Specify the name in the Add New Item dialog as 'product_details', 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 in 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 are available for end-user manipulation. Then there will be a description of how to define the functions that will be performed when executing these actions.

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 to navigate from the "Categories" 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 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. Drag the dxCommand item from the Toolbox to the view in the simulator. 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 command's options by setting the configuration object fields.

    • Specify an identifier for the command.
    • Use the "find" icon from the built-in icon library.
    • Assign unspecified to the action field. Add the action implementation once you have data bound to the application.
  • Add a textbox for search input using the dxTextBox widget. To add the dxTextbox widget, drag the dxTextBox item from the Toolbox to the view in the simulator. A div element with the data-bind attribute set to dxTextBox will be added. To configure the widget, specify options within the configuration object.

    HTML
    <div data-options="dxView : { name: 'products', title: 'Products' } " >    
        <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 data-bind="dxCommand: { id: 'search', title: 'Search', icon: 'find', action: undefined }" ></div>      
    </div>
  • Add the "Search" command to the required command containers of the layouts in which the view will be displayed. For this purpose, use application command mapping. To learn about the layouts used for views within the "Navbar" navigation strategy, refer to the Predefined Navigation Types topic. To learn about the command containers that are contained in the built-in layouts, refer to the Built-in Layouts topic.

    JavaScript
    Application1.app = new DevExpress.framework.html.HtmlApplication({
        //...
        commandMapping: {
            "ios-header-toolbar": {                
                commands: [
                    { id: "search", location: 'right', showText: false }
                ]
            },
            "android-footer-toolbar": {
                commands: [
                    { id: "search", location: 'center', showText: false }
                ]
            },
            "tizen-footer-toolbar": {
                commands: [
                      { id: "search", location: 'center', showText: false }
                ]
            },
            "generic-header-toolbar": {
                commands: [
                    { id: "search", location: 'right', showText: false }
                ]
            },
            "win8-phone-appbar": {
                commands: [
                    { id: "search", location: 'center', showText: true }
                ]
            },
        }
    });

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 for the current device (if there is no 'Back' button on the device). For details, refer to the Navigate Back topic.

Global Navigation

As you can see in the running application, the NavBar layout uses the dxNavBar 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 app.config.js file and specify the following set of commands.

JavaScript
window.Application1 = $.extend(true, window.Application1, {
    "config": {
        "navigationType": "navbar",
        "navigation": [
            {
                "title": "Categories",
                "action": "#categories",
                "icon": "home"
            },
            {
                "title": "About",
                "action": "#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 navigationType = Application1.config.navigationType,
    startupView = "categories";
//...
Application1.app = new DevExpress.framework.html.HtmlApplication({
    namespace: Application1,
    navigationType: navigationType,
    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 usage 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
<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

  • To provide data for the list items using a data service, use the dataSource option for the dxList widget like you did in the "Categories" view. Bind this option to the ViewModel's dataSource field.
  • 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 from 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 presented Category 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 textbox 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 textbox initially invisible, and provides the capability to make it visible by setting the showSearch field to true when required.
  • Bind the Search command's action option to the ViewModel's find function. In this function, change the textbox visibility state and make the textbox empty.
  • Bind the Search textbox 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 textbox valueUpdateEvent option value.
  • Update the ViewModel's searchString field to which the textbox 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, valueUpdateEvent: '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', action: 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;
};