spatialillusions / milsymbol

Military Symbols in JavaScript
www.spatialillusions.com/milsymbol
MIT License
571 stars 139 forks source link

Cannot call method 'apply' of undefined" when trying to use library in a QJsEngine (Qt6) #300

Closed flexd closed 1 month ago

flexd commented 2 months ago

Hi there!

I'm trying to make a QT Image Provider using this library, via the QJSEngine javascript engine in Qt. I realise this might be a bit (way?) out of scope, but trying to load the latest release milsymbol.js all I'm getting is the error warning: Error creating symbol: "TypeError: Cannot call method 'apply' of undefined" when trying to do essentially the first line in documentation, but in C++.

The following C++ Qt code should essentially be doing the same as var symbol = new ms.Symbol("SFG-UCI----D"),, but fails with that error on that step.

#include <QCoreApplication>
#include <QJSEngine>
#include <QFile>
#include <QDebug>

void loadMilsymbol(QJSEngine &engine) {
    QFile file("path/to/milsymbol.js");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Failed to open milsymbol.js";
        return;
    }
    QTextStream stream(&file);
    QString script = stream.readAll();
    file.close();

    QJSValue result = engine.evaluate(script);
    if (result.isError()) {
        qWarning() << "Error loading milsymbol.js:" << result.toString();
    }
}

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    QJSEngine engine;
    loadMilsymbol(engine);

    // Your code to use milsymbol.js goes here

    QJSValue ms = engine.globalObject().property("ms");
    QJSValue symbolConstructor = ms.property("Symbol");

    if (!symbolConstructor.isCallable()) {
        qWarning() << "Symbol is not a function";
    } else {
        QJSValueList args;
        args << QJSValue("SFG-UCI----D"); // SIDC code for the symbol
        QJSValue symbol = symbolConstructor.call(args);

        if (symbol.isError()) {
             qWarning() << "Error creating symbol:" << symbol.toString();
        } else {
            QJSValue svg = symbol.property("asSVG").call();
            if (svg.isError()) {
                qWarning() << "Error getting SVG:" << svg.toString();
            } else {
                qDebug() << "SVG representation of the symbol:" << svg.toString();
           }
       }
   }
   return app.exec();
}

I also happened to see this, which may be a better approach https://github.com/cneben/milsymbol-qml

In the end what I want is to easily be able to show these icons in a map (using Qt+Qml)

flexd commented 1 month ago

I think the Qt6 Javascript engine only supports the 7th edition ECMAScript (ECMAScript 2016). Is it possible to generate milsymbol.js that supports that standard? @spatialillusions

flexd commented 1 month ago

Got some help from Qt support, turns out you need to use callAsConstructor(...) and callWithInstance(..) to do those calls, so the updated working code is like this

#include <QApplication>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QJSEngine>
#include "mainwindow.h"

void loadMilsymbol(QJSEngine &engine)
{
    QFile file("milsymbol.development.js");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Failed to open milsymbol.js";
        return;
    }
    QTextStream stream(&file);
    QString script = stream.readAll();
    file.close();

    QJSValue result = engine.evaluate(script);
    if (result.isError()) {
        qWarning() << "Error loading milsymbol.js:" << result.toString();
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QJSEngine engine;
    loadMilsymbol(engine);

    QJSValue ms = engine.globalObject().property("ms");
    QJSValue symbolConstructor = ms.property("Symbol");

    if (!symbolConstructor.isCallable()) {
        qWarning() << "Symbol is not a function";
    } else {
        QJSValueList args;
        args << QJSValue("SFG-UCI----D");                            // SIDC code for the symbol
        QJSValue symbol = symbolConstructor.callAsConstructor(args); // <--- updated here

        if (symbol.isError()) {
            qWarning() << "Error creating symbol:" << symbol.toString();
        } else {
            QJSValue svg = symbol.property("asSVG").callWithInstance(symbol); // and here
            if (svg.isError()) {
                qWarning() << "Error getting SVG:" << svg.toString();
            } else {
                qDebug() << "SVG representation of the symbol:" << svg.toString();
            }
        }
    }

    return app.exec();
}
spatialillusions commented 1 month ago

If you would be able to create a repository with a small sample application, I will link to it since I think there are other that would be interested in that.