Создание консоли JS

Добавлено 9 сентября 2022 в 19:50

В качестве небольшого примера мы создадим JS-консоль. Нам нужно поле ввода, где пользователь может вводить свои выражения JavaScript, и в идеале должен быть список выходных результатов. Поскольку это должно больше походить на десктопное приложение, мы используем модуль Qt Quick Controls.

Совет

JS консоль внутри вашего следующего проекта может быть очень полезна для тестирования. Чтобы использовать ее с умом, вам нужно контролировать область видимости, в которой она вычисляется, например, текущий видимый экран, основная модель данных, объект синглтона или всё вместе.

JS консоль

Для создания проекта пользовательского интерфейса Qt Quick с использованием Qt Quick Controls мы используем Qt Creator. Назовем проект JConsole. После завершения работы мастера у нас уже будет базовая структура приложения с окном приложения и меню для выхода из приложения.

Для ввода мы используем TextField, а для отправки ввода для вычисления – Button. Результат вычисления выражения отображается с помощью ListView с ListModel в качестве модели и двух меток для отображения выражения и вычисленного результата.

Наше приложение будет разделено на два файла:

  • JConsole.qml: основное представление приложения;
  • jsconsole.js: библиотека JavaScript, отвечающая за вычисление пользовательских выражений.

JSConsole.qml

Окно приложения

// JSConsole.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import "jsconsole.js" as Util

ApplicationWindow {
    id: root
    
    title: qsTr("JSConsole")
    width: 640
    height: 480
    visible: true

    menuBar: MenuBar {
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit()
            }
        }
    }

Форма

ColumnLayout {
    anchors.fill: parent
    anchors.margins: 9
    RowLayout {
        Layout.fillWidth: true
        TextField {
            id: input
            Layout.fillWidth: true
            focus: true
            onAccepted: {
                // вызываем нашу функцию вычисления в root
                root.jsCall(input.text)
            }
        }
        Button {
            text: qsTr("Send")
            onClicked: {
                // вызываем нашу функцию вычисления в root
                root.jsCall(input.text)
            }
        }
    }
    Item {
        Layout.fillWidth: true
        Layout.fillHeight: true
        Rectangle {
            anchors.fill: parent
            color: '#333'
            border.color: Qt.darker(color)
            opacity: 0.2
            radius: 2
        }

        ScrollView {
            id: scrollView
            anchors.fill: parent
            anchors.margins: 9
            ListView {
                id: resultView
                model: ListModel {
                    id: outputModel
                }
                delegate: ColumnLayout {
                    id: delegate
                    required property var model
                    width: ListView.view.width
                    Label {
                        Layout.fillWidth: true
                        color: 'green'
                        text: "> " + delegate.model.expression
                    }
                    Label {
                        Layout.fillWidth: true
                        color: delegate.model.error === "" ? 'blue' : 'red'
                        text: delegate.model.error === "" ? "" + delegate.model.result : delegate.model.error
                    }
                    Rectangle {
                        height: 1
                        Layout.fillWidth: true
                        color: '#333'
                        opacity: 0.2
                    }
                }
            }
        }
    }
}

Вызов библиотеки

Функция вычисления jsCall выполняет вычисление не сама по себе, она была перемещена в модуль JS (jsconsole.js) для более четкого разделения.

import "jsconsole.js" as Util
function jsCall(exp) {
    const data = Util.call(exp)
    // вставляем результат в начало списка
    outputModel.insert(0, data)
}

Совет

Для безопасности мы не используем функцию eval из JS, так как она позволит пользователю изменить локальную область видимости. Мы используем конструктор Function для создания функции JS во время выполнения и передаем нашу область видимости в качестве переменной. Поскольку функция создается каждый раз, она не действует как замыкание и не сохраняет свою собственную область видимости, нам нужно использовать this.a = 10 для хранения значения внутри этой области видимости функции. Эта область устанавливается скриптом в переменную scope.

jsconsole.js

// jsconsole.js
.pragma library

const scope = {
    // наша пользовательская область видимости, внедренная в нашу функции вычисления
}

function call(msg) {
    const exp = msg.toString()
    console.log(exp)
    const data = {
        expression : msg,
        result: "",
        error: ""
    }
    try {
        const fun = new Function('return (' + exp + ')')
        data.result = JSON.stringify(fun.call(scope), null, 2)
        console.log('scope: ' + JSON.stringify(scope, null, 2), 'result: ' + data.result)
    } catch(e) {
        console.log(e.toString())
        data.error = e.toString()
    }
    return data
}

Данные, возвращаемые функцией call, представляют собой объект JS со свойствами результата, выражения и ошибки: данные: data: { expression: "", result: "", error: "" }. Мы можем использовать этот JS-объект непосредственно внутри ListModel и затем получить к нему доступ из делегата, например, delegate.model.expression дает нам входное выражение.

Теги

GUI / Графический интерфейс пользователяJavaScriptQMLQtQtQuickQtQuick ControlsПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.