Warning: This article was last updated on June 23, 2015, and as such, many of the examples are now out of date with newer NativeScript releases. You’re welcome to read through the article, but we recommend developers get started with NativeScript on either our JavaScript tutorial, or our TypeScript & Angular 2 tutorial.
NativeScript is a framework that lets you use JavaScript to build mobile applications utilizing platform specific, native API calls. It supports iOS, Android, and (as of February 2015) Windows Universal.
In this article, you will learn how to build a native mobile app using NativeScript. If you’d like some background on how NativeScript works first, you can read NativeScript – a Technical Overview before getting started.
This article is about the NativeScript library itself, and assumes you have a NativeScript development environment up and running. Specifically, you must
If you don’t have all of this ready, start by heading over to the NativeScript getting started page to get everything you need set up. If you’re using the NativeScript CLI, the NativeScript documentation has everything you need to get up and running for Windows or OS X or Linux.
Good? Alright, let’s get started.
The NativeScript framework provides you with different building blocks to structure your project. This is why it is best to approach it in steps, one feature at a time. In this article you’ll learn about the following features:
If you want to see the final result, you can check the code at any time at https://github.com/sebawita/GettingStarted.
For clarity I added components folder (inside the app folder) where I am going to put all examples.
Here is how your folder structure should look.
Now you are ready to create your first NativeScript app.
Add helloWorldView.xml to the components folder, with the following code:
<Page>
<Label text="Hello World" />
</Page>
Open app.js and change application.module to components/helloWorldView.
Your app.js code should look as follows:
var application = require("application");
application.mainModule = "components/helloWorldView";
application.cssFile = "./app.css";
application.start();
Build the app and deploy it to your device or emulator (for instructions see the getting started page, Step 4).
And voila! You have your very first Native app built with NativeScript.
<Page> tag can only contain one item. Adding more items to the page will break it. In the next example we’ll examine how to add multiple items.application.module is used to specify the starting page.In this example you are going to add a few UI components. Since the <Page> element can only contain one component you’re going to need a component that can hold and organise other components (a container). We’ll use StackLayout since it’s easy to use.
To explore other containers see the documentation area and Demystifying NativeScript Layouts
In app.js change the line that sets the mainModule to:
application.mainModule = "components/signUpView";
Add signUpView.xml to the components folder with the following contents:
<Page loaded="loadSignUpView">
<StackLayout>
<Label text="Name:" />
<TextField id="name" width="200"/>
<Label text="Email:" />
<TextField id="email" width="200"/>
<Label text="Twitter Handle:" />
<TextField id="twitterHandle" text="@" width="200"/>
<Button id="signUpButton" text="Sign Up" width="100"/>
<Label id="result" text=" " textWrap="true"/>
</StackLayout>
</Page>
The bit worth noting is loaded="loadSignUpView". loaded is an event that NativeScript triggers each time a page is loaded. The definition of loadSignUpView is located in the JavaScript file (see below).
Add signUpView.js to the components folder with the following contents:
var view = require("ui/core/view");
function onLoad(args) {
var page = args.object;
var nameTextField = view.getViewById(page, "name");
var emailTextField = view.getViewById(page, "email");
var twitterHandleTextField = view.getViewById(page, "twitterHandle");
var signUpButton = view.getViewById(page, "signUpButton");
var resultLabel = view.getViewById(page, "result");
signUpButton.on("tap", function () {
var result = "";
if(nameTextField.text === "" || emailTextField.text === "" || twitterHandleTextField.text === "")
result = "Error: All fields required";
else
result = "Success!!!\nName: " + nameTextField.text + "\nEmail:" + emailTextField.text + "\nTwitter Handle: " + twitterHandleTextField.text;
resultLabel.text = result;
});
}
exports.loadSignUpView = onLoad;
require function loads modules in NativeScript. In this case it loads a view module linked with the current page, which gives you access to UI components on the page.exports variable exposes functions and attributes to the elements on the page. In this case it exposes the loadSignUpView function (which corresponds to line 1 in signUpView.xml).view.getViewById retrieves a UI component from the page.tap event adds code to be called when the button referenced by signUpButton is tapped.Calling an element by its id and manually getting its text is a bad practice, as it ties the business logic of your app to the specific UI components. It also forces you to know the id of each UI component in your JavaScript code. Luckily NativeScript provides a more elegant approach: binding UI properties to attributes of a JavaScript object.
In this example you will create a journey cost calculator. The user will enter distance, miles per gallon and fuel cost, and the app will calculate the cost for the journey.
In app.js change the line that sets the mainModule to:
application.mainModule = "components/journeyCalculatorView";
Add journeyCalculatorView.js to the components folder with the following contents:
var view = require("ui/core/view");
var journeyInfo = {
distance: "24",
mpg: "35",
fuelCost: "120.5"
};
var page;
exports.loadSignUpView = function(args) {
page = args.object;
page.bindingContext = journeyInfo;
}
exports.calculate = function() {
var distance = parseFloat(journeyInfo.distance);
var milesPerGallon = parseFloat(journeyInfo.mpg);
var milesPerLitre = milesPerGallon / 4.54609;
var fuelCost = parseFloat(journeyInfo.fuelCost) / 100; // cost in £
var costPerMile = fuelCost / milesPerLitre;
var journeyCost = distance * costPerMile;
var resultLabel = view.getViewById(page, "result");
resultLabel.text = "Journey cost £" + journeyCost.toFixed(2)
+ "\nCost per mile: £" + costPerMile.toFixed(2);
}
journeyInfo object’s attributes using {{ attribute name }}journeyInfo object, rather than getting explicit references to UI components and setting their text property.Add journeyCalculatorView.xml to the components folder with the following contents:
<Page loaded="loadSignUpView">
<StackLayout>
<StackLayout orientation="horizontal">
<Label text="Distance" width="50"/>
<TextField text="{{distance}}" width="100"/>
<Label text="Miles" />
</StackLayout>
<StackLayout orientation="horizontal">
<Label text="MPG" width="50" />
<TextField text="{{mpg}}" width="100"/>
<Label text="MPG" />
</StackLayout>
<StackLayout orientation="horizontal">
<Label text="Fuel Cost" width="50" />
<TextField text="{{fuelCost}}" width="100"/>
<Label text="Pence/Liter" />
</StackLayout>
<Button tap="calculate" id="calculateButton" text="Calculate" width="100"/>
<Label id="result" textWrap="true"/>
</StackLayout>
</Page>
journeyInfo object, are created using {{ attributeName }}.text={{ miles + "miles" }}.The observable object is the missing piece in the binding puzzle. Its main purpose is to notify the UI about any relevant (bound) changes so that the UI can update itself.
In the previous example, if the journeyInfo was an observable object, you wouldn’t need to retrieve the result label to display the result on the page. Instead you would update the journeyInfo result and the UI would display the new value automatically.
In this example you will create a small game. In the game there are 10 numbers (1-10) in a random order. The player is presented with a number, and needs to guess whether the next number is higher or lower. The game needs to display the current card, the score, and the number of cards left. Finally, it must allow users to start a new game. Let’s see how this is built.
In app.js change the line that sets the mainModule to:
application.mainModule = "components/upDownView";
Create upDownViewModel.js in the components folder with the following contents:
var observable = require("data/observable");
var upDownViewModel = new observable.Observable();
upDownViewModel.noOfCards = 10;
upDownViewModel.cards = [];
upDownViewModel.currentCardIndex = 0;
upDownViewModel.currentCard = 0;
upDownViewModel.nextCard = 0;
upDownViewModel.score = 0;
upDownViewModel.streak = 0;
upDownViewModel.cardsLeft = 0;
upDownViewModel.startNewGame = function() {
this.set("score", 0);
this.set("streak", 0);
this.shuffleCards();
this.getFirstCard();
}
upDownViewModel.goHigher = function() {
if(!this.hasMoves())
return;
if(this.currentCard < this.nextCard)
this.goodGuess();
else
this.badGuess();
this.getNextCard();
}
upDownViewModel.goLower = function() {
if(!this.hasMoves())
return;
if(this.currentCard > this.nextCard)
this.goodGuess();
else
this.badGuess();
this.getNextCard();
}
upDownViewModel.getFirstCard = function() {
this.currentCardIndex = -1;
this.getNextCard();
}
upDownViewModel.getNextCard = function() {
this.currentCardIndex = this.currentCardIndex +1;
index = this.currentCardIndex;
this.set("currentCard", this.cards[index]);
this.nextCard = this.cards[index+1];
this.set("cardsLeft", this.noOfCards - index -1);
}
upDownViewModel.hasMoves = function() {
return this.cardsLeft > 0;
}
upDownViewModel.goodGuess = function() {
this.set("streak", this.streak + 1);
this.set("score", this.score + this.streak);
}
upDownViewModel.badGuess = function() {
this.set("streak", 0);
}
upDownViewModel.shuffleCards = function() {
var cardDeck = [];
for(var i=0; i<this.noOfCards; i++) {
cardDeck.push(i+1);
}
var shuffledCards = [];
while(cardDeck.length > 0) {
var index = getRandom(cardDeck.length);
var card = cardDeck.splice(index, 1)[0];
shuffledCards.push(card);
}
this.set("cards", shuffledCards);
}
module.exports = upDownViewModel;
function getRandom (max) {
return Math.floor(Math.random() * max);
}
startNewGame, goHigher and goLower functions.The above code is self-contained and is completely separated from the UI. Its sole purpose is to provide the game logic.
Create upDownView.js in the components folder with the following contents:
var view = require("ui/core/view");
var model = require("./upDownViewModel");
exports.loadUpDownView = function(args) {
var page = args.object;
page.bindingContext = model;
}
upDownViewModel.js file.Create upDownView.xml with the following contents:
<Page loaded="loadUpDownView">
<StackLayout>
<Button tap="{{startNewGame}}" text="New Game"/>
<Label text="{{'Cards Left: ' + cardsLeft}}" />
<Label text="{{'Score (Streak):' + score + ' ('+ streak +') '}}" />
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Button tap="{{goLower}}" text="Lower" />
<Label text="{{currentCard}}" />
<Button tap="{{goHigher}}" text="Higher" />
</StackLayout>
</StackLayout>
</Page>
set("attributeName", value) sets the value of the attributeName attribute and notifies all bound properties in the UI. This should be used on attributes that are expected to be bound in the UI (like score, currentCard and cardsLeft).myObject.attributeName = value) sets the value of the attribute, but it doesn’t notify the UI. This should be used on attributes that are not meant to be displayed in the UI (like cards, nextCard and currentCardIndex).The game from the previous example works, but the UI isn’t all that attractive. Now is a good time to introduce NativeScript CSS styling and to make the game look better.
Add upDownViewWithStyle.css to the components folder, with the following code:
.info {
font-size: 30;
color: #2E4F8A;
}
.infoLabel {
font-size: 22;
color: #99ABD6;
}
.card {
color: #D63326;
font-size: 100;
width: 100;
}
Button {
vertical-align: center;
background-color: #037AC4;
color: #8CE8FC;
font-size: 30;
}
In app.js change the line that sets the mainModule to:
application.mainModule = "components/upDownViewWithStyle";
Create upDownViewWithStyle.js in the components folder with the following contents:
var view = require("ui/core/view");
var viewModel = require("./upDownViewModel");
exports.loadUpDownView = function(args) {
var page = args.object;
page.bindingContext = viewModel;
}
Create upDownViewWithStyle.xml with the following code:
<Page loaded="loadUpDownView">
<StackLayout>
<Button tap="{{startNewGame}}" text="New Game" horizontalAlignment="center" cssClass="actionButton"/>
<GridLayout rows="auto">
<Label text=" Cards Left:" cssClass="infoLabel" horizontalAlignment="left" />
<Label text="Score (Streak): " cssClass="infoLabel" horizontalAlignment="right" />
</GridLayout>
<GridLayout rows="auto">
<Label text="{{' ' + cardsLeft}}" cssClass="info" horizontalAlignment="left" />
<Label text="{{score + ' ('+ streak +') '}}" cssClass="info" horizontalAlignment="right" />
</GridLayout>
<StackLayout orientation="horizontal" horizontalAlignment="center">
<Button tap="{{goLower}}" text="Lower" cssClass="actionButton" />
<Label text="{{currentCard}}" cssClass="card" />
<Button tap="{{goHigher}}" text="Higher" cssClass="actionButton" />
</StackLayout>
</StackLayout>
</Page>
cssClass that corresponds to a class name defined in upDownViewWithStyle.css.cssClass attribute because their styling is defined by the Button selector in upDownViewWithStyle.css.view-name.css to add styling to the specific view-name.js view (i.e. helloWorldView.js -> helloWorldView.css).upDownViewWithStyle.css follows the CSS structures and naming conventions. However only a small subset of CSS properties are available for use at the moment: font-size, color, background-color, etc.upDownViewModel for this example, as the styling of the UI is separate from the logic of the game.Now you should have a working example of a NativeScript app with bindings and CSS styling. Hopefully these examples have prepared you to build your own NativeScript apps.
There are other things that you will need to know in order to build a successful app such as page navigation, adding 3rd-party native libraries, and adding platform-specific code. Don’t worry – we’ll be covering those topics (and more) in the near future.
For more information on NativeScript check out the docs.
Header image courtesy of Opassande
Sebastian Witalec