Page 1 of 2 12 LastLast
Results 1 to 20 of 37

Thread: Qt 5.4 - Scriptable Qml Application

  1. #1
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Qt 5.4 - Scriptable Qml Application

    Hi all

    I want to learn how to make a qml application scriptable. I would like to have user defined scripts that can be changed without having to recompile the app. Also, I would like to create an object and call a function from anywhere in javascript. During researching the topic I found some webpages mentioning that QtScript is no longer maintained. Is that true? What is the recommanded way to make a qml-application fully scriptable in qt5.4 (for qml applications)?


    Some background:
    At first I thought, that there is already a javascript functionality in qml. So, I started to write some javascripts and loaded them from a file. I can make my c++ objects available with:
    Qt Code:
    1. // in main.cpp
    2. qmlRegisterType<CustonObject>("MyCustonObject", 1, 0, "QmlCustonObject");
    3.  
    4. // In my qml I can use a script with
    5. import "someFile.js" as File
    To copy to clipboard, switch view to plain text mode 
    This works great as long as the file is in the resources (qrc). However, I want to be able to change the scripts without having to recompile the application. So, I looked at
    Qt Code:
    1. Qt.import("otherFile.js")
    To copy to clipboard, switch view to plain text mode 
    And wanted to import that file from the "someFile.js" which is in the resources. I found that I need an absolute path for this to work. The problem is then that the path will change across different platforms. So I needed a way to have the path dynamic. The only way here was to wrap the import in a function
    Qt Code:
    1. // function in "someFile.js"
    2. function loadScript(path)
    3. {
    4. Qt.import(path);
    5. }
    To copy to clipboard, switch view to plain text mode 
    While that works, the script is then only available inside the function scope. If I warp everything in such a function call, the script gets laoded every time the function is called. So, I packed it into an object instead to avoid loading the script multiple times.
    Qt Code:
    1. // defined in "someFile.js"
    2. function Loader() {
    3. Qt.import(getPathFromQmlFunction());
    4. }
    5. Loader.prototype.Execute() {
    6. // do stuff here...
    7. }
    8.  
    9. var MyLoader = new Loader();
    To copy to clipboard, switch view to plain text mode 
    In a qml file I can then add a helper function. The only part to be aware of is that this function must be declared before the "someFile.js" gets included.
    Qt Code:
    1. // defined in qml
    2. function getPathFromQmlFunction()
    3. {
    4. // figure out path for plattform
    5. return "some/path/goes/here/otherFile.js";
    6. }
    To copy to clipboard, switch view to plain text mode 
    while this works, it requires me to fiddle with the Loader object and extend it depending on what I want to make available to the scripts. It seams a cumbersome way to implement scripting and I am unsure about it's limitations and restrictions across platforms. Hence, my initial question. What is the proper/recommanded way to make a qml application fully scriptable? Should I continue with this approach or should I use Qt Script instead and load my scripts with a QScriptEngine?

    Edit: fixed some typo
    Last edited by motaito; 14th April 2015 at 14:41.

  2. #2
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    QML already is scriptable. You just need to get the paths right. At worst expose a C++ interface that will execute your scripts on the QML engine.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  3. #3
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Thanks for the reply!

    I am not sure I got your point. As I pointed out in the background info, it does work. I am not sure if it's good practice. Because it depends on the order in which the elements are created and I don't know if there are differences across platforms at what point in time qml's are created. I figured it should be possible to make some javascripts available and use them without a wrapper class (the Loader class I mentioned earlier). However, I can not load such files unless they are included in the resources. That is not what I am looking for. I want to be able to adjust the javascripts without having to recompile the app. E.g. fix a bug extend functionality through scripts etc.

    Also, does that mean that QtScript is indeed no longer maintained? Otherwise I might be able to load the scripts with a QScriptEngine instead which could solve the path problem. But if Qml is already scriptable and QtScript is not maintained anymore I would essentially add a second script engine that lacks support later on. So, I am still not sure about the best practice here.

  4. #4
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Ok, I think I just realized what you mean... I got so caught up in over thinking the issue that I overlooked a QQmlApplicationEngine has it's own evaluate function...

    What I haven't figured out yet is, how to call a function that is defined in an external javascript from qml. E.g.

    Qt Code:
    1. // in main.cpp
    2. QQmlApplicationEngine engine;
    3. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    4.  
    5. QFile file("../path/to/file.js");
    6. file.open(QIODevice::ReadOnly);
    7. engine.evaluate(scriptFile.readAll(), "file.js");
    8. file.close();
    9.  
    10.  
    11. // in qml
    12. ...
    13. MouseArea {
    14. onClicked: {
    15. var test = fromExternalJS(); // this doesn't work
    16. console.log(test);
    17. }
    18. }
    19. ...
    20.  
    21.  
    22. // in file.js
    23. function fromExternalJS()
    24. {
    25. return "testing...";
    26. }
    To copy to clipboard, switch view to plain text mode 
    I am not sure how to do this. I assume that's what you referred to by
    At worst expose a C++ interface that will execute your scripts on the QML engine.
    Sorry, I am new to this. Maybe I am using the wrong search terms and so I can't find an example online. Does anyone have a link to where I can find the answer, or maybe present a code snippet?

    Any input is greatly appreciated.

  5. #5
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    However, I can not load such files unless they are included in the resources.
    That's what I meant about getting the paths right. You can use any file you want anywhere ("not in the resource system") as long as the engine knows how to access it (e.g. similar to Qt.resolvePath()). You need to make entry points where scripts can hook up.

    Also, does that mean that QtScript is indeed no longer maintained?
    Indeed. You couldn't merge it with QML anyway.

    Otherwise I might be able to load the scripts with a QScriptEngine instead which could solve the path problem.
    You can do that with QJSEngine if you want however those scripts would be totally separated from your Qt Quick environment.

    We cannot provide any concrete help without you specifying what you mean by an application being "scriptable". I don't think letting the user execute arbitrary scripts on your engine is a good idea, by the way.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  6. #6
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Thanks for your help!
    That's what I meant about getting the paths right. You can use any file you want anywhere ("not in the resource system") as long as the engine knows how to access it (e.g. similar to Qt.resolvePath()). You need to make entry points where scripts can hook up.
    I don't know how to dynamically adjust a path in an import statement, which would be necessary to have the app working on different platforms (or different machines for that matter) as the path needs to be absolute if they are not embedded in a resource.

    You can do that with QJSEngine if you want however those scripts would be totally separated from your Qt Quick environment.
    That's a problem because I need to update the qml after calling a function in javascript. They need to be connected.

    I can't wrap my head around scripting... I don't want to start learning QScriptEngine if it's no longer maintained. However, QJSEngine isn't doing it for me either

    consider this:

    Qt Code:
    1. // in foo.js
    2. function fooTest()
    3. {
    4. return "some text";
    5. }
    6.  
    7.  
    8. // in main.cpp
    9. int main(int argc, char *argv[])
    10. {
    11. QGuiApplication app(argc, argv);
    12.  
    13. QQmlApplicationEngine engine;
    14. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    15.  
    16.  
    17. QFile scriptFile("foo.js");
    18. scriptFile.open(QIODevice::ReadOnly);
    19. QTextStream s(&scriptFile);
    20. QString content = s.readAll();
    21. scriptFile.close();
    22. engine.evaluate(content, "foo.js");
    23.  
    24. QJSValue aa = engine.globalObject().property("fooTest");
    25. qDebug() << aa.call().toString(); // output: "undefined"
    26.  
    27. QJSEngine eng;
    28. eng.evaluate(content);
    29. QJSValue bb = eng.globalObject().property("fooTest");
    30. qDebug() << bb.call().toString(); // output: "some text"
    31.  
    32. return app.exec();
    33. }
    To copy to clipboard, switch view to plain text mode 


    Why is there a different output and why does it not work when I use the evaluate function from QQmlApplicationEngine? Does QQmlApplicationEngine implement a different QJSEngine? That would not make sense at all, the return type for both evaluate functions is the same which indicates they use the same type of QJSEngine.

    Also, by loading files that way I seam to loose a lot of functionality. E.g. I can not use Qt.import() in a file loaded like this nor does it work to make an instance of c++ class (var c = new MyClass(); will not work in javascript).


    further details:
    I try to explain my actual issue in the app. I have a network manager that grabs content form html pages. I then filter out the part that I want and use it further. However, parsing the html response differs for each page. I might add other pages later. Also, if a page gets redesigned I would need to adjust the parsing function. So, I want to be able to add parsing functions later or change them if needed, without having to recompile the app. Instead I want to be able to add parsing functions in javascript.

    I do have a working solution but it leaves me with the impression that it's far more complicated then it should be. Anything that I read about scripting did not work and I had to fiddle for 3 days to find something that works. Here is an overview of what I am doing, in case it helps. This is the only way I could make it work completely. By that I mean call javascript functions from qml, dynamically include javascript files and update qml from javascript files etc. It seams like a hack to me. I am looking for a better, cleaner solution.



    details (only for the eager reader )

    Qt Code:
    1. // in main.cpp
    2. int main(int argc, char *argv[])
    3. {
    4. QGuiApplication app(argc, argv);
    5.  
    6. qmlRegisterType<NetworkManager>("MyNetworkManager", 1, 0, "NetworkManagerHandler");
    7.  
    8. QQmlApplicationEngine engine;
    9. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    10.  
    11. return app.exec();
    12. }
    13.  
    14.  
    15.  
    16. // in main.qml
    17. Window {
    18. ...
    19. // component with the actual layout
    20. MainLayout {
    21. id: layoutMain
    22. }
    23. ...
    24.  
    25. // must be defined in hierarchy above where Builder is created
    26. function getIncludePaths()
    27. {
    28. // adjust due to platform (I exclude the path building function to keep it more clear)
    29. var path =["file:///path/goes/here/defaultBuildFunction.js",
    30. "file:///path/goes/here/buildDefinitions.js"];
    31. return path;
    32. }
    33. }
    34.  
    35.  
    36.  
    37. // in mainLayout.qml -> Builder is created here
    38. import "fileBuildScript.js" as FileBuildScript
    39. ...
    40. MouseArea {
    41. ...
    42. onClicked: {
    43. FileBuildScript.build("someIdentifier");
    44. }
    45. }
    46. ...
    47.  
    48.  
    49.  
    50. // in fileBuildScript.js
    51. Qt.include("networkManagerScript.js"); // from resource file -> explained further down
    52.  
    53. var BuilderClass = function()
    54. {
    55. var path = getIncludePaths(); // function from main.qml
    56. for(var i=0; i < path.length; ++i)
    57. Qt.include(path[i]); // from external file
    58.  
    59. this.buildDefinitions = buildDefs;
    60. }
    61. BuilderClass.prototype.build = function(identifier)
    62. {
    63. var index = 0; // rest ommited to keep it clear -> would find index based on identifier
    64. this.buildDefinitions[index].buildFunction();
    65. }
    66.  
    67. var Builder = new BuilderClass();
    68.  
    69.  
    70. // wrapper function to call build from Builder instance in mainLayout (for conveniance)
    71. function build(identifier)
    72. {
    73. Builder.build(identifier);
    74. }
    75.  
    76.  
    77.  
    78. // in file:///path/goes/here/buildDefinitions.js -> external file that the I can adjust without recompiling the app
    79. .pragma library
    80.  
    81. var buildDefs = [
    82. {
    83. name: "someIdentifier",
    84. buildFunction: buildNormal // function pointer -> function defined in defaultBuildFunction.js
    85. },
    86. {
    87. name: "otherIdentifier",
    88. buildFunction: buildSpecial // function pointer -> function defined in defaultBuildFunction.js
    89. }
    90. ];
    91.  
    92.  
    93.  
    94. // in file:///path/goes/here/defaultBuildFunction.js -> external file that the I can adjust without recompiling the app
    95. .pragma library
    96.  
    97. function buildNormal()
    98. {
    99. function reply(text)
    100. {
    101. text = "buildNormal"; // ommited parsing to keep it clear
    102. updateElement(text); // update in qml element
    103. updateGUI(); // update in qml element
    104. }
    105.  
    106. var m = new NetworkMangerScriptable();
    107. m.fetch(reply, "http://www.somepage.com");
    108. }
    109. ...
    To copy to clipboard, switch view to plain text mode 


    finally I have to define the network manager...

    Qt Code:
    1. // in networkmanager.h
    2. #ifndef NETWORKMANAGER_H
    3. #define NETWORKMANAGER_H
    4.  
    5. #include <QObject>
    6. #include <QUrl>
    7. #include <QNetworkAccessManager>
    8. #include <QNetworkRequest>
    9. #include <QNetworkReply>
    10.  
    11. class NetworkManager : public QObject
    12. {
    13. Q_OBJECT
    14. public:
    15. NetworkManager(QObject *parent=0);
    16. Q_INVOKABLE void fetch(const QString& url);
    17.  
    18. public slots:
    19. void replyFinished(QNetworkReply*);
    20.  
    21. signals:
    22. void reply(const QString& reply);
    23.  
    24. private:
    25. QNetworkAccessManager* m_NetworkManager;
    26. };
    27.  
    28. #endif // NETWORKMANAGER_H
    29.  
    30.  
    31.  
    32. // in networkmanager.cpp
    33. #include "networkmanager.h"
    34.  
    35. NetworkManager::NetworkManager(QObject *parent)
    36. : QObject(parent)
    37. {
    38. m_NetworkManager = new QNetworkAccessManager(this);
    39.  
    40. connect(m_NetworkManager, SIGNAL(finished(QNetworkReply*)),
    41. this, SLOT(replyFinished(QNetworkReply*)));
    42. }
    43.  
    44. void NetworkManager::fetch(const QString& url)
    45. {
    46. m_NetworkManager->get(QNetworkRequest(QUrl(url)));
    47. }
    48.  
    49. void NetworkManager::replyFinished(QNetworkReply* pReply)
    50. {
    51. QByteArray data = pReply->readAll();
    52. QString str(data);
    53.  
    54. //process str -> in my case I hook up the javascript to the signal
    55. emit reply(str);
    56. }
    57.  
    58.  
    59. // in NetworkManager.qml (necessary component to make an instance in javascript)
    60. import QtQuick 2.4
    61. import MyNetworkManager 1.0
    62.  
    63. NetworkManagerHandler {
    64. id: networkManager
    65. }
    66.  
    67.  
    68. // in networkManagerScript.js (so I can make an instance in a javascript e.g. var m = new NetworkMangerScriptable();)
    69. .pragma library
    70.  
    71. var NetworkMangerScriptable = function()
    72. {
    73. this.Manager = this.init();
    74. this.currentReplyFunction = function(text){}; // dummy definition
    75. };
    76.  
    77. NetworkMangerScriptable.prototype.init = function()
    78. {
    79. var objManager;
    80.  
    81. var component = Qt.createComponent("../../component/network/NetworkManagerQml.qml");
    82. objManager = component.createObject(mainLyout);
    83. if(null == objManager)
    84. console.log("Error: failed to create network manager.");
    85.  
    86. return objManager;
    87. };
    88.  
    89. NetworkMangerScriptable.prototype.fetch = function(replyFunction, uri)
    90. {
    91. try { this.Manager.reply.disconnect(this.currentReplyFunction); }
    92. catch(e) { console.log("Error: " + e); }
    93.  
    94. this.currentReplyFunction = replyFunction;
    95. this.Manager.reply.connect(this.currentReplyFunction);
    96.  
    97. this.Manager.fetch(uri);
    98. };
    To copy to clipboard, switch view to plain text mode 

  7. #7
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    Also, does that mean that QtScript is indeed no longer maintained?
    QtScript is part of the Qt5 release, so of course it is maintained at least until the end of the version 5 series.
    It is not actively developed any further, so no new functionality will be added in any of the new releases.

    QJSEngine is under active development and might at some point gain the same capabilities as QScriptEngine, but the current focus is on the needs of QQmlEngine.
    Given that you have a working solution it seems that you don't need any of the features from QScriptEngine.

    There is quite a lot of things that QQmlEngine can do, see http://code.woboq.org/qt5/qtdeclarat...guage.cpp.html for inspiration.

    One option for you might be to load a QML file and let it import the JavaScript like you would do for the main application itself.

    Cheers,
    _

  8. #8
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    I don't know how to dynamically adjust a path in an import statement,
    Not in an import statement. I have no idea what sense would it make. You should provide a function similar to Qt.resolvePath() that will resolve paths relative to your scripts directory.

    That's a problem because I need to update the qml after calling a function in javascript. They need to be connected.

    I can't wrap my head around scripting... I don't want to start learning QScriptEngine if it's no longer maintained. However, QJSEngine isn't doing it for me either
    QScriptEngine is of no use to you. You cannot interconnect two script engines this way.

    consider this:

    Qt Code:
    1. // in foo.js
    2. function fooTest()
    3. {
    4. return "some text";
    5. }
    6.  
    7.  
    8. // in main.cpp
    9. int main(int argc, char *argv[])
    10. {
    11. QGuiApplication app(argc, argv);
    12.  
    13. QQmlApplicationEngine engine;
    14. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    15.  
    16.  
    17. QFile scriptFile("foo.js");
    18. scriptFile.open(QIODevice::ReadOnly);
    19. QTextStream s(&scriptFile);
    20. QString content = s.readAll();
    21. scriptFile.close();
    22. engine.evaluate(content, "foo.js");
    23.  
    24. QJSValue aa = engine.globalObject().property("fooTest");
    25. qDebug() << aa.call().toString(); // output: "undefined"
    26.  
    27. QJSEngine eng;
    28. eng.evaluate(content);
    29. QJSValue bb = eng.globalObject().property("fooTest");
    30. qDebug() << bb.call().toString(); // output: "some text"
    31.  
    32. return app.exec();
    33. }
    To copy to clipboard, switch view to plain text mode 


    Why is there a different output and why does it not work when I use the evaluate function from QQmlApplicationEngine? Does QQmlApplicationEngine implement a different QJSEngine? That would not make sense at all, the return type for both evaluate functions is the same which indicates they use the same type of QJSEngine.
    In the first case your script is probably executed in a local context so that you cannot modify the global context.

    Also, by loading files that way I seam to loose a lot of functionality. E.g. I can not use Qt.import() in a file loaded like this nor does it work to make an instance of c++ class (var c = new MyClass(); will not work in javascript).
    You didn't answer my previous question -- what exactly do you want to "script"? Because for now it looks like you want to allow the user to define some functions which are going to be called from your main program. This implies those functions would have to have hard-coded names and without providing them the application would not work. That's hardly what I'd call "scripting". Of course you can still do it through the asset system.


    further details:
    I try to explain my actual issue in the app. I have a network manager that grabs content form html pages. I then filter out the part that I want and use it further. However, parsing the html response differs for each page. I might add other pages later. Also, if a page gets redesigned I would need to adjust the parsing function. So, I want to be able to add parsing functions later or change them if needed, without having to recompile the app. Instead I want to be able to add parsing functions in javascript.
    I don't see the problem. Prepeare a C++ function that will read the script from file, execute it on the qml engine and assign the result to a context property in the engine. Then whenever your main application wants to call the function, retrieve the context and execute the function.

    javascript Code:
    1. function getIncludePaths()
    2. {
    3. // adjust due to platform (I exclude the path building function to keep it more clear)
    4. var path =["file:///path/goes/here/defaultBuildFunction.js",
    5. "file:///path/goes/here/buildDefinitions.js"];
    6. return path;
    7. }
    8. }
    To copy to clipboard, switch view to plain text mode 
    Seems like a job for the asset system rather than manually building paths in javascript.

    javascript Code:
    1. var BuilderClass = function()
    2. {
    3. var path = getIncludePaths(); // function from main.qml
    4. for(var i=0; i < path.length; ++i)
    5. Qt.include(path[i]); // from external file
    6.  
    7. this.buildDefinitions = buildDefs;
    8. }
    9. BuilderClass.prototype.build = function(identifier)
    10. {
    11. var index = 0; // rest ommited to keep it clear -> would find index based on identifier
    12. this.buildDefinitions[index].buildFunction();
    13. }
    14.  
    15. var Builder = new BuilderClass();
    16.  
    17.  
    18. // wrapper function to call build from Builder instance in mainLayout (for conveniance)
    19. function build(identifier)
    20. {
    21. Builder.build(identifier);
    22. }
    23.  
    24.  
    25.  
    26. // in file:///path/goes/here/buildDefinitions.js -> external file that the I can adjust without recompiling the app
    27. .pragma library
    28.  
    29. var buildDefs = [
    30. {
    31. name: "someIdentifier",
    32. buildFunction: buildNormal // function pointer -> function defined in defaultBuildFunction.js
    33. },
    34. {
    35. name: "otherIdentifier",
    36. buildFunction: buildSpecial // function pointer -> function defined in defaultBuildFunction.js
    37. }
    38. ];
    39.  
    40.  
    41.  
    42. // in file:///path/goes/here/defaultBuildFunction.js -> external file that the I can adjust without recompiling the app
    43. .pragma library
    44.  
    45. function buildNormal()
    46. {
    47. function reply(text)
    48. {
    49. text = "buildNormal"; // ommited parsing to keep it clear
    50. updateElement(text); // update in qml element
    51. updateGUI(); // update in qml element
    52. }
    53.  
    54. var m = new NetworkMangerScriptable();
    55. m.fetch(reply, "http://www.somepage.com");
    56. }
    57. ...
    To copy to clipboard, switch view to plain text mode 
    That's all very "not-declarative".
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  9. #9
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    @anda_skoa
    Thanks for the additional input and the link. If QtScript is no longer under development, I probably wont look at it further, especially if QQmlEngine can do the job as well. Even if it's still maintained. I basically consider it deprecated and prefer to learn the newer way of doing things.


    @wysota
    Not in an import statement. I have no idea what sense would it make. You should provide a function similar to Qt.resolvePath() that will resolve paths relative to your scripts directory.
    If I have an external javascript file, I must provide an absolute link. So I am facing this problem
    Qt Code:
    1. // inside some qml
    2. import "file:///home/user/someDirectory/fileBuildScript.js" as FileBuildScript
    3. ...
    4. MouseArea {
    5. ...
    6. onClicked: {
    7. FileBuildScript.build("someIdentifier");
    8. }
    9. }
    To copy to clipboard, switch view to plain text mode 
    If I now compile the app on windows instead of linux, the absolute path will no longer be correct. Thus It won't run on windows. Furthermore, the path would change depending on the directory in which the app is installed. So, How would I properly load an external javascript file?

    In the first case your script is probably executed in a local context so that you cannot modify the global context.
    I must be missing something obvious because whatever I try it does not work. Can you provide a code snippet to illustrate how to execute javascript on the QQmlApplicationEngine?

    What I am looking for is to load an external javascript file e.g.
    Qt Code:
    1. // someFile.js
    2. function foo()
    3. {
    4. return "foo got called.";
    5. }
    6.  
    7. function bar()
    8. {
    9. return "bar got called.";
    10. }
    To copy to clipboard, switch view to plain text mode 
    And then execute these functions from qml. As I understand, I would need to wrap this into a class of my own and declare it in main.cpp.
    Qt Code:
    1. // main.cpp
    2. qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClassQml");
    To copy to clipboard, switch view to plain text mode 
    How would MyClass have to execute the loaded javascript?

    Ultimately my goal it this. My network manager has an async function call. So, I would need these components.
    Qt Code:
    1. // inside some qml
    2. ...
    3. MouseArea {
    4. ...
    5. onClicked: {
    6. MyClass.build(); // external javascript gets called here.
    7. }
    8. }
    9.  
    10. Text {
    11. id: myText
    12. text: "This will be updated."
    13. }
    14. ...
    15. function updateGUI(text)
    16. {
    17. myText.text = text;
    18. }
    19.  
    20.  
    21. // inside external js file
    22. function build()
    23. {
    24. function reply(text)
    25. {
    26. updateGUI(text); // call function from qml
    27. }
    28.  
    29. var m = new NetworkManager(); // create instance form c++ class in javascript
    30. m.fetch(reply, "http://www.somePage.com");
    31. }
    To copy to clipboard, switch view to plain text mode 
    It would be a great help if you could provide a code snippet to illustrate how MyClass would look like. It doesn't need to be a complete implementation. Just the relevant parts of MyClass should suffice. I would greatly appreciate your effort!

  10. #10
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    If I have an external javascript file, I must provide an absolute link. So I am facing this problem
    Qt Code:
    1. // inside some qml
    2. import "file:///home/user/someDirectory/fileBuildScript.js" as FileBuildScript
    3. ...
    4. MouseArea {
    5. ...
    6. onClicked: {
    7. FileBuildScript.build("someIdentifier");
    8. }
    9. }
    To copy to clipboard, switch view to plain text mode 
    This does not make much sense since you would need to know the path upfront. You can do this though:

    javascript Code:
    1. MouseArea {
    2. onClicked: executeHook("build", "someIdentifier")
    3. }
    To copy to clipboard, switch view to plain text mode 

    Where executeHook() is a C++ function calling script "build" with parameter "someIdentifier". How to get script "build" is the responsibility of executeHook() function. Since the function is written in C++, it can access all kind of useful stuff, starting with files at arbitrary paths, ending on the script engine itself.

    If I now compile the app on windows instead of linux, the absolute path will no longer be correct. Thus It won't run on windows. Furthermore, the path would change depending on the directory in which the app is installed. So, How would I properly load an external javascript file?
    For the sake of answering the question -- you use the assets system.


    What I am looking for is to load an external javascript file e.g.
    Qt Code:
    1. // someFile.js
    2. function foo()
    3. {
    4. return "foo got called.";
    5. }
    6.  
    7. function bar()
    8. {
    9. return "bar got called.";
    10. }
    To copy to clipboard, switch view to plain text mode 
    And then execute these functions from qml. As I understand, I would need to wrap this into a class of my own and declare it in main.cpp.
    Qt Code:
    1. // main.cpp
    2. qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClassQml");
    To copy to clipboard, switch view to plain text mode 
    How would MyClass have to execute the loaded javascript?
    Not sure what you mean.

    Ultimately my goal it this.
    Hurray, finally we'll know what the actual problem is

    My network manager has an async function call. So, I would need these components.
    Qt Code:
    1. // inside some qml
    2. ...
    3. MouseArea {
    4. ...
    5. onClicked: {
    6. MyClass.build(); // external javascript gets called here.
    7. }
    8. }
    9.  
    10. Text {
    11. id: myText
    12. text: "This will be updated."
    13. }
    14. ...
    15. function updateGUI(text)
    16. {
    17. myText.text = text;
    18. }
    19.  
    20.  
    21. // inside external js file
    22. function build()
    23. {
    24. function reply(text)
    25. {
    26. updateGUI(text); // call function from qml
    27. }
    28.  
    29. var m = new NetworkManager(); // create instance form c++ class in javascript
    30. m.fetch(reply, "http://www.somePage.com");
    31. }
    To copy to clipboard, switch view to plain text mode 
    This seems much more declarative:

    javascript Code:
    1. // inside some qml
    2. ...
    3.  
    4. Component {
    5. id: builder
    6. url: getPathToScriptFile() // <--- your "script" URL goes here
    7. }
    8.  
    9. MouseArea {
    10. ...
    11. onClicked: {
    12. var obj = builder.createObject(myText)
    13. obj.ready.connect(function(txt) { myText.text = txt; obj.destroy() })
    14. obj.build()
    15. }
    16. }
    17.  
    18. Text {
    19. id: myText
    20. text: "This will be updated."
    21. }
    To copy to clipboard, switch view to plain text mode 

    and the "script" file:
    javascript Code:
    1. import "....."
    2.  
    3. QtObject {
    4. id: root
    5. signal ready(string txt)
    6.  
    7. function build() {
    8. nm.fetch("http://www.somePage.com")
    9. }
    10.  
    11. NetworkManager {
    12. id: nm
    13.  
    14. onReply: root.ready(txt)
    15. }
    16.  
    17. }
    To copy to clipboard, switch view to plain text mode 
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  11. #11
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Where executeHook() is a C++ function calling script "build" with parameter "someIdentifier". How to get script "build" is the responsibility of executeHook() function. Since the function is written in C++, it can access all kind of useful stuff, starting with files at arbitrary paths, ending on the script engine itself.
    That's exactly my problem. I can not for the live of me figure out how the c++ function calls the script "build". It can't possible be that difficult. It is driving me completely nuts!

    Qt Code:
    1. // in foo.js
    2. function funcA()
    3. {
    4. return "funcA called.";
    5. }
    6.  
    7. function funcB()
    8. {
    9. return "funcB called.";
    10. }
    11.  
    12.  
    13.  
    14. // in main.cpp
    15. int main(int argc, char *argv[])
    16. {
    17. QGuiApplication app(argc, argv);
    18.  
    19. QQmlApplicationEngine engine;
    20. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    21.  
    22.  
    23. QFile scriptFile("foo.js");
    24. scriptFile.open(QIODevice::ReadOnly);
    25. QTextStream s(&scriptFile);
    26. QString content = s.readAll();
    27. scriptFile.close();
    28.  
    29. engine.evaluate(content, "foo.js");
    30. // how do I call funcA() here ???
    31.  
    32.  
    33. // Because by creating a separate QJSEngine this works!
    34. QJSEngine eng;
    35. eng.evaluate(content, "foo.js");
    36. QJSValue val = eng.globalObject().property("funcA");
    37. qDebut() << val.call().toString();
    38.  
    39. return app.exec();
    40. }
    To copy to clipboard, switch view to plain text mode 

    For the sake of answering the question -- you use the assets system.
    I would love to read up on the asset system. I have been google-ing for "QT asset system" for hours now and can't find anything online... Maybe you have a link?

    Thanks a lot for the code snippet! Not in a million years would I have gotten to that solution based on the Qt-documentation. I will try to implement it and see how it works.

    Do I understand correctly, that you have to write the JavaScript file as if you write a component in qml rather than "normal" JavaScript code?

    That would mean QQmlApplicationEngine behaves different than QJSEngine. Even though the documentation suggests that QQmlApplicationEngine inherits from QJSEngine. If it truly inherits, should it then not be usable in the exact same way (interpreting JavaScript code rather than qml declarative code)?

    Sorry for being a pain in the butt. Somehow it just doesn't click in my brain. I am having a hard time finding information online or making the transfer from reading the Qt-documentation into actual code. I have to figure this out so I can sleep again I do understand that it takes up your time and effort. If you provide say a bitcoin address, I will make up for it by making a donation


    Added after 44 minutes:


    This just added to the confusion

    Qt Code:
    1. Component {
    2. id: builder
    3. url: getPathToScriptFile() // <--- your "script" URL goes here
    4. }
    To copy to clipboard, switch view to plain text mode 

    This adds an externally defined qml component. Not a JavaScript right?

    I still don't know how to execute a function from a JavaScript like in my code snippet from the previous post... I need to figure out how to do this part
    Qt Code:
    1. QQmlApplicationEngine engine;
    2. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    3.  
    4. engine.evaluate(content, "foo.js");
    5. // how do I call funcA() here ???
    To copy to clipboard, switch view to plain text mode 
    Last edited by motaito; 15th April 2015 at 22:05.

  12. #12
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    That's exactly my problem. I can not for the live of me figure out how the c++ function calls the script "build". It can't possible be that difficult. It is driving me completely nuts!
    I don't understand what is the problem here. At some point your C++ code will "load" the script and store its functions in some variables. Calling the script is as simple as executing QJSValue::call() on it.

    Qt Code:
    1. engine.evaluate(content, "foo.js");
    2. // how do I call funcA() here ???
    To copy to clipboard, switch view to plain text mode 
    Qt Code:
    1. QJSValue myFunction = engine.evaluate(content, "foo.js"); // with foo.js containing "function() { ... }"
    2. myFunction.call();
    To copy to clipboard, switch view to plain text mode 

    I would love to read up on the asset system. I have been google-ing for "QT asset system" for hours now and can't find anything online... Maybe you have a link?
    It's late today, come back tomorrow And seriously this was introduced in Qt 5.4 or 5.3. It allows to use separate versions of the same file on different platforms. As for a link, see the first sentence

    Do I understand correctly, that you have to write the JavaScript file as if you write a component in qml rather than "normal" JavaScript code?
    It depends on the effect you want to achieve. Since you are already in a declarative environment it would be a sin not to use its power.

    That would mean QQmlApplicationEngine behaves different than QJSEngine. Even though the documentation suggests that QQmlApplicationEngine inherits from QJSEngine. If it truly inherits, should it then not be usable in the exact same way (interpreting JavaScript code rather than qml declarative code)?
    No idea what you mean. The behavior of both is the same as far as JavaScript is concerned.

    If you provide say a bitcoin address, I will make up for it by making a donation
    You can donate to Qt Centre Foundation to help maintain this site if you really have to.

    This just added to the confusion

    Qt Code:
    1. Component {
    2. id: builder
    3. url: getPathToScriptFile() // <--- your "script" URL goes here
    4. }
    To copy to clipboard, switch view to plain text mode 

    This adds an externally defined qml component. Not a JavaScript right?
    Yes, of course. That's the whole point of my snippet.

    I still don't know how to execute a function from a JavaScript like in my code snippet from the previous post... I need to figure out how to do this part
    Qt Code:
    1. QQmlApplicationEngine engine;
    2. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    3.  
    4. engine.evaluate(content, "foo.js");
    5. // how do I call funcA() here ???
    To copy to clipboard, switch view to plain text mode 
    You are stuck on trying to fix your wheelchair while a brand new Ferrari waits just besides you.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  13. #13
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    I don't understand what is the problem here. At some point your C++ code will "load" the script and store its functions in some variables. Calling the script is as simple as executing QJSValue::call() on it.
    No, it's not that simple. I found that I have to wrap the function in parenthesis for it to work... Sorry, but that is far from obvious. Also, that way I can only have one single function in one script. I can also not have another script that calls the function from the first script. It's hardly behaving like JavaScript.

    using QQmlApplicationEngine
    Qt Code:
    1. // main.cpp
    2. QQmlApplicationEngine engine;
    3. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    4.  
    5. QJSValue vA = engine.evaluate("function a() { return "a"; }");
    6. QJSValue vB = engine.evaluate("function b() { return a() + " b"; }");
    7. QJSValue vC = engine.evaluate("(function c() { return "c"; })");
    8. QJSValue vD = engine.evaluate("(function d() { return c() + " d"; })");
    9.  
    10. qDebug() << vA.call().toString(); // output: undefined
    11. qDebug() << vB.call().toString(); // output: undefined
    12. qDebug() << vC.call().toString(); // output: "c"
    13. qDebug() << vD.call().toString(); // output: undefined
    To copy to clipboard, switch view to plain text mode 
    using QJSEngine
    Qt Code:
    1. // main.cpp
    2. QQmlApplicationEngine engine;
    3. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    4.  
    5. QJSEngine eng;
    6. eng.evaluate("function a() { return "a"; }");
    7. eng.evaluate("function b() { return a() + " b"; }");
    8. eng.evaluate("(function c() { return "c"; })");
    9. eng.evaluate("(function d() { return c() + " d"; })");
    10.  
    11. QJSValue vA = eng.globalObject().property("a");
    12. QJSValue vB = eng.globalObject().property("b");
    13. QJSValue vC = eng.globalObject().property("c");
    14. QJSValue vD = eng.globalObject().property("d");
    15.  
    16. qDebug() << vA.call().toString(); // output: "a"
    17. qDebug() << vB.call().toString(); // output: "a b"
    18. qDebug() << vC.call().toString(); // output: undefined
    19. qDebug() << vD.call().toString(); // output: undefined
    To copy to clipboard, switch view to plain text mode 
    That is neither the same usage nor the same result. Therefore my statement QQmlApplicationEngine and QJSEngine behave differently!

    But if QQmlApplicationEngine is inherited by QJSEngine that means QJSEngine is actively developed. So, I guess I could just add a QJSEngine to my app and get it to work that way. It seams a bit strange though as QQmlApplicationEngine should already provide the needed functionality.
    It's late today, come back tomorrow
    Of course, no problem. Take your time I am glad you help at all and I don't take it for granted!
    As for a link, see the first sentence
    I must be blind... I rechecked every first sentence from every paragraph you wrote. I find no further info on the asset system nor any link to it. The only link you provided is to QScriptEngine. But never mind. I want to focus on the scripting fist.

    There is also an issue with the example you provided. It seams less intuitive to adjust the build function.

    In my initial solution this would work.
    Qt Code:
    1. function build()
    2. {
    3. function reply(text)
    4. {
    5. var t = parse(text);
    6. updateGUI(t);
    7. }
    8.  
    9. var m = new NetworkManager();
    10. m.fetch("http://www.somePage.com");
    11. }
    To copy to clipboard, switch view to plain text mode 
    I could change the function to this and add more info without having to adjust the app and recompile:
    Qt Code:
    1. function build()
    2. {
    3. function reply(text)
    4. {
    5. var t = parse(text);
    6.  
    7. function innerReply(text)
    8. {
    9. var tInner = t + parseAdditionalInfoFromOtherWebsite(text);
    10. updateGUI(tInner);
    11. }
    12.  
    13. var m2 = new NetworkManager();
    14. m2.fetch("http://www.someOtherPage.com");
    15. }
    16.  
    17. var m = new NetworkManager();
    18. m.fetch("http://www.somePage.com");
    19. }
    To copy to clipboard, switch view to plain text mode 
    In my mind this is much easier to maintain than qml components. But maybe that's because I haven't really figured out how to use components to the fullest of their potential. I am still new to qml.

    You are stuck on trying to fix your wheelchair while a brand new Ferrari waits just besides you.
    I guess I haven't figured out how to really drive a Ferrari yet I will play more with the sample you provided and give you some feedback. However, I also must sleep a bit and go to work tomorrow

    Edit: fixed some typos
    Last edited by motaito; 16th April 2015 at 06:28.

  14. #14
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    No, it's not that simple. I found that I have to wrap the function in parenthesis for it to work... Sorry, but that is far from obvious. Also, that way I can only have one single function in one script. I can also not have another script that calls the function from the first script. It's hardly behaving like JavaScript.
    It is behaving exactly like JavaScript because it is Javascript. What you call "difference" is probably that your code is executed in different contexts as QML probably sandboxes its environment to prevent breaking it by an irresponsible script.

    But if QQmlApplicationEngine is inherited by QJSEngine that means QJSEngine is actively developed. So, I guess I could just add a QJSEngine to my app and get it to work that way.
    You wouldn't be able to talk to your QtQuick scene so I see no benefits of that.

    I must be blind... I rechecked every first sentence from every paragraph you wrote. I find no further info on the asset system nor any link to it. The only link you provided is to QScriptEngine.
    "Come back tomorrow"

    There is also an issue with the example you provided. It seams less intuitive to adjust the build function.

    In my initial solution this would work.
    Qt Code:
    1. function build()
    2. {
    3. function reply(text)
    4. {
    5. var t = parse(text);
    6. updateGUI(t);
    7. }
    8.  
    9. var m = new NetworkManager();
    10. m.fetch("http://www.somePage.com");
    11. }
    To copy to clipboard, switch view to plain text mode 
    I could change the function to this and add more info without having to adjust the app and recompile:
    Qt Code:
    1. function build()
    2. {
    3. function reply(text)
    4. {
    5. var t = parse(text);
    6.  
    7. function innerReply(text)
    8. {
    9. var tInner = t + parseAdditionalInfoFromOtherWebsite(text);
    10. updateGUI(tInner);
    11. }
    12.  
    13. var m2 = new NetworkManager();
    14. m2.fetch("http://www.someOtherPage.com");
    15. }
    16.  
    17. var m = new NetworkManager();
    18. m.fetch("http://www.somePage.com");
    19. }
    To copy to clipboard, switch view to plain text mode 
    The same can be done with my approach.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  15. #15
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    It is behaving exactly like JavaScript because it is JavaScript. What you call "difference" is probably that your code is executed in different contexts as QML probably sandboxes its environment to prevent breaking it by an irresponsible script.
    Fair enough. But how could I then load a JavaScript library that contains more than one function or call a function in one file that uses a function defined in another file?

    Say I have an extended JavaScript library to manipulate/parse strings in various ways. I can not load it with QQmlApplicationEngine::evaluate(). I have to reimplement the library by creating a qml component as a wrapper for the JavaScript library. Is that correct?

    I am asking because that would make QQmlApplicationEngine::evaluate() practically useless. It could not be used for anything more than a trivial function (one single function). And even then you would better write the function inside a qml component. So, it would be better not to have QQmlApplicationEngine::evaluate() at all. Thereby avoiding all the confusion and force a user to work with qml components from the start which would be the new way to do things anyway.

    I'll rewrite my app to use qml components when I get back from work and let you know how it works for me.

  16. #16
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    Fair enough. But how could I then load a JavaScript library that contains more than one function or call a function in one file that uses a function defined in another file?

    Say I have an extended JavaScript library to manipulate/parse strings in various ways. I can not load it with QQmlApplicationEngine::evaluate().
    Well, from purely architectonic point of view you shouldn't want to do that. What you are trying to do thoughout this thread is IMO the exact opposite of what I'd call "scripting". When you script something, you expose some environment for the script to use in a controlled fashion, that is the script has access to nothing more than you allow it to. In your case you want the script to be fully in control of the environment (e.g. be allowed to delete or replace objects it doesn't own).

    If you create a library that manipulates data in a certain way and you want to "register it" in the environment then something in the environment should know how to use it thus all names and algorithms from the script would have to be known in the main environment. In that case you could use the import statement to import the document. Hard to call that scripting.

    I have to reimplement the library by creating a qml component as a wrapper for the JavaScript library. Is that correct?
    No. You create a QML component if you want to use a QML component. It gives you much richer API than a number of dangling functions. And it is definitely more object oriented. I'd say that if you were to do this in pure javascript, you should also expose an object implementing a well-known API instead of providing dangling functions.

    I am asking because that would make QQmlApplicationEngine::evaluate() practically useless. It could not be used for anything more than a trivial function (one single function). And even then you would better write the function inside a qml component. So, it would be better not to have QQmlApplicationEngine::evaluate() at all. Thereby avoiding all the confusion and force a user to work with qml components from the start which would be the new way to do things anyway.
    To be honest I would not want some arbitrary code to interfere with my Qt Quick app. I would prefer to have complete control over what can be done with my environment.

    To have some control over what is going on, you could pass a context object to your hook and scripts could modify that object however they wanted. That would limit the scope of unsanitized actions to that object only.

    javascript Code:
    1. // contextObject.label = some instance of Text in QML scripts are allowed to manipulate
    2.  
    3. function init(contextObject) {
    4. contextObject.myFunc = function() { ... }
    5. }
    6.  
    7. function someOtherHook(contextObject) {
    8. contextObject.myFunc()
    9. contextObject.label.text = "Hello"
    10. contextObject.label.color = "red"
    11. }
    To copy to clipboard, switch view to plain text mode 
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  17. #17
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Well, from purely architectonic point of view you shouldn't want to do that. What you are trying to do thoughout this thread is IMO the exact opposite of what I'd call "scripting". When you script something, you expose some environment for the script to use in a controlled fashion, that is the script has access to nothing more than you allow it to. In your case you want the script to be fully in control of the environment (e.g. be allowed to delete or replace objects it doesn't own).
    That is not what I want to do at all. suppose I would want to do something like this:

    1. go to "http://www.episodeworld.com/show/Big_Bang_Theory/season=8/english"
    2. grab the title of the next episode that comes up
    3. grap the imdb link
    4. go to "http://www.imdb.com/title/tt0898266/"
    5. grab the imdb rating for the show
    6. form a string like:
    "The Big Bang Theory (rating 8.5)
    coming up: The Communication Deterioration (2015-04-16)"
    7. show the result in the GUI

    or

    1. go to "http://www.imdb.com/movies-coming-soon/"
    2. grap the title of the first 5 upcomming movies
    3. form a string like:
    "The Age of Adaline (2015)
    Little Boy (2015)
    The Water Diviner (2014)
    Adult Beginners (2014)
    Misery Loves Comedy (2015)"
    7. show the result in the GUI

    or

    1. to to "http://groundation.com/tour-date/"
    2. grab the next tour date
    3. form a string like:
    "17/APRA Miracle Tour 2015Nantes, France // La Trocardiere"
    7. show the result in the GUI

    From the app side that could all have the same logic (Get info online and present it).

    I define what I have available (extendable in script or config file)
    Qt Code:
    1. // in external JavaScript (buildDef.js)
    2. var buildDef = [
    3. {
    4. identifier: "nextEpisode",
    5. name: "The Big Bang Theory",
    6. url: ""http://www.episodeworld.com/show/Big_Bang_Theory/season=8/english""
    7. buildFunction: episodeBuildFunctionCallback
    8. },
    9. {
    10. identifier: "upcommingMovies",
    11. name: "",
    12. url: "http://www.imdb.com/movies-coming-soon/"
    13. buildFunction: movieBuildFunctionCallback
    14. },
    15. {
    16. identifier: "groundationTour",
    17. name: "",
    18. url: "http://groundation.com/tour-date/",
    19. buildFunction: tourBuildFunctionCallback
    20. }
    21. ];
    To copy to clipboard, switch view to plain text mode 

    In another script I could add build functions as needed.

    Qt Code:
    1. // in external JavaScript (buildFunc.js)
    2. // e.g. in the first case:
    3. // helper
    4. function parseEpList(replyFromNetworkManager)
    5. {
    6. // parse replyFromNetworkManager
    7. return "The Big Bang Theory {0} coming up: The Communication Deterioration (2015-04-16)";
    8. }
    9. // helper
    10. function parseEpListToImdbUrl(replyFromNetworkManager)
    11. {
    12. // parse replyFromNetworkManager
    13. return "http://www.imdb.com/title/tt0898266/";
    14. }
    15. // helper
    16. function parseIMDB(replyFromNetworkManager)
    17. {
    18. // parse replyFromNetworkManager
    19. return "(rating 8.5)";
    20. }
    21. // actual build function callback
    22. function episodeBuildFunctionCallback(index)
    23. {
    24. function reply(text)
    25. {
    26. var epPart = parseEpList(text)
    27.  
    28. function innerReply(text)
    29. {
    30. var imdbPart = parseIMDB(text);
    31. UpdateTextInQml(epPart.replace("{0}", imdbPart));
    32. }
    33.  
    34. var url = parseEpListToImdbUrl(text);
    35. var m2 = new NetworkManager();
    36. m2.fetch(url);
    37. }
    38.  
    39. var m = new NetworkManager();
    40. m.fetch(buildDef[index].url);
    41. }
    42. // add similar functions for the remaining buildDef's.
    To copy to clipboard, switch view to plain text mode 

    Then I only need a build(identifier, name) function or functionality in the qml with the job to do this:
    - find the appropriate build function based on identifier and name
    - run the build function (e.g. episodeBuildFunctionCallback)

    Qt Code:
    1. // in qml
    2. ...
    3. MouseArea {
    4. ...
    5. onClicked: {
    6. // find index for buildDefinition based on identifier, dropdown selection or whatever and initiate the build
    7. buildDef[index].buildFunction(index);
    8. }
    9. }
    10.  
    11. Text {
    12. id: myText
    13. text: "This will be updated."
    14. }
    15. ...
    To copy to clipboard, switch view to plain text mode 


    So, I don't expose all of the environment to the script. Only one Text component needs to be exposed as well as the network manager. It seams to me by loading external qml files, I expose potentially more of the environment.

    The script should be able to create an instance of a network manager and update one single Text component. In case the script fails the Text components is empty. Nothing else in the environment gets touched. Yet I can extend/adjust the "buildDef.js" and "buildFunc.js" as needed if I want to add info that can be found online. I would also add some sort of dropdown that gets filled based on the buildDef's so I can select what Info I want to find.

    The app has one task. Grab info online and present it. The script defines how that happens. Is that not scripting? Do I have the terminology wrong?

    Maybe I use the wrong terminology. But to me that is scripting. If I want to add some other info I add to the buildDef's and add a new buildFunction without having to touch or recompile the app at all. But there will be more than one function in "buildFunc.js" and they will call each other. So, QQmlApplicationEngine::evaluate() can not handle that. Correct?

    I have to find a solution with loading qml components as you pointed out in order to get a clean approach in achieving that. I will try to implement your suggestion later today. And let you know how it goes and whether it solves what I am looking for.

    If you create a library that manipulates data in a certain way and you want to "register it" in the environment then something in the environment should know how to use it thus all names and algorithms from the script would have to be known in the main environment. In that case you could use the import statement to import the document. Hard to call that scripting.
    That sound like what a dll should do. That is certainly not what I am looking for.

  18. #18
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Qt 5.4 - Scriptable Qml Application

    Quote Originally Posted by motaito View Post
    That is not what I want to do at all. suppose I would want to do something like this:
    (snip)
    This looks like "plugins" and not "scripts" to me. The "plugin" exposes functionality and the main application calls it.

    So, I don't expose all of the environment to the script. Only one Text component needs to be exposed as well as the network manager. It seams to me by loading external qml files, I expose potentially more of the environment.
    You complain that the functions you define in the script are not visible from within QML context. If they were your script could call any function on any object defined in QML, effectively taking control over your program.

    Is that not scripting?
    In my opinion -- hardly. And I still think your extension should define a function which should be called by your main application and for that the extension doesn't need access to the scope of the application and the application doesn't need access to the scope of the function (apart the entry point to the function itself). The environment after calling the function should be identical to before the function was called, no symbols should be left over.

    So, QQmlApplicationEngine::evaluate() can not handle that. Correct?
    Evaluate is not a virtual method. Therefor QJSEngine::evaluate() works exactly the same as QQmlApplicationEngine::evaluate().

    That sound like what a dll should do. That is certainly not what I am looking for.
    DLL = "Dynamically loaded library". Hence your "script" is also "DLL". Correct me if I'm wrong but from all your code snippets it looks like the "script" should implement a function with a well defined name and parameters that will be called from the main application (e.g. line #7 of your last snippet). That's exactly the use case I am talking about.

    Furthermore you struggle to obtain a declarative environment using imperative language (e.g. your buildDef.js) while having an existing declarative environment at hand. If you leverage the features of QML you will get an environment where the application will not care whether you implement your "builder" in javascript, C++ or any other language as long as it has a QML binding.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  19. #19
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    Awesome, your post just cleared up a lot of my confusion. Thanks.

    This looks like "plugins" and not "scripts" to me. The "plugin" exposes functionality and the main application calls it.
    Interesting! Maybe I should have looked into building a plugin system instead. I have never even given it any thoughts nor looked at how something like this would be done. The one part that I am unsure about is that I would have a network manager in the application providing functionality. That would then be used in a plugin to get functionality back into the application... Maybe that's why I got stuck on scripts instead of thinking about plugins. I will have to do some reading on the topic. It shouldn't be too hard to build a small plugin system, right?

    In my opinion -- hardly. And I still think your extension should define a function which should be called by your main application and for that the extension doesn't need access to the scope of the application and the application doesn't need access to the scope of the function (apart the entry point to the function itself). The environment after calling the function should be identical to before the function was called, no symbols should be left over.
    I think that cleared up for me where I misunderstood what scripting should be. Or at least why my approach is not really considered scripting.

    Evaluate is not a virtual method. Therefor QJSEngine::evaluate() works exactly the same as QQmlApplicationEngine::evaluate().
    Hmm, I still can not figure out how it can be the same. QQmlApplicationEngine::evaluate() requires parenthesis around the script, QJSEngine::evaluate() does not. I can not have more than one function in QQmlApplicationEngine::evaluate(). Even if they are in a sand-boxed environment or a different context. If they are built the same way I should be able to have more than one function in both cases and later call a function by name... It seams QQmlApplicationEngine::evaluate() must evaluate an expression while QJSEngine::evaluate() loads parts/functions for individual use. Be that as it may, I will not use this approach anyway. I was just hoping to learn how to use it out of interest. I will move on from this and put it behind me.

    DLL = "Dynamically loaded library". Hence your "script" is also "DLL". Correct me if I'm wrong but from all your code snippets it looks like the "script" should implement a function with a well defined name and parameters that will be called from the main application (e.g. line #7 of your last snippet). That's exactly the use case I am talking about.
    Ok, I get now how you look at it. From that perspective yes it's actually pretty much like a dll. That also clears for me why earlier you pointed out that what I am doing is hardly scripting.

    If you leverage the features of QML you will get an environment where the application will not care whether you implement your "builder" in JavaScript, C++ or any other language as long as it has a QML binding.
    I need to get a better grasp of what it means to work with a declarative environment. I am still driving my Ferrari in the parking lot

    I will work a bit with the various ways you suggested to make my app more declarative and give some feedback later whether my issue is solved.

  20. #20
    Join Date
    Apr 2015
    Posts
    26
    Thanks
    2
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Qt 5.4 - Scriptable Qml Application

    I try to implement your suggestion but I keep having issues loading the qml.

    Qt Code:
    1. Component {
    2. id: networkManager
    3. url: "../network/NetworkManagerQml.qml"
    4. }
    To copy to clipboard, switch view to plain text mode 

    I get this error:
    Component elements may not contain properties other than id

    I have also tried this:
    Qt Code:
    1. ...
    2. MouseArea {
    3. id: maRename
    4. anchors.fill: parent
    5. onClicked: {
    6. var component = Qt.createComponent("../network/NetworkManagerQml.qml");
    7. if (component.status == Component.Ready) {
    8. var obj = component.createObject(myText);
    9. if (null == obj)
    10. console.log("Error creating component: " + component.errorString());
    11. else
    12. {
    13. obj.ready.connect(function(text){ myText.text = text; obj.destroy(); });
    14. obj.fetch("http://www.google.com");
    15. }
    16. }
    17. else
    18. console.log("Error loading component " + component.errorString());
    19. }
    20. }
    To copy to clipboard, switch view to plain text mode 
    I get this error:
    Error loading component qrc:/component/network/NetworkManagerQml.qml: 13 Cannot assign to non-existaent default property.

    If it helps my class looks like this:
    Qt Code:
    1. #ifndef NETWORKMANAGER_H
    2. #define NETWORKMANAGER_H
    3.  
    4. #include <QObject>
    5. #include <QUrl>
    6. #include <QNetworkAccessManager>
    7. #include <QNetworkRequest>
    8. #include <QNetworkReply>
    9.  
    10.  
    11. class NetworkManager : public QObject
    12. {
    13. Q_OBJECT
    14.  
    15. public:
    16. NetworkManager(QObject *parent=0);
    17. Q_INVOKABLE void fetch(const QString& url);
    18.  
    19. public slots:
    20. void replyFinished(QNetworkReply*);
    21.  
    22. signals:
    23. void reply(const QString& reply);
    24.  
    25. private:
    26. QNetworkAccessManager* m_NetworkManager;
    27. };
    28.  
    29. #endif // NETWORKMANAGER_H
    To copy to clipboard, switch view to plain text mode 


    my NetworkManagerQml.qml
    Qt Code:
    1. import QtQuick 2.4
    2. import MoeNetworkManager 1.0
    3.  
    4. QtObject {
    5. id: manager
    6. signal ready(string text)
    7.  
    8. function fetch(url) {
    9. networkManager.fetch(url);
    10. }
    11.  
    12. NetworkManagerHandler {
    13. id: networkManager
    14.  
    15. onReply: {
    16. manager.ready(text);
    17. }
    18. }
    19. }
    To copy to clipboard, switch view to plain text mode 

    in my main.cpp
    Qt Code:
    1. int main(int argc, char *argv[])
    2. {
    3. QGuiApplication app(argc, argv);
    4.  
    5. qmlRegisterType<NetworkManager>("MoeNetworkManager", 1, 0, "NetworkManagerHandler");
    6.  
    7. QQmlApplicationEngine engine;
    8. engine.load(QUrl(QStringLiteral("qrc:/component/main.qml")));
    9.  
    10. return app.exec();
    11. }
    To copy to clipboard, switch view to plain text mode 
    Any idea whats going wrong?


    Added after 1 43 minutes:


    Sorry about the stupid question... Sometimes I don't get what I read. I have some dyslexia. In your example QtObject was meant to be a general component. I thought it actually has to be of the type QtObject. If I change it to e.g. Item it all works as expected. I am back on track now doing some more testing

    The second snippet from above actually works. However, this
    Qt Code:
    1. Component {
    2. id: networkManager
    3. url: "../network/NetworkManagerQml.qml"
    4. }
    To copy to clipboard, switch view to plain text mode 
    doesn't work as expected. See error above. But that's ok I can load the object in a function.
    Last edited by motaito; 16th April 2015 at 20:42.

Similar Threads

  1. Replies: 4
    Last Post: 19th November 2012, 14:35
  2. Replies: 3
    Last Post: 28th October 2011, 23:24
  3. Replies: 2
    Last Post: 7th September 2011, 13:12
  4. Replies: 1
    Last Post: 30th May 2011, 13:46
  5. Replies: 3
    Last Post: 6th January 2010, 16:55

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.