Module 4: Programming basics in QML and JavaScript

In the previous two modules you learned a lot about QML components that make up an Ubuntu Touch app. We focused on the visual aspects there. In this module we’ll focus more on the programming aspects by adding more complex JavaScript code.

You can find this module’s code of the app in the GitLab repository of the course.

1. Introduction

In the previous modules you learned how to let your app do some simple actions like adding items to your shopping list and removing them from the same list. However, we glossed over a lot of the programming concepts that make your app tick.

This changes in this module, were we introduce the basic programming concepts in QML and JavaScript that you’ve been using, and more. You’ll learn about some basic data types in JavaScript and QML. You’ll also learn how to test for specific values, how to repeat statements, and how to create your own functions for more complex sequences of actions. We’ll use all this information to extend the functionality of our shopping list app.

2. Data types, variables and properties

In the previous modules you already encountered a couple of data types. A data type is a 'sort' of data, such as a number or a string. Let’s have a look at them in more detail in this section.

The QML type system has three sorts of types: basic types, QML object types, and JavaScript types. We’ll explain the first two in the next subsections. JavaScript types will be explained later in this module as part of the use of JavaScript expressions in QML.

2.1. Basic types

The QML language has built-in support for various basic types, including integers, double-precision floating-point numbers, strings, and boolean values (true or false).

QML objects may have properties of these types, and you can pass values of these types as arguments to methods of objects.

The QtQuick module extends the QML language with more basic types.

Let’s take a look at the basic types provided by the QML language:

2.1.1. bool

The bool type can have two values: true or false. This is often used to enable or disable specific functionality. For instance, the MainView component has a property automaticOrientation that you can set to true or false. Many other QML components support boolean properties.

2.1.2. int

The int type refers to a whole number, so without a decimal point, for example 0, 7, or -15. You already used this type for the numberOfSlots property of the ActionBar in the previous course module.

2.1.3. real and double

The real type refers to a number with a decimal point, such as 1.5 or -75.3. We’ve already used it in grid units: we passed the real value 0.5 to units.gu(0.5) to compute the size of half a grid unit. If you look at the documentation of Ubuntu.Components Units, you see that the method gu expects a real value as its argument and returns a real value too.

double is also a number with a decimal point, stored in double precision according to the IEEE 754 floating-point format.

2.1.4. string

The string type refers to a free form text string. You need to put a string between double or single quotes, as in "Shopping List" or 'Shopping List'. For instance, the property applicationName of the MainView component is a string. We defined it as:

applicationName: 'shoppinglist.koenvervloesem'
Each string has a length attribute that holds the number of characters in the string. For instance, if you want to know the number of characters in the text in the TextField of your app, you can get it with textFieldInput.text.length.

2.1.5. enumeration

The enumeration type refers to a value that can be one of a fixed set of values, each with their own name. We already saw this in the wrapMode property of the Label component in the about dialog you created in the previous course module. This property can have one of four values: Text.NoWrap, Text.WordWrap, Text.WrapAnywhere, and Text.Wrap.

The Text component has quite a lot of properties with an enumeration type.

2.1.6. list

A list type refers to a list of QML objects. You assign values to the list between square brackets and separated by a comma. We already saw it in the actions property of the ActionBar, to which we assigned a list of two actions:

actions: [
	Action {
		iconName: "settings"
		text: i18n.tr("Settings")
	},
	Action {
		iconName: "info"
		text: i18n.tr("About")
		onTriggered: PopupUtils.open(aboutDialog)
	}
]

If a list only contains one object, you can omit the square brackets. For instance, you could write the leadingActions property of the ListItem from our shopping list app as follows:

leadingActions: ListItemActions {
	actions: Action {
			     iconName: "delete"
			     onTriggered: shoppinglistModel.remove(index)
		     }
}
A list can only store QML objects and not basic type values. If you want to store basic types in a list, use the var type instead.

2.1.7. url

The url type refers to a uniform resource locator (URL). In most cases, this will be a file name, for instance for an image.

The Action component has an iconSource property of the url type that you can use instead of iconName to set your own custom image. Just put the file in your assets directory and then set the property as follows:

iconSource: "../assets/custom-image.png"
Use the .. to navigate to the top directory, because the directory assets sits on the same level as the directory qml.

2.1.8. var

The var type is a generic type that can refer to any data type. It’s equivalent to a regular JavaScript variable. You can even assign functions to a var property, or an array of basic types (an array is the JavaScript equivalent of a list).

2.2. Object types

A QML object type is a type from which you can instantiate a QML object. You’ve been doing this since the first module of this course: time after time you’ve specified a type followed by a set of curly braces that encompass the object’s attributes.

For instance, this is how you defined the button to add an item to your shopping list:

Button {
	id: buttonAdd
	anchors {
		top: header.bottom
		right: parent.right
		topMargin: units.gu(2)
		rightMargin: units.gu(2)
	}
	text: i18n.tr('Add')
	onClicked: shoppinglistModel.append({"name": textFieldInput.text})
}

Button is a QML object type, and it has a couple of attributes: id, anchors, text, and onClicked.

Let’s take a look at the various types of attributes a QML object type can have.

2.2.1. The id attribute

Every QML object instance has an id attribute that you can assign a value to, so other objects can refer to this object.

The value of the id attribute has to begin with a lower-case letter or an underscore. It can’t contain any characters other than letters, numbers and underscores.

You’ve used this is the code above with the add button. In the onClicked signal handler you referred to the text of the input text field as textFieldInput.text. This works because you added id: textFieldInput to the declaration of the TextField object, and text is a property of this TextField object.

2.2.2. Property attributes

A property is an attribute of an object that can be assigned a static value or bound to a dynamic expression. You’ve already done this countless times in this course.

You assign a value to a property on initialization of the object by typing the property name, a colon, and the property value. For instance, in the MainView object:

objectName: 'mainView'

You can also bind a property to a dynamic expression, such as the following one in the add button:

text: i18n.tr('Add')

This dynamically computes a string with the i18n.tr method.

You can also use any JavaScript expression, such as the following one in the Button object with id buttonRemoveAll:

width: parent.width / 2 - units.gu(0.5)

Other objects can read and often also modify an object’s property values. That’s why you could access the text field’s text property in the add button, or the width property of the parent of the remove all button.

The QML engine enforces the relationship between a property and the variables in a JavaScript expression. When any of the variables change in value, the QML engine automatically re-evaluates the binding expression and assigns the new result to the property.

In some cases properties contain a logical group of subproperties. The anchors property is a typical example:

anchors {
	top: header.bottom
	right: parent.right
	topMargin: units.gu(2)
	rightMargin: units.gu(2)
}

The previous code is called the group notation of the group property anchors. You can also use the dot notation, where you set the group properties individually:

anchors.top: header.bottom
anchors.right: parent.right
anchors.topMargin: units.gu(2)
anchors.rightMargin: units.gu(2)

The behavior of both notations is the same. However, when you only need to set one of the subproperties, the dot notation is a bit easier to type, such as in anchors.fill: parent for the Page element.

2.2.3. Method attributes

A method of an object type is a function that you can call to perform some processing. You can add your own methods to a QML type to define reusable blocks of JavaScript code.

For instance, you could add a method addItem to the ListModel object:

ListModel {
	id: shoppinglistModel

	function addItem(name) {
		shoppinglistModel.append({"name": name})
	}
}

This method has one parameter: name.

Now in the button with id buttonAdd, change the signal handler if you click on the button to:

onClicked: shoppinglistModel.addItem(textFieldInput.text)

So when you click on the button, it calls the addItem method of the shoppinglistMode object, with textFieldInput.text as the value for its name parameter.

The specific value given to a parameter when you call a function (or a method) is called an argument.

Of course, this is just a simple method. But later in this module, when you learn some JavaScript basics, we’ll create some more complex functions and methods.

2.2.4. Signal attributes

A signal is a notification from an object that some event has occurred. In OKCancelDialog.qml you defined your own signal attribute:

signal doAction()
Because this signal has no parameters, the parentheses are optional.

To emit a signal, you need to invoke it as a method, in this case by calling doAction() in JavaScript code. This invokes the corresponding signal handler then.

2.2.5. Signal handler attributes

A signal handler is a special type of method attribute: the method is invoked by the QML engine whenever the associated signal is emitted.

If you add a signal to a QML object definition, this will automatically add an associated signal handler to the object definition. Its implementation is empty by default, and you can provide your own implementation to react to the emitted signal.

For instance, because you defined the signal attribute doAction in OKCancelDialog.qml, you’ve also access to the signal handler attribute onDoAction in the OKCancelDialog object type. You’ve instantiated this object in the components with id removeAllDialog and removeSelectedDialog, and you added your own implementation to the signal handler, for instance by:

onDoAction: console.log("Remove all items")

For existing QML object type, look at the QML API documentation of the object type to see which signals it emits.

3. JavaScript expressions in QML

QML provides a JavaScript Host Environment that is similar to the JavaScript environments of your web browser or of a server-side environment such as Node.js. Of course, the JavaScript environment in QML is tailored to writing QML applications. So you don’t have access to a window object or a DOM API found in a browser environment.

If you want to know the details of what the QML JavaScript Host Environment implements, you need to read the 7th edition of the ECMAScript Language Specification. The QML documentation has a list of supported JavaScript objects, functions and properties.

A QML document can contain JavaScript code in three places: in the body of a property binding, in the body of a signal handler, and as a method of a QML object. You’ve already seen examples of these three places, but let’s revisit this for completeness.

You can also import code in a QML document from a standalone JavaScript file. You can then use the functions and variables defined in the JavaScript in any property bindings, signal handlers and methods in the QML document.

3.1. In property bindings

You’ve seen the use of JavaScript in properties like this one for buttons:

width: parent.width / 2 - units.gu(0.5)

This gets the width property of the button’s parent, divides it by two and subtracts units.gu(0.5) from it, the latter being a function call.

You can use any JavaScript expression in the body of a property, and you’ll see some more complex examples later.

3.2. In signal handlers

Another place where you’ve seen the use of JavaScript is in signal handlers, for instance when you click on a button. Until now the body of those signal handlers was quite simple. For instance, the add button has the following signal handler, calling the addItem method of the shopping list model:

onClicked: shoppinglistModel.addItem(textFieldInput.text)

You’ve probably noticed that adding an item to our shopping list isn’t as user-friendly as it could be. After entering a text in the text field and clicking on the add button, the item is added to the shopping list, but the text stays in the text field. So if you now want to add another item, you first have to clear the text field by clicking on the icon with the cross at the right of the text field or by typing backspaces.

Let’s fix this issue in the onClicked signal handler:

onClicked: {
    shoppinglistModel.addItem(textFieldInput.text);
    textFieldInput.text = "";
}

So if you click now on the button, the text is added to an item in the shopping list, and after that the text field is made empty.

3.3. As methods

Earlier in this module you’ve seen how you create a function inside a QML object type, which makes it a method of that object. The JavaScript code in a method can be as complex as you want, and it has access to the object’s properties.

4. JavaScript basics

This is not a JavaScript course, so if you want to learn JavaScript, you should read a book or an online tutorial such as the W3Schools JavaScript reference. However, in the remainder of this module we’ll give some JavaScript basics to get you up to speed for Ubuntu Touch app development.

Many learning resources focus on JavaScript in the web browser. For instance, in the W3Schools example, only the JavaScript Reference section is relevant for QML, not the other ones such as Window Reference, HTML DOM Reference and Web APIs.

4.1. JavaScript types

As you’ve already seen, any standard JavaScript type can be created using the var type. You can declare properties of these types, and you can declare and use them in your JavaScript code. Let’s take a look at some common JavaScript types.

4.1.1. String

A String stores a series of characters. For instance:

var itemName = "apples";

You can get the number of characters in the string by using the length property:

var itemName = "apples";
var itemLength = itemName.length;

A string has a lot of useful methods, for instance concat to concatenate multiple strings into one string:

var item1 = "apples";
var item2 = "bananas";
var items = item1.concat(" and ", item2);

After this code, the variable items contains the string "apples and bananas".

4.1.2. Number

In contrast to many other languages, including QML, JavaScript has only one type of number: Number. You can write numbers with or without decimals, and even in scientific notation:

var x = 3.14;
var y = 20;
var z = 6.0221409e+23;

You can convert a number to a string representation of this number with the method toString():

var x = 3.14;
var text = x.toString();

After this code, the variable text contains the string "3.14".

You can also check whether a number is an integer:

var x = 3.14;
var isint = Number.isInteger(x);

After this, the variable isint has the boolean value false. If you would have called the isInteger method on a value such as 20, it would have returned the value true.

4.1.3. Boolean

A Boolean can have one of two values: true or false. This is mostly used inside expressions, such as with the ternary operator (see below). But you can also define a boolean variable to store information about something that can have two values, for instance:

var enabled = true;

You can convert a boolean to a string representation with the method toString():

var enabled = true;
var text = enabled.toString();

After this code, the variable text contains the string "true".

4.1.4. Array

We’ve already hinted at the use of a JavaScript array. An array is used to store multiple values in a single variable. You can create it like this:

var shoppingList = ["apples", "bananas", "water"];

An array has a property length, which returns the number of elements in the array:

var shoppingList = ["apples", "bananas", "water"];
var n = shoppingList.length;

After this, the variable n contains the value 3.

4.2. JavaScript operators

JavaScript has a lot of operators, used to assign and compare values, to perform arithmetic operations, and much more. We’ll give a brief overview of the most useful operators.

If you want to know all JavaScript operators, read the JavaScript Operators Reference at W3Schools.

4.2.1. Arithmetic operators

Arithmetic operators are used to make calculations with numbers. These are the ones you’ve learned in mathematics lessons in primary school. You add numbers with + and subtract them with -. For multiplication you use * and for division /.

Another interesting operator is %, the modulus operator. This computes the remainder of the division of the first number by the second:

var remainder = 7 % 3;

The value of remainder is 1, because 7 divided by 3 is 2 with remainder 1.

Especially useful are the increment (++) and decrement (--) operators. They’re generally used to add or subtract one to a counter variable:

var x = 5;
x++;

After this, x has the value 6.

You’ll see an example of this later in this module when we’re covering loops.

4.2.2. Assignment operators

You’ve used the basic assignment operator every time you assigned a value to a variable in JavaScript:

var x = 5;
var y = x;

There are some shorthand assignment operators that do an arithmetical calculation and assignment in one time. For instance, instead of x = x + y, you can use x += y.

4.2.3. String operators

You’ve seen how to concatenate strings with the concat method. However, the + operator does the same when used on strings:

var item1 = "apples";
var item2 = "bananas";
var items = item1 + " and " + item2;

After this, the variable items contains the string "apples and bananas".

The += assignment operator also works for strings:

var item1 = "apple";
item1 += " pie";

Now item1 contains the string "apple pie".

4.2.4. Comparison operators

A lot of times, you need to compare two variables, or a variable to a type. We can use this to fix another bug in the signal handler of the add button. Maybe you didn’t notice, but have you tried clicking the add button without entering a text in the text field? The app happily adds an empty item to the shopping then.

Let’s fix this issue in the onClicked signal handler:

onClicked: {
	if(textFieldInput.text != "") {
		shoppinglistModel.addItem(textFieldInput.text);
		textFieldInput.text = "";
	}
}

So if you click on the add button now, the signal handler first checks whether the text field has a text typed into it. If it has (textFieldInput.text doesn’t equal the empty string ""), the text is added to an item in the shopping list, and after that the text of the text field is made empty. If the text field didn’t have a text when clicked on the button, nothing happens.

The if statement only executes the code block between the curly braces if the condition between parentheses is true. In this case the condition uses the != operator, in textFieldInput.text != "". This operator returns true when both sides are not equal.

The == operator, on the other hand, returns true when both sides are equal. Other comparison operators are > (greater than), < (less than), >= (greater than or equal to), and <= (less than or equal to).

4.2.5. Logical operators

Logical operators are useful to change or combine logical expressions.

For instance, the ! (not) operator returns true when its operand is false and false otherwise. So the expression !(x == y) is the same as x != y.

You should use parentheses ( and ) to make the precedence of operators clear.

The && (and) operator returns true only if both operands are true. In all other cases it returns false. For instance, with (x >= 1 && x <=10) is true if x is a number between 1 and 10. In all other cases the expression evaluates to false.

In the same vein, the || (or) operator returns true if at least one of both operands is true. It only returns false is both operands are false.

4.2.6. Ternary operator

One JavaScript expression that is regularly used is the ternary operator, also called the conditional operator. This operator takes three operands: a condition, followed by a question mark (?), then an expression to execute if the condition is true, followed by a colon (:), and finally the expression to execure if the condition is false.

Let’s give an example. What if you want to give items in your shopping list another background color depending on their position in the list being even or odd? You can do this by replacing the Text element in the ListItem by the following rectangle code:

Rectangle {
	anchors.fill: parent
	color: index % 2 ? theme.palette.normal.selection : theme.palette.normal.background
	Text {
		text: name
	}
}

This creates a rectangle, lets it fill the full dimensions of its parent (the list item), and puts a text inside it.

What’s special about this rectangle is the expression bound to its color property: index % 2 ? theme.palette.normal.selection : theme.palette.normal.background. This evaluates index % 2, which computes the remainder of the division of index by 2. If this is 1, the index is odd; if this is 0, the index is even. The value 1 gets evaluated as true, so the first expression after the question mark is returned. In the other case, the second expression is returned. This gives items on an odd position the background color theme.palette.normal.selection and items on an even position the background color theme.palette.normal.background.

By the way, if you find the ternary operator syntax difficult to follow, the above code for the color property is equivalent to the following JavaScript code with an if statement:

color: {
	if(index % 2)
		return theme.palette.normal.selection;
	else
		return theme.palette.normal.background;
}

This executed the first code block if the condition after if is true, and the second code block in the other case.

index is a special property that is available inside a list item delegate. It corresponds to the index of the element in the associated list model.

If you run this code and add a few items, you’ll see the different background colors:

Items on odd and even positions get another color thanks to the ternary operator.
Figure 1. Items on odd and even positions get another color thanks to the ternary operator.

5. Removing items from the shopping list

Now that you have some more knowledge about JavaScript, let’s implement the signal handlers for the buttons at the bottom to remove all or only the selected items.

The onDoAction signal handler for the dialog to remove all items is actually quite simple:

onDoAction: shoppinglistModel.clear()

This uses the clear() method from the ListModel component.

We’ve also added a button to remove selected items. But before we can implement its signal handler, we first should have a way to select items. A common way to do this is to let the user press or long-press on an item and then show a checkbox before all items. The user can then select one or more of these checkboxes and then click on the button to remove all selected items.

We’ll implement this way step by step.

5.1. Creating a property for selection mode

First you’ll have to recognize that our app will have two modes: in selection mode or not. So we should store the current mode in a property of the MainView element:

property bool selectionMode: false

This is the first time you added your own custom property to a QML object. This is done with the property keyword, then the type of the property, then the name, followed by a colon (:) and the property’s initial value. After this, the property can be used the same way as built-in properties.

Then we create a checkbox before each text in a list item. It should only be shown in selection mode, so we use the property root.selectionMode in a few places (root is the ID of the MainView element). We also have to change the Text element:

CheckBox {
	id: itemCheckbox
	visible: root.selectionMode
	anchors {
		left: parent.left
		leftMargin: units.gu(2)
		verticalCenter: parent.verticalCenter
	}
}
Text {
	id: itemText
	text: name
	anchors {
		left: root.selectionMode ? itemCheckbox.right : parent.left
		leftMargin: root.selectionMode ? units.gu(1) : units.gu(2)
		verticalCenter: parent.verticalCenter
	}
}

As you see, the CheckBox component is only visible if root.selectionMode is true. Because we set it to false by default, the checkbox won’t be visible when the app starts. For the text, we let the left and leftMargin properties depend on the selection mode too. In selection mode, the left of the text is anchored to the right of the checkbox and its left margin is 1 grid unit. When not in selection mode, the left of the text is anchored to the left of the parent, with a left margin of 2 grid units.

Run the app. You don’t see any difference compared to our previous version. Now change the value of the selectionMode property of the MainView to true. If you run the app again, there’s a checkbox shown at the left of each item you add:

We show a checkbox next to each item when our app is in selection mode.
Figure 2. We show a checkbox next to each item when our app is in selection mode.

Now change back the default value of the selectionMode property to false, because we’re going to let you set the selection mode in the app.

5.2. Adding the selection status to the list model

First we need to adapt the list model: each list element not only needs to hold the item text, but also its selection status. So change the ListModel object to:

ListModel {
	id: shoppinglistModel

	function addItem(name, selected) {
		shoppinglistModel.append({"name": name, "selected": selected})
	}
}

In the onClicked signal handler of the Button object with ID buttonAdd, you need to change the addItem method call to:

shoppinglistModel.addItem(textFieldInput.text, false);

So a newly added item isn’t selected, because you call the method with false as the value for the selected argument.

5.3. Using a MouseArea to handle mouse events

Now to set the selection mode yourself, we need a new component: MouseArea. This is an invisible item that provides mouse handling. It has a signal pressAndHold that is emitted when you do a long-press (800 ms). We’ll react to this signal in the corresponding onPressAndHold signal handler. So add the following MouseArea object after the Text object, but still inside the Rectangle:

MouseArea {
	anchors.fill: parent
	onPressAndHold: root.selectionMode = true;
	onClicked: {
		if(root.selectionMode) {
			shoppinglistModel.get(index).selected = !shoppinglistModel.get(index).selected;
			shoppinglistView.refresh();
        }
	}
}

With anchors.fill we let the mouse area fill the whole area of the rectangle that contains it. Thanks to the onPressAndHold signal handler, the app shows a checkbox before each item in the list if you long-press on an item. You can then select items in the list by clicking on a list item. It doesn’t matter whether you click on the checkbox, the text, or next to the text: the onClicked signal handler of the mouse area catches all those events.

You could also use the onClicked signal handler of the CheckBox, but then it would only react on the checkbox area. Our solution with the MouseArea is more user-friendly.

In this signal handler the selected property of the list element in the list model corresponding to the current list item in the list view is toggled: its new value is the negation (the logical operator !) of its old value. We can do this because this is a Boolean value: true or false.

Then we need to refresh the list view, so it shows the status of the checkbox. We do this with the following method added to the ListView object:

function refresh() {
	// Refresh the list to update the selected status
	var tmp = model;
	model = null;
	model = tmp;
}

This is a neat trick: we store the current model associated with the list view in a temporary value, set the model to null and then reset the model to the initial value. The app responds by redrawing the list view according to the new model.

We also need to set the checkbox status. We can do this with the following property binding in the CheckBox element:

checked: shoppinglistModel.get(index).selected

5.4. Remove selected items

Now the only thing we need to implement is the onDoAction signal handler of the component with ID removeSelectedDialog. This should remove all items with a checked checkbox, and then turn off selection mode. Let’s add a method removeSelectedItems to the ListModel object:

function removeSelectedItems() {
	for(var i=shoppinglistModel.count-1; i>=0; i--) {
		if(shoppinglistModel.get(i).selected)
			shoppinglistModel.remove(i);
	}
}

This function introduces an important statement in JavaScript: a for loop that iterates over all elements in the shopping list model.

A for loop iterates over a code block. Between the parentheses after the for keywoard, there are three components:

  • Initialization of the loop: This initializes the variable i to the number of elements in the shopping list model decreased by one.

  • Condition: The code block in the loop is executed as long as this condition is true, in this case as long as the variable i is greater than or equal to 0.

  • Decrement: The variable i is decremented after every execution of the block in the loop.

So, for instance, if your shopping list model has five elements, the code block is executed five times, and every time i gets a new value: 4, 3, 2, 1, and 0. After this, the loop ends.

Now inside the for loop’s code block, the if block checks the selection status of each element. We use the loop’s variable i as an index to the get method of the ListModel object. And if that element’s selected property is true, we remove the element.

The index of an element in a ListModel is counted from 0: the first element in the list model has index 0, and the last element has the number of elements - 1 as its index. That’s why we initialize the variable i to shoppinglistModel.count-1 in the loop.

The signal handler of the dialog to remove all selected items then looks like this:

onDoAction: {
	shoppinglistModel.removeSelectedItems();
	root.selectionMode = false;
}

So if you know long-press on the item list, select a few items and click on the "Remove selected items" button and confirm, all selected items are removed from the list, and the app leaves selection mode, so no checkboxes are shown.

Select items to remove from your shopping list.
Figure 3. Select items to remove from your shopping list.

Now you just have to add a signal in OKCancelDialog.qml so you can also have a signal handler onCancelAction that just leaves selection mode. We’ll keep this as an exercise for the reader.