I am trying to create a standard JS library that is mostly shaped like Qbs (which uses deprecated QScriptEngine
) with QJSEngine
, so people who make Qt software can add things like file-operations to their plugin JS environment.
You can see the repo here
I've got basic classes exposed to the JS engine, like this:
QJSEngine jsEngine;
jsEngine.installExtensions(QJSEngine::AllExtensions);
jsEngine.globalObject().setProperty("BinaryFile", jsEngine.newQMetaObject(&Qbs4QJS::BinaryFile::staticMetaObject));
but I can's seem to figure out how to get a reference to the QJSEngine
, inside a function, so I can throw an error:
Q_INVOKABLE BinaryFile(const QString &filePath, QIODevice::OpenModeFlag mode = QIODevice::ReadOnly) {
m_file = new QFile(filePath);
if (!m_file->open(mode)) {
// how do I get jsEngine, here
jsEngine->throwError(m_file->errorString());
}
}
I'd like it if I could somehow derive the calling engine from inside the function, so the class could be exposed to several separate engine instances, for example.
I saw QScriptable and it's engine()
method, but couldn't figure out how to use it.
I added
Depends { name: "Qt.script" }
in my qbs file, and
#include <QtScript>
but it still isn't throwing the error with this (just fails silently):
#include <QObject>
#include <QString>
#include <QFile>
#include <QIODevice>
#include <QFileInfo>
#include <QtScript>
namespace Qbs4QJS {
class BinaryFile : public QObject, protected QScriptable
{
Q_OBJECT
public:
Q_ENUM(QIODevice::OpenModeFlag)
Q_INVOKABLE BinaryFile(const QString &filePath, QIODevice::OpenModeFlag mode = QIODevice::ReadOnly) {
m_file = new QFile(filePath);
// should check for false and throw error with jsEngine->throwError(m_file->errorString());
if (!m_file->open(mode)){
context()->throwError(m_file->errorString());
}
}
private:
QFile *m_file = nullptr;
};
} // end namespace Qbs4QJS
I may be confused about it, too, but it seems like it's using QScriptEngine
, which I'm trying to get away from.
What is the best way to accomplish the task of adding a class that QJSEngine
can use, which has cpp-defined methods that can throw errors in the calling engine?
The object under construction does not have any association with QJSEngine
yet. So you can only do one of the following alternatives:
QJSEngine
in your whole application.QThreadStorage
) if you can ensure that there is only one engine per thread.QJSValue
parameter.Solution 4.: Passing the engine implicitly via a QJSValue
parameter.
I assume that your throwing constructor always has a parameter. QJSValue
has a (deprecated) method engine() which you then could use. You can replace any parameter in a Q_INVOKABLE
method with QJSValue
instead of using QString
and friends.
class TextFileJsExtension : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE TextFileJsExtension(const QJSValue &filename);
};
TextFileJsExtension::TextFileJsExtension(const QJSValue &filename)
{
QJSEngine *engine = filename.engine();
if (engine)
engine->throwError(QLatin1String("blabla"));
}
I guess there is a reason why it is deprecated, so you could ask the QML team, why and what alternative you could use.
Solution 5 Implement a JS wrapper for the constructor
This builds upon solution 4. and works even for parameter-less constructors. Instead of registering your helper class directly like this:
engine->globalObject().setProperty("TextFile", engine->newQMetaObject(&TextFile::staticMetaObject));
You could write an additional generator class and a constructor wrapper in JS. Evaluate the wrapper and register this function as the constructor for your class. This wrapper function would pass all desired arguments to the factory method. Something like this:
engine->evaluate("function TextFile(path) { return TextFileCreator.createObject(path);
TextFileCreator
is a helper class that you would register as singleton. The createObject()
method would then finally create the TextFile
object and pass the engine as a paremter:
QJSValue TextFileCreator::createObject(const QString &path)
{
QJSEngine *engine = qmlEngine(this);
return engine->createQObject(new TextFile(engine, filePath));
}
This gives you access to the QJSEngine in the TextFile
constructor and you can call throwError().