4

I am starting with Qt and one of my projects is using QJSEngine to evaluate javascript and I want to provide an entire API to the script, with classes and global functions.

Right now my program provides only the ECMAScript default stuff (eval, encodeURI, parseInt, etc...), but I need to expose some custom classes to the code, like the browsers API (WebSocket class, Image class, document object). For example:

var obj = new CustomClass("", 0);
var ret = obj.customClassMethod("[...]!");
customFunction(ret);

I need to define the behavior of the classes in C++, it wouldn't help evaluate the classes definition and let the user code run.

Guerreiro
  • 143
  • 1
  • 11

4 Answers4

9

In contrast to QScriptEngine, where you can add custom classes if they inherit from QObject by using the Q_SCRIPT_DECLARE_QMETAOBJECT macro, the QJSEngine does not directly provide this functionality.

You can still use the Qt Meta-object system to provide interfaces for Javascript, but you have to instantiate the object in C++ and add it to the Javascript context. Then its slots, methods defined with Q_INVOKABLE, and properties defined with Q_PROPERTY are all accessible from within the Javascript runtime.

Now you can create a factory which creates instances of your custom class CustomClass for a given QJSEngine wrapped as Javascript objects:

class CustomClassFactory : public QObject
{
    Q_OBJECT
public:
  CustomClassFactory(QJSEngine* engine) : m_engine(engine) {}
  Q_INVOKABLE QJSValue createInstance() {
      // The engine takes ownership and destroys the object if no longer required.
      return m_engine->newQObject(new CustomClass());
  }
private:
    QJSEngine* m_engine;
}

A factory instance needs to be constructed and added to the global object of the Javascript runtime:

QJSEngine engine;
QJSValue factoryObj = engine.newQObject(new CustomClassFactory());
engine.globalObject().setProperty("_customClassFactory", factoryObj);

Now we can construct objects in Javascript with:

var obj = _customClassFactory.createInstance()

As we've come this far, lets additionally inject a constructor for the custom class into the Javascript runtime:

QJSEngine engine;
// Again, the QJSEngine will take ownership of the created object.
QJSValue factoryObj = engine.newQObject(new CustomClassFactory());
engine.globalObject().setProperty("_customClassFactory", factoryObj);
engine.evaluate(
    "function CustomClass() {"
    "    return _customClassFactory.createInstance()"
    "}");

Et voilà, now you can construct C++ object in Javascript, like you would custom Javascript classes:

var obj = new CustomClass()

For the mentioned WebSocket API you could wrap QtWebSocket for that purpose – that was exactly what I required when I came up with the proposed approach.

Note that for the sake of simplicity I omitted parameters for the constructor, but they can simply be added as well.

PS: I would have added more links to the official documentation, but due to the lack of reputation I'm not allowed to.

Max
  • 1,387
  • 1
  • 15
  • 29
  • This was helpful! Works for a class Color:public QObject, with properties r,g,b plus getR/G/B and setR/G/B, three constructors and methods toHex and toStr. BUT: as soon as I use toString, the method returns s.th. like "Color(0x564cfa5c3c10)". Any ideas? – laune May 10 '20 at 09:03
  • @laune `QColor` probably has no `toString()` member. What you are looking for is probably [`QColor.name()`](https://doc.qt.io/qt-5/qcolor.html#name). For a Hex-String, you should be able to call `color.name(0)` in Javascript. – Max May 11 '20 at 06:26
  • The name "Color" is coincidental; Color has nothing to do with QColor. If I replace the method name "toString" in my custom class Color with "toStr" it works just fine. Is there some rule that you cannot override QObject's to methods? – laune May 11 '20 at 09:57
4

In Qt 5.8 a new feature was added to QJSEngine: newQMetaObject

You simple add the static meta object e.g. &MyQObjectDerivedClass::staticMetaObject to the JSEngine using the above function.

You will then be able to new those objects in Javascript from within QML. I have found this a very neat solution.

As the documentation says you must mark you constructor Q_INVOKABLE or you won't be able to instantiate an object of your class.

The property system (setters/getters) works as expected as do slots.

https://doc.qt.io/qt-5/qjsengine.html#newQMetaObject

Here is my test code - first is the C++ part that adds the meta object

QQmlApplicationEngine engine;
QJSValue jsMetaObject = engine.newQMetaObject(&MyClassOfObject::staticMetaObject);
engine.globalObject().setProperty("MyClassOfObject", jsMetaObject);

I can now write JS that news an object of that type and use setters/getters etc. This code actually exists in a MouseArea onClicked handler for manual testing.

var bob = new MyClassOfObject();
print(bob.x);
bob.x = 918264;
print(bob.x);
print(bob.words);

And here is the class definition...

class MyClassOfObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int x READ getX WRITE setX)
    Q_PROPERTY(int y READ getY WRITE setX)
    Q_PROPERTY(QStringList words READ getWords)    
public:
    Q_INVOKABLE explicit MyClassOfObject(QObject *parent = nullptr);
public slots:
    int getX() const { return x; }
    int getY() const { return y; }
    void setX(int x) { this->x = x; }
    void setY(int y) { this->y = y; }
    QStringList getWords() const;
private:
    int x = -113;
    int y = 616;
    QStringList stringList;
};
  • QJSEngine complains: "ReferenceError: Color is not defined" after following your proposal with a class Color:public QObject with properties r,g,b. 3 constructors marked as Q_INVOKABLE. Anything I might miss? I managed this using QScriptEngine. Any ideas? – laune May 10 '20 at 08:48
  • 1
    @laune - I've updated the original post. I hope this helps. I would check the name given in "setProperty" first. Sorry if you got a lot of "edit" messages. I'm very new to SO! – matthew.kuiash May 10 '20 at 12:11
  • Thanks a lot - it works! I ifdef'd the setProperty from the previous effort (see Max' answer) completely away, thinking that the newQMetaObject method would take care of everything. Actually, it might, if the class name is good as the property name, but now I see that the enhanced flexibility re name and containing object still warrants the extra call. – laune May 10 '20 at 15:04
  • 1
    @laune. Yes - if you need to do this a lot then one could use the `className` member of the static meta object to automatically deduce the property name. – matthew.kuiash May 11 '20 at 07:08
1

As of Qt5.5 QScriptEngine has been deprecated, so only QJsEngine should be used in the future. https://wiki.qt.io/New_Features_in_Qt_5.5#Deprecated_Functionality

coterobarros
  • 941
  • 1
  • 16
  • 25
-2

If you look up Documentation of QScriptEngine, or by searching "QScriptEngine examples" you can find some stuff about making Custom C++ Classes available to QScriptEngine.

Here is a good place to start: link to example

QScriptEngine is very similiar to QJsEngine, so it shouldn't be a big problem for you.

Hope this helps :)

user2840647
  • 1,236
  • 1
  • 13
  • 32