User Interface Basics
The user interface of NativeScript mobile apps consists of pages. Typically, the design of the user interface is developed and stored in XML
files, styling is done via CSS and the business logic is developed and stored in JavaScript
or TypeScript
files. When you develop the user interface of your app, you can implement each application screen in a separate page or implement your application screens on a single page with a tab view. For each page, you need to have a separate XML
file that holds the layout of the page. For each XML
file that NativeScript parses, the framework also looks for a JavaScript
or TypeScript
file with the same name and executes the business logic inside it.
Declare the Home Page
Each NativeScript app must have a home page that loads when you launch the app. You need to explicitly set the home page for your app by calling the run
method of the Application
module and pass NavigationEntry
with the desired moduleName
.
The NativeScript navigation framework looks for an XML
file with the specified name, loads it and navigates to the respective page. If NativeScript discovers a JavaScript
or TypeScript
file with the same name, it executes the code inside it.
var application = require("tns-core-modules/application");
// Start the application. Don't place any code after this line as it will not be executed on iOS.
application.run({ moduleName: "my-page" });
import application = require("tns-core-modules/application");
// Start the application. Don't place any code after this line as it will not be executed on iOS.
application.run({ moduleName: "my-page" });
Note: Before NativeScript 4.0.0 the
start
method automatically created an underlying rootFrame
instance and wrapped your page. The newrun
method will set up the root element of the provided module as application root element. This effectively means that apart fromPage
, you can now have other roots of your app likeTabView
andSideDrawer
. Thestart
is now marked as deprecated.
Navigate to a Page
You can navigate between pages with the navigate
method of the Frame
class. The Frame
class represents the logical unit that is responsible for navigation between different pages. With NativeScript 4 and above each app can have one or more frames. To get a reference to a frame we can use getFrameById
method. Detailed information about navigation can be found in the dedicated article.
When you trigger navigation, NativeScript looks for an XML
file with the specified name, loads it and navigates to the respective page. If NativeScript discovers a JavaScript
or TypeScript
file with the same name, it executes the code inside it.
// To import the "tns-core-modules/ui/frame" module:
let getFrameById = require("tns-core-modules/ui/frame").getFrameById;
const frame = getFrameById("myFrame");
// Navigate to page called “my-page”
frame.navigate("my-page");
// To import the "tns-core-modules/ui/frame" module:
import { getFrameById } from "tns-core-modules/ui/frame";
const frame = getFrameById("myFrame");
// Navigate to page called “my-page”
frame.navigate("my-page");
<Frame id="myFrame"/>
Paths are relative to the application root. In the example above, NativeScript looks for a
my-page.xml
file in the app directory of your project (e.g.app/my-page.xml
).
Passing Binding Context while Navigating
You could provide bindingContext
automatically while navigating to a page. This will give you a simple way to make the context become the bindingContext
of the page on navigation. The way to do that is to set up the bindingContext
property, which points to your custom view model, on navigate
method.
// To import the "ui/frame" module and "main-view-model":
let getFrameById = require("tns-core-modules/ui/frame").getFrameById;
const frame = getFrameById("myFrame");
const HelloWorldModel = require("./main-view-model").HelloWorldModel;
// Navigate to page called “my-page” and provide "bindingContext"
frame.navigate({
moduleName: "my-page",
bindingContext: new HelloWorldModel()
});
// To import the "ui/frame" module and "main-view-model":
import { getFrameById } from "tns-core-modules/ui/frame";
const frame = getFrameById("myFrame");
import { HelloWorldModel } from "./main-view-model"
// Navigate to page called “my-page” and provide "bindingContext"
frame.navigate({
moduleName: "my-page",
bindingContext: new HelloWorldModel()
});
Passing and Receiving Custom Context
In cases where we want to pass a specific context and need more control than the automated bindingContext
, you could use the context
property in the navigatedEntry
object. The navigated page can obtain the passed context via the navigatedTo
event and the navigationContext
property.
Sending binding context from the main page.
// e.g. main-page.js
let getFrameById = require("tns-core-modules/ui/frame").getFrameById;
const frame = getFrameById("myFrame");
// Navigate to page called “sub-page” and provide "bindingContext"
frame.navigate({
moduleName: "sub-page",
context: { title: "NativeScript is Awesome!"}
});
// e.g main-page.ts
import { getFrameById } from "tns-core-modules/ui/frame";
const frame = getFrameById("myFrame");
// Navigate to page called “sub-page” and provide "bindingContext"
frame.navigate({
moduleName: "sub-page",
context: { title: "NativeScript is Awesome!"}
});
Recieving context from sub-page
// sub-page.js
function onNavigatedTo(args) {
const page = args.object;
page.bindingContext = page.navigationContext;
}
exports.onNavigatedTo = onNavigatedTo;
// sub-page.ts
import { Page } from "tns-core-modules/ui/page";
export function onNavigatedTo(args) {
const page = <Page>args.object;
page.bindingContext = page.navigationContext;
}
<!-- sub-page.xml -->
<Page xmlns="http://www.nativescript.org/tns.xsd" navigatedTo="onNavigatedTo">
<Label text="" textWrap="true" />
</Page>
Execute Business Logic
When you have a JavaScript
or a TypeScript
file in the same location with the same name as your XML
file, NativeScript loads it together with the XML
file. In this JavaScript
or TypeScript
file you can manage event handlers, bind context or execute additional business logic.
In this example of main-page.xml
, your page consists of a button. When you tap the button, the buttonTap
function is triggered.
<Page>
<StackLayout>
<Label id="Label1" text="This is Label!" />
<Button text="This is Button!" tap="buttonTap" />
</StackLayout>
</Page>
This example demonstrates a simple counter app. The logic for the counter is implemented in a main-page.js
or main-page.ts
file.
const view = require("tns-core-modules/ui/core/view");
let count = 0;
function buttonTap(args) {
count++;
let button = args.object;
let parent = button.parent;
if (parent) {
let lbl = view.getViewById(parent, "Label1");
if (lbl) {
lbl.text = "You tapped " + count + " times!";
}
}
}
exports.buttonTap = buttonTap;
import { EventData } from "tns-core-modules/data/observable";
import { getViewById } from "tns-core-modules/ui/core/view";
import { Label } from "tns-core-modules/ui/label";
import { View } from "tns-core-modules/ui/core/view";
let count = 0;
export function buttonTap(args: EventData) {
count++;
let button = <View>args.object;
let parent = button.parent;
if (parent) {
let lbl = <Label>getViewById(parent, "Label1");
if (lbl) {
lbl.text = "You tapped " + count + " times!";
}
}
}
To access variables or functions from the user interface, you need to declare them in the exports
object in the module. NativeScript sets each attribute value in the XML declaration to a respective property or an event of the component. If a respective property does not exist, NativeScript sets the attribute value as an expando object.
User interface components
NativeScript provides a wide range of built-in user interface components—layouts and widgets. You can also create your own custom user interface components. When NativeScript parses your XML
files, it looks for components that match a name in the module exports. For example, when you have a Button
declaration in your XML
file, NativeScript looks for a Button
name in the module exports.
var Button = ...
...
exports.Button = Button;
export let Button = ...
The default content components
The top-level user interface components are content components like pages and layouts. These content components let you arrange your interactive user interface components in specific ways.
Page
Your application pages (or screens) are instances of the page
class of the Page
module. Typically, an app will consist of multiple application screens.
You can execute some business logic when your page loads using the pageLoaded
event. You need to set the loaded
attribute for your page in your main-page.xml
.
<Page loaded="pageLoaded">
<!-- Page content follows here -->
</Page>
You need to handle the business logic that loads in a main-page.js
or main-page.ts
file.
function pageLoaded(args) {
var page = args.object;
}
exports.pageLoaded = pageLoaded;
import { EventData } from "tns-core-modules/data/observable";
import { Page } from "tns-core-modules/ui/page";
// Event handler for Page "loaded" event attached in main-page.xml
export function pageLoaded(args: EventData) {
// Get the event sender
const page = <Page>args.object;
}
TabView
With a tabview
, you can avoid spreading your user interface across multiple pages. Instead, you can have one page with multiple tabs.
The following sample main-page.xml
contains two tabs with labels.
<Page loaded="pageLoaded">
<TabView id="tabView1">
<TabView.items>
<TabViewItem title="Tab 1">
<TabViewItem.view>
<Label text="This is Label in Tab 1" />
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="Tab 2">
<TabViewItem.view>
<Label text="This is Label in Tab 2" />
</TabViewItem.view>
</TabViewItem>
</TabView.items>
</TabView>
</Page>
The respective main-page.js
or main-page.ts
loads the first tab by its ID and shows its contents.
var getViewById = require("tns-core-modules/ui/core/view").getViewById;
function pageLoaded(args) {
var page = args.object;
var tabView1 = getViewById(page, "tabView1");
tabView1.selectedIndex = 1;
}
exports.pageLoaded = pageLoaded;
import { EventData } from "tns-core-modules/data/observable";
import { getViewById } from "tns-core-modules/ui/core/view";
import { Page } from "tns-core-modules/ui/page";
import { TabView } from "tns-core-modules/ui/tab-view";
// Event handler for Page "loaded" event attached in main-page.xml
export function pageLoaded(args: EventData) {
// Get the event sender
var page = <Page>args.object;
var tabView1 = <TabView>getViewById(page, "tabView1");
tabView1.selectedIndex = 1;
}
ScrollView
Insert a ScrollView
inside your page to make the page or the content enclosed in the scrollView
scrollable.
<Page>
<ScrollView>
<!-- Scrollable content goes here -->
</ScrollView>
</Page>
StackLayout
Arrange the user interface components in your page in a horizontal or vertical stack using StackLayout
and its orientation
.
<Page>
<StackLayout orientation="horizontal">
<Label text="This is Label 1" />
<Label text="This is Label 2" />
</StackLayout>
</Page>
GridLayout
Arrange the user interface components in your page in a flexible grid area using GridLayout
.
<Page>
<GridLayout rows="*, auto" columns="250, *">
<Label text="This is Label in row 0, col 0" />
<Label text="This is Label in row 0, col 1" col="1" />
<Label text="This is Label in row 1, col 0" row="1" />
<Label text="This is Label in row 1, col 1" row="1" col="1" />
<Label text="This is Label in row 0, col 0" rowSpan="2" colSpan="2" />
</GridLayout>
</Page>
WrapLayout
Arrange your user interface components in rows or columns until the space is filled and then wrap them on a new row or column using WrapLayout
. By default, if orientation is not specified, WrapLayout
arranges items horizontally.
<Page>
<WrapLayout>
<Label text="This is Label 1" />
<Label text="This is Label 2" />
<Label text="This is Label 3" />
<Label text="This is Label 4" />
</WrapLayout>
</Page>
AbsoluteLayout
Arrange your user interface components by left/top coordinates using AbsoluteLayout
.
<Page>
<AbsoluteLayout>
<Label text="This is Label 1" left="30" top="70" />
</AbsoluteLayout>
</Page>
Custom Components
You can define your own XML namespaces to create custom user interface components. The custom components can be created via XML files or via code-behind JS/TS implementation.
Code-only Custom Component
The page using the custom component
The sample main-page.xml
is using a custom component defined in separate declarations in the app/components/my-control.ts
file (or *.js
if using plain JavaScript).
<!-- app/main-page.xml -->
<Page xmlns:customControls="components/my-control" navigatingTo="navigatingTo" class="page">
<customControls:MyControl />
</Page>
The custom component implementation
This sample custom component declared in app/components/my-control.ts
or app/components/my-control.js
exports the MyControl
variable, which creates a simple counter inside your main-page.xml
page.
// app/components/my-control.ts
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout/stack-layout";
import { Label } from "tns-core-modules/ui/label/label";
import { Button } from "tns-core-modules/ui/button/button";
export class MyControl extends StackLayout {
constructor() {
super();
let counter: number = 0;
const lbl = new Label();
const btn = new Button();
btn.text = "Tap me!";
btn.on("tap", (args) => {
lbl.text = "Tap " + counter++;
});
this.addChild(lbl);
this.addChild(btn);
}
}
// app/components/my-control.js
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var stackLayout = require("tns-core-modules/ui/layouts/stack-layout");
var label = require("tns-core-modules/ui/label");
var button = require("tns-core-modules/ui/button");
var MyControl = (function (_super) {
__extends(MyControl, _super);
function MyControl() {
_super.call(this);
var counter = 0;
var lbl = new label.Label();
var btn = new button.Button();
btn.text = "Tap me!";
btn.on(button.Button.tapEvent, function (args) {
lbl.text = "Tap " + counter++;
});
this.addChild(lbl);
this.addChild(btn);
}
return MyControl;
})(stackLayout.StackLayout);
exports.MyControl = MyControl;
When referring to code-only components in your pages with an xmlns
declaration, you should point it either to the code file with the component implementation or to the folder containing the files. In the latter case, you will have to add a package.json
file in the folder so that the file can be required properly.
XML-based Custom Component with a Code File
The page using the custom component
The sample main-page.xml
is using a custom component my-control.xml
and the my-control.ts
code-behind defined as a separate files in the app/components
folder.
<!-- app/main-page.xml -->
<Page xmlns:comps="components" navigatingTo="navigatingTo">
<comps:my-control />
</Page>
// app/main-page.ts
import { EventData } from 'tns-core-modules/data/observable';
import { Page } from 'tns-core-modules/ui/page';
import { HelloWorldModel } from './main-view-model';
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
// the page binding context will be accerss in components/my-toolbar
page.bindingContext = new HelloWorldModel();
}
// app/main-page.js
var main_view_model_1 = require("./main-view-model");
function navigatingTo(args) {
var page = args.object;
// the page binding context will be accerss in components/my-toolbar
page.bindingContext = new main_view_model_1.HelloWorldModel();
}
exports.navigatingTo = navigatingTo;
The custom component implementation
The custom component in app/components/my-control.xml
defines a Button, a Label with a related binding properties and tap function.
<!-- app/components/my-control.xml -->
<StackLayout class="p-20" loaded="onLoaded">
<Label text="This custom component binding is coming from the parent page" textWrap="true" />
<Label text="Tap the button (custom component)" class="h1 text-center"/>
<Button text="TAP" tap="{{ onTap }}" class="btn btn-primary btn-active"/>
<Label text="{{ message }}" class="h2 text-center" textWrap="true"/>
</StackLayout>
// app/components/my-control.ts
import { EventData } from "tns-core-modules/data/observable";
export function onLoaded(args: EventData) {
console.log("Custom Component Loaded");
// you could also extend the custom component logic here e.g.:
// let stack = <StackLayout>args.view;
// stack.bindingContext = myCustomComponentViewModel;
}
// app/components/my-control.js
function onLoaded(args) {
console.log("Custom Component Loaded");
// you could also extend the custom component logic here e.g.:
// let stack = args.view;
// stack.bindingContext = myCustomComponentViewModel;
}
exports.onLoaded = onLoaded;
The View Model used for bindings
The main-page
has a binding context set thought view model (MVVM pattern). The binding context can be accessed through the custom component as demonstrated.
// app/main-view-model.ts
import { Observable } from "tns-core-modules/data/observable";
export class HelloWorldModel extends Observable {
private _counter: number;
private _message: string;
constructor() {
super();
// Initialize default values.
this._counter = 42;
this.updateMessage();
}
get message(): string {
return this._message;
}
set message(value: string) {
if (this._message !== value) {
this._message = value;
this.notifyPropertyChange('message', value)
}
}
public onTap() {
this._counter--;
this.updateMessage();
}
private updateMessage() {
if (this._counter <= 0) {
this.message = 'Hoorraaay! You unlocked the NativeScript clicker achievement!';
} else {
this.message = `${this._counter} taps left`;
}
}
}
// app/main-view-model.js
var Observable = require("tns-core-modules/data/observable").Observable;
function getMessage(counter) {
if (counter <= 0) {
return "Hoorraaay! You unlocked the NativeScript clicker achievement!";
} else {
return counter + " taps left";
}
}
function createViewModel() {
var viewModel = new Observable();
viewModel.counter = 42;
viewModel.message = getMessage(viewModel.counter);
viewModel.onTap = function() {
this.counter--;
this.set("message", getMessage(this.counter));
}
return viewModel;
}
exports.createViewModel = createViewModel;
Dynamic Loading of Custom Components
Dynamic load of JavaScript/TypeScript component
Load a pure JavaScript component by finding it in the exports of the module. The component is specified by a path and its name. Then the code from the JavaScript file is executed.
import * as builder from "tns-core-modules/ui/builder";
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl"
});
let builder = require("tns-core-modules/ui/builder");
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl"
});
Dynamic load of XML with JavaScript/TypeScript component
Load the XML file with JavaScript code-behind by finding the specified XML filename through the specified path in the exports of the modules. JavaScript file with the same name will be required and served as code-behind of the XML.
import * as builder from "tns-core-modules/ui/builder";
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl"
});
let builder = require("tns-core-modules/ui/builder");
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl"
});
The UI builder will automatically load the CSS file with the same name as the component name and apply it to the specified page:
let myComponentInstance = builder.load({ path: "~/components/my-control", name: "MyControl", page: yourPageInstancex });
let myComponentInstance = builder.load({ path: "~/components/my-control", name: "MyControl", page: yourPageInstance });
Dynamic load and passing additional attributes
The attributes
option can be used to pass additional arguments.
import * as builder from "tns-core-modules/ui/builder";
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl",
attributes: {
bindingContext: myBindingModel
}
});
let builder = require("tns-core-modules/ui/builder");
let myComponentInstance = builder.load({
path: "~/components/my-control",
name: "MyControl",
attributes: {
bindingContext: myBindingModel
}
});
## Gestures
All [UI Gestures](/ui/components/gestures)
(gestures.md) can be defined in XML. For example:
```XML
<Page>
<Label text="Some text" tap="myTapHandler" />
</Page>
import { GestureEventData } from "tns-core-modules/ui/gestures";
export function myTapHandler(args: GestureEventData) {
const context = args.view.bindingContext;
}
function myTapHandler(args) {
const context = args.view.bindingContext;
}
exports.myTapHandler = myTapHandler;
Bindings
To set a binding for a property in the XML
, you can use double curly brackets syntax. All about binding can be found in the data-binding article
Property Binding
This sample main-page.xml
contains a simple label whose text will be populated when the page loads.
<Page>
<Label text="{{ myTitle }}" />
</Page>
The main-page.js
or main-page.ts
code file sets a bindingContext
for the page. The bindingContext
contains the custom property and its value. When NativeScript parses main-page.xml
, it will populate the custom name property with the value in the bindingContext
.
function navigatingTo(args) {
const page = args.object;
page.bindingContext = { myTitle: "NativeScript is Awesome!"};
}
exports.navigatingTo = navigatingTo;
import { EventData } from "tns-core-modules/data/observable";
import { Page } from "tns-core-modules/ui/page";
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = { myTitle: "NativeScript is Awesome!"};
}
NativeScript looks for the custom property in the
bindingContext
of the current component or thebindingContext
of its parents. By default, all bindings, defined in XML, are two-way bindings.
Event Binding
This sample main-page.xml
contains a button. The text for the button and the event that the button triggers are determined when the page loads from the matching main-page.js
or main-page.ts
file.
<Page navigatingTo="navigatingTo">
<Button text="{{ myProperty }}" tap="{{ myFunction }}" />
</Page>
This sample main-page.js
or main-page.ts
sets a bindingContext
for the page. The bindingContext
contains the custom property for the button text and its value and the custom function that will be triggered when the button is tapped. When NativeScript parses main-page.xml
, it will populate the button text with the value in the bindingContext
and will bind the custom function to the tap event.
function navigatingTo(args) {
const page = args.object;
page.bindingContext = {
myProperty: "Some text",
myFunction: () => {
// Your code
}
};
}
exports.navigatingTo = navigatingTo;
import { EventData } from "tns-core-modules/data/observable";
import { Page } from "tns-core-modules/ui/page";
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = {
myProperty: "Some text",
myFunction: () => {
// Your code
}
};
}
ListView Binding
You can use the double curly brackets syntax to bind the items to a listView
. You can also define a template with the itemTemplate
property from which NativeScript will create the items for your listView
.
Avoid accessing components by ID, especially when the component is part of a template. It is recommended that you use bindings to specify component properties.
NativeScript can create the items from a template when the listView
loads inside your page. When you work with templates and a listView
, keep in mind the scope of the listView
and its items.
In this sample main-page.xml
, the ListView consists of labels and each item will be created from a template. The text of each label is the value of the name property of the corresponding item.
<Page navigatingTo="navigatingTo">
<ListView id="listView1" items="{{ myItems }}">
<ListView.itemTemplate>
<Label id="label1" text="{{ name }}" />
</ListView.itemTemplate>
</ListView>
</Page>
The sample main-page.js
or main-page.ts
populates the bindingContext
for the page. In this case, the code sets values for the name property for each label. Note that because the ListView
and the Label have different scopes, you can access ListView by ID from the page, but you cannot access the Label by ID. The ListView
creates a new Label
for every item.
const view = require("tns-core-modules/ui/core/view");
function navigatingTo(args) {
const page = args.object;
page.bindingContext = { myItems: [{ name: "Name1" }, { name: "Name2" }, { name: "Name3" }] };
// Will work!
let listView1 = view.getViewById(page, "listView1");
// Will not work!
let label1 = view.getViewById(page, "label1");
}
exports.navigatingTo = navigatingTo;
import { EventData } from "tns-core-modules/data/observable";
import { Page } from "tns-core-modules/ui/page";
import { getViewById } from "tns-core-modules/ui/core/view";
import { ListView } from "tns-core-modules/ui/list-view";
import { Label } from "tns-core-modules/ui/label";
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = { myItems: [{ name: "Name1" }, { name: "Name2" }, { name: "Name3" }] };
// Will work!
const listView1 = <ListView>getViewById(page, "listView1");
// Will not work!
const label1 = <Label>getViewById(page, "label1");
}
To show some inner collection items inside ListView.itemTemplate
you can use a Repeater:
<Page>
<ListView items="{{ myItems }}">
<ListView.itemTemplate>
<Repeater items="{{ mySubItems }}" />
</ListView.itemTemplate>
</ListView>
</Page>
Binding Expressions
To set an expression as a value of a property in the XML
, you might as well go with the mustache syntax here.
NativeScript reevaluates your expression on every property change of the
Observable
object set forbindingContext
. This binding is a one-way binding—from the view model to the user interface.
The following sample main-page.xml
shows how to set an expression as the value for a label.
<Label text="{{ author ? 'by ' + author : '[no author]' }}" />
<Label text="{{ author || '[no author]' }}" />
Complex property paths
your.sub.property[name]
Logical not operator and comparators
!,<, >, <=, >=, ==, !=, ===, !==,||, &&
Unary and binary operators
+, -, *, /, %
Ternary operator
a ? b : c
Grouping
(a + b) * (c + d)
Constants
numbers, strings, null, undefined
Platform-specific declarations
To declare a platform-specific property value or platform-specific component in the XML
, you can use the following syntax:
Platform-specific property value
<Page>
<TextField ios:editable='False' android:editable='True' />
</Page>
Platform-specific component declaration
<Page>
<ios>
<TextField />
</ios>
<android>
<Label />
</android>
</Page>
You cannot nest platform tags!
Lowercase-dashed component declaration
Since the release of NativeScript 1.3, you can declare your UI using the lowercase-dashed syntax:
<page>
<scroll-view>
<stack-layout>
<label ctext="Label" />
<button text="Button" tap="tap" />
...