JavaScript Functions and Preserving Data

Cascades is a framework built on top of Qt, a C++ application framework. Qt allows for fantastic user interfaces with a custom language based on JavaScript called QML, or Qt Modeling Language. Not only is QML based on the structure of JavaScript, but it allows us to use JavaScript in our code.

Coming from a web design background, I have almost ZERO experience with C++. I'm not saying it's difficult to use or challenging, I'm just personally not use to it. When I learned that I can opt for JavaScript to do simple functions instead of C++ I was ecstatic, I just wasn't sure where to start.

In this tutorial I'm going to walk you through making a random number generator using some JavaScript MATH functions as well as creating our own.

 To start we will need to create a new empty cascades project in momentics.

Create a new empty Cascades Project

Name the project whatever you like, once your finished you should be greeted with the following code:

main.qml

/*..

import bb.cascades 1.2

Page {
    Container {
        //Todo: fill me with QML
        Label {
            // Localized text with the dynamic translation and locale updates support
            text: qsTr("Hello World") + Retranslate.onLocaleOrLanguageChanged
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
}

The first thing we want to do is structure the application with elements that we may need. In our case were going to add a title bar, a slider to determine how big the number should be, a header to describe to the user what the slider does, a label to show the value of the slider, a textarea to show the random number, and finally a button to get a new random number. Were going to put these in a scrollview so there is some idle interaction instead of just a stiff interface.

The Layout

We will add all the elements by changing the code as follows: (Green = add; Red = remove;)

main.qml

/*..

import bb.cascades 1.2

Page {
    titleBar: [
        TitleBar {
            title: "Lucky Lloyd's Number Generator"
        }
    ]
    Container {
        //Todo: fill me with QML
        Label {
            // Localized text with the dynamic translation and locale updates support
            text: qsTr("Hello World") + Retranslate.onLocaleOrLanguageChanged
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
    ScrollView {
        Container {
            
            Header {
                title: "Select the number of digits to generate:"
            }
            Label {
                text: "6 Digits"
                horizontalAlignment: HorizontalAlignment.Center
            }
            Slider {
                value: 6
                fromValue: 1
                toValue: 100
                horizontalAlignment: HorizontalAlignment.Fill
            }
            Button {
                text: "Shuffle Numbers"
                horizontalAlignment: HorizontalAlignment.Center
            }
            TextArea {
                text: "Random Numbers go here."
                horizontalAlignment: HorizontalAlignment.Center
            }
        }
    }
    
}

Next we make the UI reflect the values of our inputs by assigning id's and calling values. The goal here is to make the slider an input and to reflect that input with our label. To do this we need to assign id's to the slider and the label and let the label update as were moving the slider.

main.qml

/*..

import bb.cascades 1.2

Page {
    titleBar: [
        TitleBar {
            title: "Lucky Lloyd's Number Generator"
        }
    ]
    ScrollView {
        Container {
            
            Header {
                title: "Select the number of digits to generate:"
            }
            Label {
		id: slabel
                text: Math.floor(slider.value)+"6 Digits"
                horizontalAlignment: HorizontalAlignment.Center
            }
            Slider {
		id: slider
                value: 6
                fromValue: 1
                toValue: 100
		onImmediateValueChanged: {
                    slabel.text=Math.floor(immediateValue)+" Digits"
                }
                horizontalAlignment: HorizontalAlignment.Fill
            }
            Button {
                text: "Shuffle Numbers"
                horizontalAlignment: HorizontalAlignment.Center
            }
            TextArea {
                text: "Random Numbers go here."
                horizontalAlignment: HorizontalAlignment.Center
            }
        }
    }
    
}

If you run the app at this point the value of the slider will be reflected on the label above it, Since we used onImmediateValueChanged the results will be shown in real time. Now we will construct our random number function. We want this function to produce a random number where its number of digits matches that of the slider. For example, If the slider is on four a number will be displayed that falls between 0000 and 9999. The number itself doesn't really matter as long as it has the same amount of digits as specified on the slider.

Our function will accept a numerical input and return a numerical string as an output. One way to do this is with a for loop. We will initialize a string and for each digit we will add a random number between 0 and 9 to the string. Once our loop is over we will return the string and write it to the textarea.

To accomplish this we are going to construct a function in out container, give the container an id to call the function, give an id to our textarea to specify a location to store the string, and finally call the function from the slider ,the button, and our textarea.

main.qml

/*..

import bb.cascades 1.2

Page {
    titleBar: [
        TitleBar {
            title: "Lucky Lloyd's Number Generator"
        }
    ]
    ScrollView {
        Container {
            id: main
            function getRand(num){
                var randnum ='';
                for (var i=0;i < num;i++){
                    randnum += Math.floor(Math.random()*10)
                }
                return randnum;
            }
            
            Header {
                title: "Select the number of digits to generate:"
            }
            Label {
		id: slabel
                text: Math.floor(slider.value)+" Digits"
                horizontalAlignment: HorizontalAlignment.Center
            }
            Slider {
		id: slider
                value: 6
                fromValue: 1
                toValue: 100
		onImmediateValueChanged: {
                    slabel.text=Math.floor(immediateValue)+" Digits"
                    result.text= main.getRand(Math.floor(slider.immediateValue))
                }
                horizontalAlignment: HorizontalAlignment.Fill
            }
            Button {
                text: "Shuffle Numbers"
                onClicked: {
                    result.text= main.getRand(Math.floor(slider.value));
                }
                horizontalAlignment: HorizontalAlignment.Center
            }
            TextArea {
                id: result
                text: main.getRand(Math.floor(slider.value))"Random Numbers go here."
                horizontalAlignment: HorizontalAlignment.Center
            }
        }
    }
    
}

At this point we have a fully functional random number generator. But each time we start the app, unless we want a 6 digit random number, we need to move the slider. This can be a bit inconvenient if we tend to use the same number or digits. So we want our application to remember the position of the slider. The best way to accomplish this is with a Qt module called QSettings. It is fantastically easy to set up on the C++ side and delivers a function we can use in our QML code.

To set up the functions for QSettings we will need to edit the applicationui files in the src folder of our project. First were going to introduce our functions to the application with a C++ header file (applicationui.hpp). Were going to tell it what kind of input values our function collects and what types of values it returns.

We're going to have three functions:

saveValueFor("id to save under","information to save") The first one is to save the values.

getValueFor("id to find","fallback value if we can't find it") The next we use to retrieves the values.

clearSettings( "clear" ) And finally a function to clear the settings ie. restore defaults ( This one uses a pass-code to ensure you don't accidentally clear the settings. )

applicationui.hpp

/*..

#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>

namespace bb
{
    namespace cascades
    {
        class Application;
        class LocaleHandler;
    }
}

class QTranslator;

/*!
* @brief Application object
*
*
*/

class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI(bb::cascades::Application *app);
    virtual ~ApplicationUI() { }

    ApplicationUI(QObject *parent = 0);
           void initFullUI();
           Q_INVOKABLE
            QString getValueFor(const QString &objectName, const QString &defaultValue);
           Q_INVOKABLE
            void clearSettings( const QString &inputValue);
           Q_INVOKABLE
            void saveValueFor(const QString &objectName, const QString &inputValue);

private slots:
    void onSystemLanguageChanged();
private:
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;
};

#endif /* ApplicationUI_HPP_ */

Now that we have the headers down, we write the actual function in the C++ file application.cpp.

applicationui.cpp

/*..

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>
#include <QSettings>

using namespace bb::cascades;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app)
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);

    bool res = QObject::connect(m_pLocaleHandler, SIGNAL(systemLanguageChanged()), this, SLOT(onSystemLanguageChanged()));
    // This is only available in Debug builds
    Q_ASSERT(res);
    // Since the variable is not used in the app, this is added to avoid a
    // compiler warning
    Q_UNUSED(res);

    // initial load
    onSystemLanguageChanged();

    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    qml->setContextProperty("_app", this);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Set created root object as the application scene
    app->setScene(root);
}

QString ApplicationUI::getValueFor(const QString &objectName, const QString &defaultValue)
{
	QSettings settings;
	if (settings.value(objectName).isNull()) {
		return defaultValue;
	}
	return settings.value(objectName).toString();
}
void ApplicationUI::saveValueFor(const QString &objectName, const QString &inputValue)
{
	QSettings settings;
	settings.setValue(objectName, QVariant(inputValue));
}
void ApplicationUI::clearSettings( const QString &inputValue)
{
	QSettings settings;
	if (inputValue == "clear") {
			settings.clear();
		}
}


void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("LuckyLloyd_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

The only thing left to go is to call the functions in the main.qml file. I'm also going to add a sheet for the reset button and throw in another slider in case potential users need longer numbers.

/*..

import bb.cascades 1.2

Page {
    titleBar: [
        TitleBar {
            title: "Lucky Lloyd's Number Generator"
        }
    ]
    Menu.definition: MenuDefinition {
        actions: [
            ActionItem {
                title: "Settings"
                enabled: true
                onTriggered: {
                    settings.open()
                } 
            }]}
    ScrollView {
        Container {
            id: main
            function getRand(num){
                var randnum ='';
                for (var i=0;i < num;i++){
                    randnum += Math.floor(Math.random()*10)
                }
                return randnum;
            }
            Header {
                title: "Select the number of digits to generate:"
            }
            Label {
                id: slabel
                text: Math.floor(slider.value)+" Digits"
                horizontalAlignment: HorizontalAlignment.Center
            }
            Slider {
                id: slider
                value: _app.getValueFor("slider.value", 6.0)
                fromValue: _app.getValueFor("slider.fromValue", 1.0)
                toValue: _app.getValueFor("slider.toValue", 100.0)
                onImmediateValueChanged: {
                    slabel.text=Math.floor(immediateValue)+" Digits"
                    result.text= main.getRand(Math.floor(slider.immediateValue))
                    _app.saveValueFor("slider.value", Math.floor(immediateValue))
                }
                horizontalAlignment: HorizontalAlignment.Fill
            }
            Button {
                text: "Shuffle Numbers"
                onClicked: {
                    result.text= main.getRand(Math.floor(slider.value));
                }
                horizontalAlignment: HorizontalAlignment.Center
            }
            TextArea {
                id: result
                text: main.getRand(Math.floor(slider.value))
                horizontalAlignment: HorizontalAlignment.Center
            }
            attachedObjects: [
                Sheet {
                    id: settings
                    peekEnabled: false
                    content: [
                        Page {
                            titleBar: TitleBar {
                                title: "Settings"
                                dismissAction: ActionItem {
                                    id: cancelButton
                                    title: "Back"
                                    onTriggered: {
                                        settings.close()
                                    }
                                }
                            }
                            Container {
                                Header {
                                    id: maxvaluelabel
                                    title: "Max Slider Value: "+maxvalue.value
                                }
                                Slider {
                                    id: maxvalue
                                    value: _app.getValueFor("slider.toValue", 100.0)
                                    fromValue: 100
                                    toValue: 700
                                    onImmediateValueChanged: {
                                        maxvaluelabel.title = "Max Slider Value: "+Math.floor(immediateValue)
                                        _app.saveValueFor("slider.toValue", Math.floor(immediateValue))
                                        slider.toValue = Math.floor(immediateValue)
                                    }
                                }
                                Header {
                                    title: "Restore Defaults"
                                }
                                Button {
                                    id: defaults
                                    text: "Default Settings"
                                    onClicked: {
                                        _app.clearSettings("clear")
                                        slider.value = 6
                                        slider.fromValue = 1
                                        slider.toValue = 100
                                        maxvalue.value = 100
                                    }
                                }
                            }
                        }
                    ]
                }
            ]
        }
    }
}

Find the Complete Code on Github | Download the project files

Leave your comments

Post comment as a guest

0

People in this conversation

Subscribe to the official OSBB BBM Channel!

osbbchannelQR

C00013E89

Back to top