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