Я разрабатываю электронную таблицу для отслеживания своих расходов и попутно использую appscript, чтобы добавить больше функциональности. Есть несколько вещей, которые я хотел бы сделать, но которые кажутся невозможными в приложениях и требуют для работы HTML, но я понятия не имею, с чего начать, и у меня нет опыта кодирования HTML.
У меня есть скрипт, привязанный к простому триггеру onOpen(e).
function addMenu() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Operations")
.addSubMenu(ui.createMenu("Add").addItem(TypeCategory, addCategory.name).addItem(TypeBill, addBill.name).addItem(TypeTarget, addTarget.name))
.addSubMenu(ui.createMenu("Delete").addItem(TypeCategory, deleteCategory.name).addItem(TypeBill, deleteBill.name).addItem(TypeTarget, deleteTarget.name))
.addSubMenu(ui.createMenu("Transfer").addItem(TypeCategory, transferCategories.name).addItem(TypeBill, transferBills.name).addItem(TypeTarget, transferTargets.name))
.addSubMenu(ui.createMenu("Modify").addItem(TypeCategory, "test").addItem(TypeBill, "test").addItem(TypeTarget, "test"))
.addToUi();
}
Просто сосредоточьтесь на скрипте transferCategory в подменю Transfer:
function transferCategories() {
let sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
let transferRange = sheet.getRange("G7:G9");
let categoryRange = sheet.getRange("F12:F");
let amount2Add = sheet.getRange("G9");
let originCategory = sheet.getRange("G7");
let destinationCategory = sheet.getRange("G8");
if (originCategory.isBlank() && destinationCategory.isBlank() && amount2Add.isBlank()) { Browser.msgBox("Please specify all fields!"); return }
if (!originCategory.isBlank() && destinationCategory.isBlank() && amount2Add.isBlank()) { Browser.msgBox("Please specify all fields!"); return }
if (originCategory.isBlank() && !destinationCategory.isBlank() && amount2Add.isBlank()) { Browser.msgBox("Please specify all fields!"); return }
if (!originCategory.isBlank() && !destinationCategory.isBlank() && amount2Add.isBlank()) { Browser.msgBox("Please specify an amount to transfer!"); return }
if (originCategory.isBlank() && !destinationCategory.isBlank() && !amount2Add.isBlank()) { Browser.msgBox("Please specify an origin category!"); return }
if (!originCategory.isBlank() && destinationCategory.isBlank() && !amount2Add.isBlank()) { Browser.msgBox("Please specify a destination category!"); return }
if ((originCategory.getValue() === destinationCategory.getValue()) && amount2Add.getValue() < 0) {
Browser.msgBox("Cannot transfer between categories of the same name!\\nAmount to transfer must be greater than zero!");
return;
}
if ((originCategory.getValue() === destinationCategory.getValue()) && isNaN(amount2Add.getValue())) {
Browser.msgBox("Cannot transfer between categories of the same name!\\nAmount to transfer must be a number!");
return;
}
if ((originCategory.getValue() === destinationCategory.getValue()) && !isNaN(amount2Add.getValue()) && amount2Add.getValue() >= 0) {
Browser.msgBox("Cannot transfer between categories of the same name!");
return;
}
if ((originCategory.getValue() != destinationCategory.getValue()) && amount2Add.getValue() < 0) {
Browser.msgBox("Amount to transfer must be greater than zero!");
return;
}
if ((originCategory.getValue() != destinationCategory.getValue()) && isNaN(amount2Add.getValue())) {
Browser.msgBox("Amount to transfer must be a number!");
return;
}
let overflowColumn = 10;
let allocatedAmountRow = 12; //Row number of first value
let allocationAmountColumn = 8;
let previousAllocationAmountColumn = 7;
let rowOrigin = categoryRange.getValues().filter(r => r!= "").flat().indexOf(originCategory.getValue());
let rowDestination = categoryRange.getValues().filter(r => r!= "").flat().indexOf(destinationCategory.getValue());
//Confirm that amount to deduct from origin category is enough
let originCell = sheet.getRange(allocatedAmountRow + rowOrigin, overflowColumn);
let originAmount = originCell.getValue();
if (originAmount < amount2Add.getValue()) { Browser.msgBox("Not enough funds to transfer!"); return; }
//Move values
let originAllocationCell = sheet.getRange(allocatedAmountRow + rowOrigin, allocationAmountColumn);
let remainingAllocationAmount = originAllocationCell.getValue() - amount2Add.getValue();
if (remainingAllocationAmount < 0) {
let prevAllocationCell = sheet.getRange(allocatedAmountRow + rowOrigin, previousAllocationAmountColumn);
let destinationAllocation = sheet.getRange(allocatedAmountRow + rowDestination, allocationAmountColumn);
prevAllocationCell.setValue(parseFloat(prevAllocationCell.getValue()) - parseFloat(Math.abs(remainingAllocationAmount)));
originAllocationCell.setValue(0);
destinationAllocation.setValue(destinationAllocation.getValue() + parseFloat(amount2Add.getValue()));
transferRange.clearContent();
return;
}
originAllocationCell.setValue(originAllocationCell.getValue() - amount2Add.getValue());
let destinationAllocation = sheet.getRange(allocatedAmountRow + rowDestination, allocationAmountColumn);
destinationAllocation.setValue(parseInt(destinationAllocation.getValue()) + parseFloat(amount2Add.getValue()));
transferRange.clearContent();
return;
}
По сути, этот скрипт позволяет пользователю переносить суммы распределения между двумя категориями расходов.
Обратите внимание, что этот сценарий изначально был написан с намерением, чтобы пользователь вводил все, что есть в самом листе, в две ячейки с раскрывающимися списками, которые ссылаются на категории расходов, с указанием категории происхождения и назначения. В отдельной ячейке также будет указана сумма для перевода. Сам скрипт изначально был привязан к кликабельному рисунку. Метод довольно длинный, но важно то, что я хочу переместить все в поле ввода браузера. Проведя небольшое исследование, я понял, что класс Browser не позволяет вводить несколько элементов, которые вы выбираете из раскрывающихся окон. Видимо, мне нужен класс HtmlService. К сожалению, документация не очень полезна.
Короче говоря, мне нужно окно, похожее по стилю на окно Browser.msgBox():
В окне будет соответствующая подсказка с заголовком и желательно три окна или поля (панель параметров?), два с именованными раскрывающимися списками, которые связаны со столбцами на листе, из которых можно выбирать, и одно, в котором можно ввести сумму. Предпочтительно, чтобы были кнопки «ОК» и «Отмена» (с регулируемым положением). Когда нажата кнопка «ОК», вызывается transferCategory, и все связанные сообщения об ошибках передаются в новое окно, в котором отображается соответствующее сообщение, из которого пользователь может выбрать попытку еще раз или отменить операцию. В случае успешной передачи также отображается сообщение с подтверждением. Я заметил, что класс Browser приостанавливает выполнение сценария, тогда как в примерах, которые я видел для HTMLService, это не так. Я думаю, что пауза здесь не нужна.
Буду признателен за шаблон о том, как начать работу.






Похоже, вам нужна боковая панель. Похоже, вы захотите использовать шаблонный HTML для создания контента на боковой панели. Вы можете создать массивы двух описанных вами категорий, превратить их в раскрывающиеся списки с помощью HTML и добавить поле ввода для суммы.
Вот основная идея, с которой можно начать.
код.gs
var testVariable = ['option1','option2','option3'];
function onOpen() {
var ui = SpreadsheetApp.getUi()
var menu = ui.createMenu('Testing');
menu.addItem('Test', 'testFunction').addToUi();
}
function testFunction() {
var ui = SpreadsheetApp.getUi();
var tmp = HtmlService.createTemplateFromFile('page.html');
tmp.testVariable = testVariable;
var html = tmp.evaluate();
html.setTitle('My custom sidebar');
ui.showSidebar(html);
}
страница.html
<!DOCTYPE html>
<html>
<head>
<base target = "_top">
</head>
<body>
<script>
<? var data = testVariable ?>
</script>
<div id = "targetDiv">
<label for = "category">Choose a category:</label>
<select name = "category" id = "category">
<? for (var i = 0 ; i < data.length ; i++) { ?>
<option value = "<?= data[i] ?>"><?= data[i] ?></option>
<? } ?>
</div>
</body>
</html>
Вам придется немного больше прочитать, чтобы изучить HTML и настроить форму. Вам также необходимо прочитать документацию google.script.run, чтобы передать вашу форму обратно в Apps Script. (И, возможно, прочитайте немного CSS, чтобы стилизовать свою форму, если вам интересно.)
Документация Google для ознакомления:
https://developers.google.com/apps-script/guides/dialogs
https://developers.google.com/apps-script/guides/html/templates
https://developers.google.com/apps-script/guides/html/communication
Главным образом потому, что я не думаю, что какие-либо другие элементы диалога будут работать для того, что вам нужно (2 раскрывающихся списка + кнопки ввода + очистки/отправки). Кажется, что боковая панель охватывает ваши базы, оставаясь при этом внутри самого Google Sheet. Для этого вам не нужно изучать огромный объем HTML, особенно если это предназначено только для вас или небольшой группы пользователей.
Это здорово, я попробую. Для начала я также прохожу несколько руководств по HTML. Не могли бы вы рассказать, почему, по вашему мнению, боковая панель более уместна?