Create a Mobile App in Visual Studio

Before you begin creating applications using DevExtreme, start with a simple application. The documentation on the PhoneJS framework supplies the Your First Application tutorial, which will guide you through creating a single-page application including two views and simple actions to navigate between these views. While you create an application using just a single HTML page, this tutorial will explain the basics of how DevExtreme applications are implemented.

The steps below will take you through creating a sample application, which includes working with data. 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 what you are doing, and why you are doing it. However, this approach to developing applications applies to real-world scenarios as well.

In this tutorial, you will not have to implement the entire application skeleton manually from scratch. You have already done this in the Your First Application tutorial. In this tutorial, you can use the project template and design-time features supplied with DevExtreme for Visual Studio instead.

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 Basic Application template, specify the new solution's name ("Application1") and click OK. You will see a project with the specified name within the Solution Explorer. This project includes the following.

    • css - a folder with common and platform-specific stylesheet files
    • data - a folder intended to place everything that is related to data, e.g., variables to which data is loaded/stored
    • js - a folder with all the JavaScript libraries required for the application
    • layouts - a folder with the files representing the predefined layouts that come with the framework
    • views - a folder with two sample views - "home" and "about"
    • app.config.js - a file that includes the configuration object used to initialize the application
    • index.css - a file with application stylesheets
    • index.html - a file where all the resources required for the application are linked
    • index.js - a file where the HtmlApplication object is created and configured
  • Run the application 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 views that are added by default are loaded to the application and you can navigate between them using the navbar. The NavBar layout is designed for each platform. So, when you switch between devices, the appropriate layout and stylesheets are applied, and the application looks native.

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. For this purpose, use the sample view markup that is defined in the home.dxView file of the application's template. Generally, the home.dxView file could have the .html extension. When a file has a .dxView extension, however, that file can be opened with the DevExtreme View Designer. Open the home.dxView file, which will result in the display of the template HTML code for the view and the simulator displaying this view. Change the view content in the following way.

  • In the simulator, 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: 'home', title: 'Home' } " >
    <div class="home-view"  data-options="dxContent : { targetPlaceholder: 'content' } " >
        <div data-bind="dxList: { items: [
                  { id: 1, name: 'Beverages' },
                  { id: 2, name: 'Fruit' }
            ] }">
            <div data-options="dxTemplate : { name: 'item' } " data-bind="text: name" ></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 in the code above, the view's markup is placed in the "content" placeholder of the "NavBar" layout, which is used by default (see the defaultLayout option specified for the application in index.js). 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) patern. The ViewModel and Model for the "Categories" view is described below.

Products View

Implement an HTML template for the "Products" view.

  • Right-click the views folder in the Solution Explorer and choose Add | NewItem... in the context menu, which will invoke the Add New Item dialog.

    Categories List View

  • Within the invoked dialog, choose the View template, specify "products" as a name and press Add. As a result, you will get automatically generated products.dxView, products.js and products.css code files. These files will be registered within the index.html file automatically.

  • Implement an HTML template for the "Products" view in the same manner as you did for the "Categories" (Home) 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' } " data-bind="text: name" ></div>
        </div>
    </div>
</div>

NOTE: The function implemented in the added JavaScript file must have the same name as the value of the name configuration option of the view's markup. Since the name given to a view in the Add New Item dialog is used to name the fake function in the JavaScript file, check that the name configuration option of the view's markup has the same name. Alternatively, you can change the function's name, but keep it similar to the view's name in the markup.

Create Detail Views

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

  • Add the "ProductDetails" view in the same manner you added the "Products" 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.
  • 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 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 the dxAction binding to navigate to a view by clicking a list item. The dxAction binding allows you to add the click handler to an HTML element.

  • Use the 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="text: name, dxAction: '#products/1'"></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 change the default routing format to ":view/:id".

    JavaScript
    Application1.app.router.register(":view/:id", { view: "home", id: undefined });
  • Use the 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="text: name, dxAction: '#product_details/1'"></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 configuration object fields.

    • Specify a location for the command within the view's layout.
    • Use the "find" icon that is built into the style sheets included in the application template (see the css folder).
    • Assign unspecified to the action field. Add the action implementation when you have data bound to the application.

    The command will be represented by the widget that is used by the view's layout to display commands from the specified location on the current device. The NavBar layout, which is used for the "Products" view by default, will display the command as a button using the appearance and location native to the device on which the application will run.

  • 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="text: name, dxAction: '#product_details/1'" ></div>
        </div>
    </div>
    <div data-bind="dxCommand: { title: 'Search', placeholder: 'Search...', location: 'create', icon: 'find', action: undefined }" ></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 place 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 Home and 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": {
        "defaultLayout": "navbar",
        "navigation": [
            {
                "title": "Home",
                "action": "#home",
                "icon": "home"
            },
            {
                "title": "About",
                "action": "#about",
                "icon": "info"
            }
        ]
    }
});

Add ViewModels

When Views are completed, the next step is to implement ViewModels. If you notice, the template has added a JavaScript file for each view and has added a <script> tag referencing it to the index.html file automatically. The JavaScript file is currently empty. You have to write a JavaScript function, named the same as the View.

Categories ViewModel

  • Open the home.js file. It contains the home function. It has the same name as the view's HTML template, and this function will be found and called when the "Categories" (Home) view is displayed. The object that is returned by the home function is a ViewModel for the "Categories" view.
  • To provide data for the list items using a data service, change the use of the items option to the dataSource option. Bind the dataSource option to the dataSource field of the ViewModel object. In this step, assign the array of objects that was previously used for the items option to the dataSource field.
  • Specify {id} as the second navigation parameter in the URI assigned to dxAction. In this instance, the id 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="text: name, dxAction: '#products/{id}'"></div>
</div>
JavaScript
Application1.home = function (params) {
    var viewModel = {
        dataSource: [
            { id: 1, name: 'Beverages' },
            { id: 2, name: 'Fruit' }
        ]
    };
    return viewModel;
};

Products ViewModel

  • Implement the products function in the newly created products.js file using the home function as a template.
  • Similar to the "Categories" view, use the dataSource option bound to the ViewModel's dataSource field here as well. In addition, set {id} as a parameter in the URI assigned to the dxAction option.
  • Add the categoryId field to the ViewModel and set it to the currently selected category, obtained from the navigation parameter.
  • 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 show an alert window.
  • 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.
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="text: name, dxAction: '#product_details/{id}'"></div>
        </div>
    </div>
    <div data-bind="dxCommand: { title: 'Search', placeholder: 'Search...', location: 'create', icon: 'find', action: find }" ></div>
</div> 
JavaScript
Application1.products = function (params) {
    var viewModel = {
        searchString: ko.observable(''),
        find: function () {
            viewModel.showSearch(!viewModel.showSearch());
            alert('searching');
        },
        showSearch: ko.observable(false),
        categoryId: params.id,
        dataSource: [
            { 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 }
        ]
    };
    return viewModel;
};

ProductDetails ViewModel

  • Implement the product_details function in the newly created product_details.js file using the home function as a template.
  • Bind the Id field in the detail view to the ViewModel's id field. Set the ViewModel's id field to the value passed as the id parameter value during navigation from the "Products" view.
  • Bind the Name field in the detail view to the ViewModel's name field. Make the ViewModel's name field observable, so that it is possible to set it to a value taken from the data service (see the next step).
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: id"></div>
            </div>
            <div class="dx-field">
                <div class="dx-field-label">Name: </div>
                <div class="dx-field-value" data-bind="text: name"></div>
            </div>
        </div>
    </div>
</div>
JavaScript
Application1['product_details'] = function(params) {
    var viewModel = {
        id: params.id,
        name: ko.observable('')
    };
    return viewModel;
};

Bind to Real Data

The last step is learning to retrieve data from a real database. This lesson will use a sample data service published for instructional use.

Bind Data to the Categories List

The dxList widget works with a DataSource object that has specific methods that are required during the widget's life cycle. The array that you assigned to the dataSource field of the "Categories" ViewModel above was actually used internally to create the DataSource object. This array was returned by the DataSource's load function. Now, when you need to get data using our sample service, create the DataSource object explicitly, and assign it to the ViewModel's dataSource option.

To create a DataSource object, use the DevExpress.data.createDataSource method. Pass an object with the defined load function as a parameter. Within the load function, check to see that the widget's data is currently refreshed, using the refresh field of the object passed as the function's parameter. If the data is being refreshed, get the Category objects using the $.get('http://sampleservices.devexpress.com/api/Categories') request. Return a deferred object so that data can be loaded asynchronously.

Although the procedures defined are generally sufficient to provide data, you will need to perform mapping as well. The fields of the objects that are returned by the service do not correspond to the ViewModel fields to which the "Categories" view elements are bound. The CategoryName field that comes from the server must be mapped to the ViewModel's name field, and the CategoryID field must be mapped to the ViewModel's id field.

JavaScript
Application1.home = function (params) {
    var viewModel = {
        dataSource: DevExpress.data.createDataSource({
            load: function (loadOptions) {
                if (loadOptions.refresh) {
                    var deferred = new $.Deferred();
                    $.get('http://sampleservices.devexpress.com/api/Categories')
                    .done(function (result) {
                        var mapped = $.map(result, function (data) {
                            return {
                                name: data.CategoryName,
                                id: data.CategoryID
                            }
                        });
                        deferred.resolve(mapped);
                    });
                    return deferred;
                }
            }
        })
    };
    return viewModel;
};

Bind Data to the Products List

  • Create a DataSource object for the Products list in the same way you did for the Categories List. Refrain, however, from requesting all data at once, and instead request a single page each time. For this purpose, pass an object with request parameters as the second parameter of the $.get request. Set the skip and take parameters to the required values. In addition, set the categoryId parameter to the ViewModel's categoryId field value, to specify the category whose products are requested.

  • 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 call the reload method of the dataSource object.

  • Implement the Search action in the Products view. When an end user inputs letters for a search, the Products list reloads data. Within the load function, use the searchString field value as one of the request parameters. In addition, make the textbox empty when clicking the Search button. To do this, assign an empty string to the ViewModel's searchString field.

JavaScript
Application1.products = function (params) {
    var skip = 0;
    var PAGE_SIZE = 10;
    var viewModel = {
        searchString: ko.observable(''),
        find: function () {
            viewModel.showSearch(!viewModel.showSearch());
            viewModel.searchString('');
        },
        showSearch: ko.observable(false),
        categoryId: params.id,
        dataSource: DevExpress.data.createDataSource({
            load: function (loadOptions) {
                if (loadOptions.refresh) {
                    skip = 0;
                }
                var deferred = new $.Deferred();
                $.get('http://sampleservices.devexpress.com/api/Products',
                    {
                        categoryId: viewModel.categoryId,
                        skip: skip,
                        take: PAGE_SIZE,
                        searchString: viewModel.searchString()
                    })
                .done(function (result) {
                    skip += PAGE_SIZE;
                    var mapped = $.map(result, function (data) {
                        return {
                            name: data.ProductName,
                            id: data.ProductID
                        };
                    });
                    deferred.resolve(mapped);
                });
                return deferred;
            }
        })
    };
    ko.computed(function () {
        return viewModel.searchString();
    }).extend({
        throttle: 500
    }).subscribe(function () {
        viewModel.dataSource.reload();
    });
    return viewModel;
};

Get Object for the ProductDetails View

To get the name of the Product object whose identifier is passed as a URL parameter, request the Product object from the sample service using the $.get() function. Assign the ProductName field value of the returned object to the ViewModel's name field.

JavaScript
Application1['product_details'] = function (params) {
    var viewModel = {
        id: params.id,
        name: ko.observable('')
    };
    $.get('http://sampleservices.devexpress.com/api/Products/' + viewModel.id)
    .done(function (data) {
        viewModel.name(data.ProductName);
    });        
    return viewModel;
};