Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon

The Project Structure

Save for later
  • 14 min read
  • 08 Jan 2016

article-image

In this article written by Nathanael Anderson, author of the book Getting Started with NativeScript, we will see how to navigate through your new project and its full structure. We will explain each of the files that are automatically created and where you create your own files. Then, we will proceed to gain a deeper understanding of some of the basic components of your application, and we will finally see how to change screens.

In this article, we will cover the following topics:

  • An overview of the project directory
  • The root directory
  • The application components

(For more resources related to this topic, see here.)


Project directory overview


By running the nativescript create crossCommunicator command, the command creates a nice structure of files and folders for us to explore.

First, we will do a high level overview of the different folders and their purposes and touch on the important files in those folders. Then, we will finish the overview by going into depth about the App directory, which is where you will spend pretty much your entire time in developing an application. To give you a good visual overview, here is what the project hierarchy structure looks like:

project-structure-img-0

The root directory


In your Root folder, you will see only a couple of directories. The package.json file will look something like this:

{

  "nativescript": {

    "id": "org.nativescript.crossCommunicator",

    "tns-android": {

      "version": "1.5.0"

    },

    "tns-ios": {

      "version": "1.5.0"

    },

  },

  "dependencies": {

    "tns-core-modules": "1.5.0"

  }

}


This is the NativeScript master project configuration file for your entire application. It basically outlines the basic information and all platform requirements of the project. It will also be modified by the nativescript tool when you add and remove any plugins or platforms. So, in the preceding package.json file, you can see that I have installed the Android (tns-android) and iOS (tns-ios) platforms, using the nativscript platform add command, and they are both currently at version 1.5.0. The tns-core-modules dependency was added by the nativescript command when we created the project and it is the core modules.

Changing the app ID
Now, if you want this to be your company's name instead of the default ID of org.nativescript.yourProjectName, there are two ways to set the app ID. The first way is to set it up when you create the project; executing a nativescript create myProjName --appid com.myCompany.myProjName command will automatically set the ID value. If you forget to run the create command with a --appid option; you can change it here. However, any time you change the option here, you will also have to remove all the installed platforms and then re-add the platforms you are using. This must be done as when each platform is added because it uses this configuration id while building all the platform folders and all of the needed platform files.

The package.json file


This file contains basic information about the current template that is installed. When you create a new application via the nativescript create command, by default, it copies everything from a template project called tns-template-hello-world. In the future, NativeScript will allow you to choose the template, but currently this is the only template that is available and working. This template contains the folders we discussed earlier and all the files that we will discuss further. The package.json file is from this template, and it basically tells you about the template and its specific version that is installed. Le's take a look at the following code snippet:

{

  "name": "tns-template-hello-world",

  "main": "app.js",

  "version": "1.5.0",

... more json documentation fields...

}


Feel free to modify the package.json file so that it matches your project's name and details. This file currently does not currently serve much purpose beyond template documentation and the link to the main application file.

License


The License file that is present in the the App folder is the license that the tns-template-hello-world is under. In your case, your app will probably be distributed under a different license. You can either update this file to match your application's license or delete it.

App.js


Awesome! We have finally made it to the first of the JavaScript files that make up the code that we will change to make this our application. This file is the bootstrap file of the entire application, and this is where the fun begins. In the preceding package.json file, you can see that the package.json file references this file (app.js) under the main key. This key in the package.json file is what NativeScript uses to make this file the entry point for the entire application.

Looking at this file, we can see that it currently has four simple lines of code:

var application = require("application");

application.mainModule = "main-page";

application.cssFile = "./app.css";

application.start();


The file seems simple enough, so let's work through the code as we see it. The first line loads the NativeScript common application component class, and the application component wraps the initialization and life cycle of the entire application.

The require() function is the what we use to reference another file in your project. It will look in your current directory by default and then it will use some additional logic to look into a couple places in the tns_core_modules folder to check whether it can find the name as a common component.


Now that we have the application component loaded, the next lines are used to configure what the application class does when your program starts and runs. The second line tells which one of the files is the main page. The third line tells the application which CSS file is the main CSS file for the entire application. And finally, we tell the application component to actually start. Now, in a sense, the application has actually started running, we are already running our JavaScript code. This start() function actually starts the code that manages the application life cycle and the application events, loads and applies your main CSS file, and finally loads your main page files.

If you want to add any additional code to this file, you will need to put it before the application.start() command. On some platforms, nothing below the application.start() command will be run in the app.js file.

The main-page.js file


The JavaScript portion of the page is probably where you will spend the majority of your time developing, as it contains all of the logic of your page. To create a page, you typically have a JavaScript file as you can do everything in JavaScript, and this is where all your logic for the page resides. In our application, the main page currently has only six lines of code:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
var vmModule = require("./main-view-model");

function pageLoaded(args) {

  var page = args.object;

  page.bindingContext = vmModule.mainViewModel;

}

exports.pageLoaded = pageLoaded;


The first line loads the main-view-model.js file. If you haven't guessed yet, in our new application, this is used as the model file for this page. We will check out the optional model file in a few minutes after we are done exploring the rest of the main-page files.

An app page does not have to have a model, they are totally optional. Furthermore, you can actually combine your model JavaScript into the page's JavaScript file. Some people find it easier to keep their model separate, so when Telerik designed this example, they built this application using the MVVM pattern, which uses a separate view model file. For more information on MVVM, you can take a look at the Wikipedia entry at https://en.wikipedia.org/wiki/Model_View_ViewModel.


This file also has a function called pageLoaded, which is what sets the model object as the model for this page. The third line assigns the page variable to the page component object that is passed as part of the event handler. The fourth line assigns the model to the current page's bindingContext attribute. Then, we export the pageLoaded function as a function called pageLoaded.

Using exports and module.exports is the way in which we publish something to other files that use require() to load it. Each file is its own independent blackbox, nothing that is not exported can be seen by any of the other files. Using exports, you can create the interface of your code to the rest of the world. This is part of the CommonJS standard, and you can read more about it at the NodeJS site.

The main-page.xml file


The final file of our application folder is also named main-page; it is the page layout file. As you can see, the main-page.xml layout consists of seven simple lines of XML code, which actually does quite a lot, as you can see:

<Page  
loaded="pageLoaded">

  <StackLayout>

    <Label text="Tap the button" cssClass="title"/>

    <Button text="TAP" tap="{{ tapAction }}" />

    <Label text="{{ message }}" cssClass="message" 
    textWrap="true"/>

  </StackLayout>

</Page>


Each of the XML layout files are actually a simplified way to load your visual components that you want on your page. In this case, it is what made your app look like this:

project-structure-img-1

The main-view-model.js file


The final file in our tour of the App folder is the model file. This file has about 30 lines of code. By looking at the first couple of lines, you might have figured out that this file was transpiled from TypeScript. Since this file actually has a lot of boilerplate and unneeded code from the TypeScript conversion, we will rewrite the code in plain JavaScript to help you easily understand what each of the parts are used for. This rewrite will be as close to what as I can make it. So without further ado, here is the original transpiled code to compare our new code with:

var observable = require("data/observable");

var HelloWorldModel = (function (_super) {

  __extends(HelloWorldModel, _super);

  function HelloWorldModel() {

    _super.call(this);

    this.counter = 42;

    this.set("message", this.counter + " taps left");

  }

  HelloWorldModel.prototype.tapAction = function () {

    this.counter--;

    if (this.counter <= 0) {

      this.set("message", "Hoorraaay! You unlocked the 
      NativeScript clicker achievement!");

    }

    else {

      this.set("message", this.counter + " taps left");

    }

  };

  return HelloWorldModel;

})(observable.Observable);

exports.HelloWorldModel = HelloWorldModel;

exports.mainViewModel = new HelloWorldModel();

The Rewrite of the main-view-model.js file


The rewrite of the main-view-model.js file is very straightforward. The first thing we need for a working model to also require the Observable class is the primary class that handles data binding events in NativeScript. We then create a new instance of the Observable class named mainViewModel. Next, we need to assign the two default values to the mainViewModel instance. Then, we create the same tapAction() function, which is the code that is executed each time when the user taps on the button. Finally, we export the mainViewModel model we created so that it is available to any other files that require this file. This is what the new JavaScript version looks like:

// Require the Observable class and create a new Model from it

var Observable = require("data/observable").Observable;

var mainViewModel = new Observable();

// Setup our default values

mainViewModel.counter = 42;

mainViewModel.set("message", mainViewModel.counter + " taps 
left");

// Setup the function that runs when a tap is detected.

mainViewModel.tapAction = function() {

  this.counter--;

  if (this.counter <= 0) {

    this.set("message", "Hoorraaay! You unlocked the NativeScript 
    clicker achievement!");

  } else {

    this.set("message", this.counter + " taps left");

  }

};

// Export our already instantiated model class as the variable 
name that the main-page.js is expecting on line 4.

exports.mainViewModel = mainViewModel;


The set() command is the only thing that is not totally self-explanatory or explained in this code. What is probably fairly obvious is that this command sets the variable specified to the value specified. However, what is not obvious is when a value is set on an instance of the Observable class, it will automatically send a change event to anyone who has asked to be notified of any changes to that specific variable. If you recall, in the main-page.xml file, the: <Label text="{{ message }}" ….> line will automatically register the label component as a listener for all change events on the message variable when the layout system creates the label. Now, every time the message variable is changed, the text on this label changes.

The application component


If you recall, earlier in the article, we discussed the app.js file. It basically contains only the code to set up the properties of your application, and then finally, it starts the application component. So, you probably have guessed that this is the primary component for your entire application life cycle. A part of the features that this component provides us is access to all the application-wide events. Frequently in an app, you will want to know when your app is no longer the foreground application or when it finally returns to being the foreground application. To get this information, you can attach  the code to two of the events that it provides like this:

application.on("suspend", function(event) {

  console.log("Hey, I was suspended – I thought I was your 
  favorite app!");

});

application.on("resume", function(event) {

  console.log("Awesome, we are back!");

});


Some of the other events that you can watch from the application component are launch, exit, lowMemory and uncaughtError. These events allow you to handle different application wide issues that your application might need to know about.

Creating settings.js


In our application, we will need a settings page; so, we will create the framework for our application's setting page now. We will just get our feet a little wet and explore how to build it purely in JavaScript. As you can see, the following code is fairly straightforward. First, we require all the components that we will be using: Frame, Page, StackLayout, Button, and finally, the Label component. Then, we have to export a createPage function, which is what NativeScript will be running to generate the page if you do not have an XML layout file to go along with the page's JavaScript file. At the beginning of our createPage function, we create each of the four components that we will need. Then, we assign some values and properties to make them have some sort of visual capability that we will be able to see. Next, we create the parent-child relationships and add our label and button to the Layout component, and then we assign that layout to the Page component. Finally, we return the page component:

// Add our Requires for the components we need on our page

var frame = require("ui/frame");

var Page = require("ui/page").Page;

var StackLayout = require("ui/layouts/stack-layout").StackLayout;

var Label = require("ui/label").Label;

var Button = require("ui/button").Button;

 

// Create our required function which NativeScript uses

// to build the page.

exports.createPage = function() {

  // Create our components for this page

  var page = new Page();

  var layout = new StackLayout();

  var welcomeLabel = new Label();

  var backButton = new Button();

 

  // Assign our page title

  page.actionBar.title = "Settings";

  // Setup our welcome label

  welcomeLabel.text = "You are now in Settings!";

  welcomeLabel.cssClass = "message";

 

  // Setup our Go Back button

  backButton.text = "Go Back";

  backButton.on("tap", function () {

    frame.topmost().goBack();

  });

 

  // Add our layout items to our StackLayout

  layout.addChild(welcomeLabel);

  layout.addChild(backButton);

 

  // Assign our layout to the page.

  page.content = layout;

  // Return our created page

  return page;

};


One thing I did want to mention here is that if you are creating a page totally programmatically without the use of a Declarative XML file, the createPage function must return the page component. The frame component is expected to have a Page component.

Summary


We have covered a large amount of foundational information in this article. We also covered which files are used for your application and where to find and make any changes to the project control files. In addition to all this, we also covered several foundational components such as the Application, Frame, and Page components.

Resources for Article:





Further resources on this subject: