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.
import { Application } from "@nativescript/core";
// 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 } from "@nativescript/core";
// 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
startmethod automatically created an underlying rootFrameinstance and wrapped your page. The newrunmethod 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 likeTabViewandSideDrawer. Thestartis 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
Frame.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.
import { Frame } from "@nativescript/core";
const frame = Frame.getFrameById("myFrame");
// Navigate to page called “my-page”
frame.navigate("my-page");
import { Frame } from "@nativescript/core";
const frame = 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.xmlfile 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.
import { Frame } from "@nativescript/core";
const frame = 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()
});
import { Frame } from "@nativescript/core";
const frame = 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
import { Frame } from "@nativescript/core";
const frame = 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 { Frame } from "@nativescript/core";
const frame = 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
export function onNavigatedTo(args) {
const page = args.object;
page.bindingContext = page.navigationContext;
}
// sub-page.ts
import { Page } from "@nativescript/core";
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.
import { getViewById } from "@nativescript/core";
let count = 0;
export function buttonTap(args) {
count++;
let button = args.object;
let parent = button.parent;
if (parent) {
let lbl = getViewById(parent, "Label1");
if (lbl) {
lbl.text = "You tapped " + count + " times!";
}
}
}
import { View, Label, getViewById, EventData } from "@nativescript/core";
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.
var Button = ...
...
export { 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.
export function pageLoaded(args) {
var page = args.object;
}
import { EventData, Page } from "@nativescript/core";
// 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.
import { getViewById } from "@nativescript/core";
export function pageLoaded(args) {
var page = args.object;
var tabView1 = getViewById(page, "tabView1");
tabView1.selectedIndex = 1;
}
import { EventData, Page, TabView, getViewById } from "@nativescript/core";
// 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 { Label, Button, StackLayout } from "@nativescript/core";
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 __();
};
import { Button, StackLayout, View, Label, getViewById, EventData } from "@nativescript/core";
var MyControl = (function (_super) {
__extends(MyControl, _super);
function MyControl() {
_super.call(this);
var counter = 0;
var lbl = new Label();
var btn = new Button();
btn.text = "Tap me!";
btn.on(Button.tapEvent, function (args) {
lbl.text = "Tap " + counter++;
});
this.addChild(lbl);
this.addChild(btn);
}
return MyControl;
})(StackLayout);
export { 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, Page } from '@nativescript/core';
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");
export 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();
}
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 "@nativescript/core";
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
export 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;
}
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 "@nativescript/core";
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
import { Observable } from "@nativescript/core";
function getMessage(counter) {
if (counter <= 0) {
return "Hoorraaay! You unlocked the NativeScript clicker achievement!";
} else {
return counter + " taps left";
}
}
export 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;
}
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 { Builder } from "@nativescript/core";
let myComponentInstance = Builder.load({
path: "~/components/my-control",
name: "MyControl"
});
import { Builder } from "@nativescript/core";
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 { Builder } from "@nativescript/core";
let myComponentInstance = Builder.load({
path: "~/components/my-control",
name: "MyControl"
});
import { Builder } from "@nativescript/core";
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 { Builder } from "@nativescript/core";
let myComponentInstance = Builder.load({
path: "~/components/my-control",
name: "MyControl",
attributes: {
bindingContext: myBindingModel
}
});
import { Builder } from "@nativescript/core";
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 "@nativescript/core";
export function myTapHandler(args: GestureEventData) {
const context = args.view.bindingContext;
}
export function myTapHandler(args) {
const context = args.view.bindingContext;
}
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.
export function navigatingTo(args) {
const page = args.object;
page.bindingContext = { myTitle: "NativeScript is Awesome!"};
}
import { EventData, Page } from "@nativescript/core";
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = { myTitle: "NativeScript is Awesome!"};
}
NativeScript looks for the custom property in the
bindingContextof the current component or thebindingContextof 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.
export function navigatingTo(args) {
const page = args.object;
page.bindingContext = {
myProperty: "Some text",
myFunction: () => {
// Your code
}
};
}
import { EventData, Page } from "@nativescript/core";
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.
import { getViewById } from "@nativescript/core";
export function navigatingTo(args) {
const page = args.object;
page.bindingContext = { myItems: [{ name: "Name1" }, { name: "Name2" }, { name: "Name3" }] };
// Will work!
let listView1 = getViewById(page, "listView1");
// Will not work!
let label1 = getViewById(page, "label1");
}
import { Button, StackLayout, View, Label, getViewById, EventData, ListView } from "@nativescript/core";
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
Observableobject 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" />
...