Способ создания формы обратной связи с использованием инструмента "формы", рассмотренный в предыдущей статье, не лишен недостатков. В этот раз я расскажу, как написать более "продвинутый" feedback. С валидацией, регистрацией пользователей, блек-джеком и прочими свистелками :). Не самый легкий для изложения материал, поэтому сразу хочу попросить уважаемого читателя запастись терпением. В свою очередь постараюсь, чтобы к финалу композиции, состоящей из трех частей: фронтэнда, бэкэнда и скрипта регистрации, - понимание вопроса, а также его решения, возникло.
Начнем с бэкэнда - таблицы. Заходим на Google Диск, нажимаем большую красную кнопку: Создать - Таблица. Я назвал ее TestFeedbackNoForm. Единственный лист таблицы назовем "Сообщения". Нетрудно догадаться, что этот самый лист будет содержать информацию о сообщениях пользователей нашей системы. Добавим немного интерактива, для чего реализуем возможность отвечать на сообщения, плюс возможность пользователей получить ответ.
Как-то запутанно получилось. Короче. Пишем заголовки столбцов, добавляем тестовые данные.
По-моему становится понятнее о чем толкую.
С целью более наглядного представления о наших вопросах-ответах нарисуем интерфейс. Открываем редактор скриптов: Инструменты - Редактор скриптов. Назовем проект TestFeedbackNoForm.
Пишем функцию отображения информации о нашем вопросе, для чего переименуем созданную по-умолчанию функцию myFunction() в openRequest().
Добавим проверку на то, что открыт именно тот лист, который нужен (пока в таблице он один, но позже будут еще), и убедимся что выбран именно диапазон с данными:
Пришло время добавить в таблицу еще один лист, в котором будет храниться информация о зарегистрированных пользователях нашей системы.
Нажимаем на плюсик в левом нижнем углу таблицы, называем лист "Пользователи", пишем заголовки столбцов и добавляем одного тестового пользователя.
В поле E-mail пишем адрес ящика текущего аккаунта Google. Еще раз затестим наш код после активации листа "Пользователи"... то, что нужно.
Пишем функцию, которая будет возвращать нам объект - пользователя, со свойствами: имя, e-mail и телефон.
Пишем функцию создания меню.
Для реализации функционала кнопок создадим новый файл скрипта: Редактор скриптов - Файл - Создать - Скрипт. Назовем его Кнопки.gs.
Начнем с функции закрытия интерфейса.
Такой вот бэкэнд получился.
Переходим к фронтэнду. Создаем файл скрипта: Google Диск - Создать - Еще - Скрипт. Я назвал его TestFeedbackNoForm.
Переименуем myFunction() в doGet() и нарисуем интерфейс.
Публикуем приложение: Публикация - Развернуть как веб-приложение - Кто имеет доступ к приложению: Все, включая анонимных пользователей - Развернуть.
Тестируем приложение: В окне "Развертывание как веб-приложения" нажимаем на ссылку "последнюю версию кода".
В функцию doGet() перед строкой "return app;" добавим код валидации на клиенте:
Сохраняем изменения, обновляем вкладку браузера с интерфейсом приложения и тестим наши кнопки.
Еще немного подредактируем код функции doGet(). После валидации добавим код обработчиков событий нажатия на кнопки.
Функции будут обращаться к таблице по ID (в адресной строке браузера - между "key=" и "#"), поэтому для хранения этого самого ID предлагаю использовать глобальную переменную. Можно использовать свойства скрипта и вытаскивать значение с помощью метода ScriptProperties.getProperty(), но глобальная переменная по-моему лучше, так как у свойств скрипта есть ограничения по количеству обращений (не знаю с чем это связано - вопросы к Google).
Итак, создаем новый файл скрипта: Файл - Создать - Скрипт. Назовем его "Переменные". Удаляем созданную по-умолчанию функцию myFunction() и пишем нашу переменную.
Создадим еще один файл скрипта, назовем его "Сообщения" и разместим в нем код функций, отображающих сообщения.
Добавляем функцию проверки.
Теперь проверим несуществующий Вопрос №4.
А теперь попробуем ввести несуществующего пользователя.
Наконец, предлагаю раскомментировать строку //var x = y; и проверить как отработает наш код в случае ошибки.
Продолжаем разговор. Закомментируем проверочную строку обратно. Пишем функцию отправки нового вопроса.
После этого обновляем приложение... и получаем ошибку: "Для выполнения этого действия необходима авторизация.". Это случилось из-за того, что в коде функции addRecord() мы упомянули службу MailApp, а также пытаемся добавить значения в таблицу: shRequests.getRange(intRow + 1, 1, 1, 7).setValues(бла-бла-бла). Можете попробовать закомментировать строку MailApp.sendEmail(бла-бла-бла); и/или shRequests.getRange(intRow + 1, 1, 1, 7).setValues(бла-бла-бла), ничего не изменится - ошибка.
Короче надо авторизовать приложение для работы со службами SpreadsheetApp и MailApp.
Для этого можно выбрать функцию addRecord в списке выбора функций и, например, нажать паучка - "Отладка", или стрелочку - "Выполнить".
Нажимаем на кнопочку "Авторизовать".
Обновляем приложение, отправляем сообщение.
Таким образом мы получили еще один вопрос пользователя в нашу таблицу.
Кроме того мы отправили уведомление на e-mail пользователя о получении и регистрации его вопроса.
Подведем промежуточный итог. На текущем этапе наша система обратной связи вполне работоспособна, за исключением того, что пользователей пока необходимо забивать вручную.
Решаем вопрос. Добавляем в таблицу TestFeedbackNoForm лист, назовем его "Регистрация". Пишем заголовки столбцов.
Регистрацию пользователей реализуем следующим образом: сохраняем данные о пользователе в только что созданном листе "Регистрация", в поле key пишем случайную последовательность символов - ключ регистрации, отправляем на e-mail пользователя ссылку на скрипт подтверждения регистрации.
Займемся этим самым скриптом. Идем на Google Диск - Создать - Еще - Скрипт, назовем его TestFeedbackNoFormReg.
Переименуем созданную по-умолчанию функцию в doGet(e), пишем код функции.
Публикуем приложение: Публикация - Развернуть как веб-приложение - Кто имеет доступ к приложению: Все, включая анонимных пользователей - Развернуть.
Тестируем... снова необходима авторизация. В окне редактора скриптов выбираем функцию doGet() и запускаем ее.
Нажимаем "Авторизовать". Обновляем веб-приложение в окне браузера...
"Скрипт был выполнен, но ничего не возвратил.". Что и требовалось, так как мы не прописали в адресной строке ключ.
Открываем лист "Пользователи" таблицы TestFeedbackNoForm, вырезаем данные о нашем единственном пользователе user1 и вставляем их на лист "Регистрация". Добавим ключ 123.
Переходим на вкладку со скриптом регистрации, в адресной строке в самом конце адреса после "/dev" добавляем параметр скрипта - "?key=123".
Адрес должен выглядеть приблизительно так: "https://script.google.com/macros/s/AKfycbykejcoOVMcJJyEyf6feUjLndcA172Lv86AFKk-_2Y/dev?key=123"
Обновляем страницу.
Возвращаемся в таблицу TestFeedbackNoForm. Наш пользователь исчез с листа "Регистрация" и вернулся на лист "Пользователи". В поле "Ключ" у него появилось значение - "123".
Проверяем почту.
Примерно такое же сообщение будут получать наши пользователи после завершения регистрации.
Остается добавить в скрипт TestFeedbackNoForm функцию обработчик события нажатия на кнопку "Регистрация", которая будет формировать ссылку на скрипт регистрации со случайным ключем, отправлять эту ссылку пользователю и одновременно добавлять данные о регистрации на лист "Регистрация" таблицы TestFeedbackNoForm.
Для того, чтобы сформировать правильную ссылку, которая будет общедоступна, переходим в редактор кода скрипта регистрации TestFeedbackNoFormReg - Публикация - Развернуть как веб-приложение, копируем ссылку из поля "Текущий URL веб-приложения" и сохраняем ее в каком-нибудь блокноте.
Адрес должен выглядеть приблизительно так: "https://script.google.com/macros/s/AKfycmPby5uqw6D9S0Xl5JN77pPbYBCYJtbChnYZLlgqLH7W6ibUKSY/exec". Что называется "почувствуйте разницу", между ссылкой для разработки и ссылкой на рабочее приложение.
Возвращаемся в редактор кода скрипта TestFeedbackNoForm, открываем файл Кнопки.gs, пишем функцию.
Законное возражение. Открываем лист "Пользователи" нашей таблицы, удаляем информацию о пользователе user1, возвращаемся на вкладку приложения, нажимаем на кнопку "Регистрация" еще раз.
Проверяем лист "Регистрация" таблицы TestFeedbackNoForm.
Проверяем почту.
Все в елочку. В третий раз пытаемся зарегистрировать пользователя user1.
Что и следовало ожидать. Возвращаемся в только что полученное почтовое сообщение, переходим по ссылке.
Открываем лист "Регистрация" нашей таблицы - данные исчезли. Открываем лист "Пользователи".
Проверяем почту.
В завершение напишем функцию, которая будет удалять истекшие запросы на регистрацию. Открываем редактор скриптов таблицы TestFeedbackNoForm: Инструменты - Редактор скриптов, создаем файл скрипта: Файл - Создать - Скрипт. Назовем его Триггер. Пишем код функции.
Начнем с бэкэнда - таблицы. Заходим на Google Диск, нажимаем большую красную кнопку: Создать - Таблица. Я назвал ее TestFeedbackNoForm. Единственный лист таблицы назовем "Сообщения". Нетрудно догадаться, что этот самый лист будет содержать информацию о сообщениях пользователей нашей системы. Добавим немного интерактива, для чего реализуем возможность отвечать на сообщения, плюс возможность пользователей получить ответ.
Как-то запутанно получилось. Короче. Пишем заголовки столбцов, добавляем тестовые данные.
По-моему становится понятнее о чем толкую.
С целью более наглядного представления о наших вопросах-ответах нарисуем интерфейс. Открываем редактор скриптов: Инструменты - Редактор скриптов. Назовем проект TestFeedbackNoForm.
Пишем функцию отображения информации о нашем вопросе, для чего переименуем созданную по-умолчанию функцию myFunction() в openRequest().
function openRequest() {
var app = UiApp.createApplication().setWidth('550').setHeight('260');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
// интерфейс
var vp = app.createVerticalPanel();
app.add(vp);
// номер вопроса
var lblRequestNumber = app.createLabel()
.setId('lblRequestNumber').setStyleAttribute('text-align', 'center').setStyleAttribute('font-weight', 'bold');
vp.add(lblRequestNumber);
// статус
var listStatus = app.createListBox().setName('listStatus').setId('listStatus').addItem('новый').addItem('в процессе').addItem('решен');
// свойства вопроса
var grid1 = app.createGrid(3, 4).setId('grid1')
.setText(0, 0, 'Дата поступления: ')
.setText(0, 2, 'Дата изменения: ')
.setText(1, 0, 'Пользователь: ')
.setText(1, 2, 'Статус: ').setWidget(1, 3, listStatus)
.setText(2, 0, 'E-mail: ')
.setText(2, 2, 'Телефон: ').setStyleAttribute(0, 1, 'width', '150px').setStyleAttribute(0, 3, 'width', '150px');
// содержание и ответ
var txtContent = app.createTextArea().setName('txtContent').setId('txtContent').setWidth(270).setHeight(100).setEnabled(false);
var txtResponse = app.createTextArea().setName('txtResponse').setId('txtResponse').setWidth(270).setHeight(100);
var grid2 = app.createGrid(2, 2).setId('grid2')
.setText(0, 0, 'Содержание: ').setWidget(1, 0, txtContent)
.setText(0, 1, 'Ответ: ').setWidget(1, 1, txtResponse).setWidth(540);
// кнопки
var btnOK = app.createButton('Сохранить').setWidth(100).setStyleAttribute('margin', '0px');
var btnCancel = app.createButton('Закрыть').setWidth(100).setStyleAttribute('margin', '0px');
var btnUp = app.createButton('Вверх ^').setWidth(100).setStyleAttribute('margin', '0px');
var btnDown = app.createButton('Вниз v').setWidth(100).setStyleAttribute('margin', '0px');
var grid3 = app.createGrid(1, 4)
.setWidget(0, 0, btnOK).setWidget(0, 1, btnUp).setWidget(0, 2, btnDown).setWidget(0, 3, btnCancel)
.setWidth(550).setStyleAttribute(0, 1, 'text-align', 'right').setStyleAttribute(0, 3, 'text-align', 'right');
// добавляем на вертикальную панель
vp.add(grid1).add(grid2).add(grid3);
ss.show(app);
}
Запускаем наш код.Добавим проверку на то, что открыт именно тот лист, который нужен (пока в таблице он один, но позже будут еще), и убедимся что выбран именно диапазон с данными:
// проверки if (sh.getName() != 'Сообщения') // проверяем лист return; var range = sh.getActiveRange(); var rangeRowIndex = range.getRowIndex(); if (rangeRowIndex > sh.getDataRange().getLastRow() || rangeRowIndex == 1) // проверяем диапазон return;Добавим этот код перед началом создания интерфейса (выше строки "// интерфейс"). Выберем ячейку вне диапазона с данными - в какой-нибудь пятой или шестой строке и затестим код... интерфейс не отобразился, что и требовалось.
Пришло время добавить в таблицу еще один лист, в котором будет храниться информация о зарегистрированных пользователях нашей системы.
Нажимаем на плюсик в левом нижнем углу таблицы, называем лист "Пользователи", пишем заголовки столбцов и добавляем одного тестового пользователя.
В поле E-mail пишем адрес ящика текущего аккаунта Google. Еще раз затестим наш код после активации листа "Пользователи"... то, что нужно.
Пишем функцию, которая будет возвращать нам объект - пользователя, со свойствами: имя, e-mail и телефон.
// получаем объект - пользователей со свойствами name, mail и phone
function getUsersObject(range) {
var rowObjects = [];
for (var i = 1; i < range.length; i++) {
var row = {name: range[i][1], mail: range[i][2], phone: range[i][3]};
rowObjects.push(row);
}
return rowObjects;
}
Осталось вытащить необходимые данные и запихнуть их в виджеты.function setWidgetsText(app, ss, sh, range) {
// получаем диапазон со свойствами заявки
var requestRange = sh.getRange(range.getRowIndex(), 1, 1, 7).getValues();
// переводим в объект
var request = {dateIn: requestRange[0][0], number: requestRange[0][1],
user: requestRange[0][2], content: requestRange[0][3], status: requestRange[0][4],
dateChanged: requestRange[0][5], response: requestRange[0][6],
mail: 'не найден', phone: 'не найден'};
// получаем диапазон со свойствами пользователей
var usersRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Пользователи')
.getDataRange().getValues();
// получаем объект пользователи - с листа "Пользователи"
var users = getUsersObject(usersRange);
// ищем пользователя
for (var i = 0; i < users.length; i++) {
if (users[i].name == request.user) {
request.mail = users[i].mail;
request.phone = users[i].phone;
break;
}
}
//интерфейс
app.getElementById('lblRequestNumber').setText('Вопрос №: ' + request.number);
switch (request.status) {
case 'в процессе':
app.getElementById('listStatus').setSelectedIndex(1);
break;
case 'решен':
app.getElementById('listStatus').setSelectedIndex(2);
break;
case 'новый':
default:
app.getElementById('listStatus').setSelectedIndex(0);
break;
}
app.getElementById('grid1').setText(0, 1, Utilities.formatDate(new Date(request.dateIn), "GMT+4", "yyyy-MM-dd' 'HH:mm:ss"))
.setText(0, 3, Utilities.formatDate(new Date(request.dateChanged), "GMT+4", "yyyy-MM-dd' 'HH:mm:ss"))
.setText(1, 1, request.user).setWidget(2, 1, app.createAnchor(request.mail, 'mailto:' + request.mail))
.setText(2, 3, request.phone);
app.getElementById('txtContent').setText(request.content);
app.getElementById('txtResponse').setText(request.response);
}
Добавим вызов функции setWidgetsText() в функцию openRequest() перед строкой "ss.show(app);". Открываем лист "Сообщения", выбираем, например, Вопрос 2, возвращаемся в Редактор скриптов и запускаем функцию openRequest().Пишем функцию создания меню.
// закидываем меню
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menu = [ {name: "Открыть", functionName: "openRequest"} ];
ss.addMenu("Вопрос", menu);
}
Запускаем ее, открываем лист "Сообщения" и запускаем наш код уже с помощью меню.Для реализации функционала кнопок создадим новый файл скрипта: Редактор скриптов - Файл - Создать - Скрипт. Назовем его Кнопки.gs.
Начнем с функции закрытия интерфейса.
function appClose(e) {
var app = UiApp.getActiveApplication();
app.close();
return app;
}
Переходим к функциям навигации.function onUpClick(e) {
var app = UiApp.getActiveApplication();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
// проверки
if (sh.getName() != 'Сообщения') // проверяем лист
return;
if (sh.getActiveRange().getRowIndex() == 2) {
ss.toast('Выше некуда :)');
return;
}
var range = sh.getActiveRange().offset(-1, 0).activate();
setWidgetsText(app, ss, sh, range);
return app;
}
function onDownClick(e) {
var app = UiApp.getActiveApplication();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
// проверки
if (sh.getName() != 'Сообщения') // проверяем лист
return;
if (sh.getActiveRange().getRowIndex() == sh.getDataRange().getLastRow()) {
ss.toast('Ниже некуда :)');
return;
}
var range = sh.getActiveRange().offset(1, 0).activate();
setWidgetsText(app, ss, sh, range);
return app;
}
И, на десерт, пишем функцию сохранения изменений.function onBtnOKClick(e) {
var app = UiApp.getActiveApplication();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
if (sh.getName() != 'Сообщения') // проверяем лист
return;
try {
var range = sh.getActiveRange();
var rangeRowIndex = range.getRowIndex();
sh.getRange(rangeRowIndex, 4, 1, 4).setValues([[e.parameter.txtContent, e.parameter.listStatus,new Date(),e.parameter.txtResponse]]);
var requestRange = sh.getRange(rangeRowIndex, 1, 1, 7);
switch (e.parameter.listStatus) {
case 'в процессе':
requestRange.setBackgroundColor('SkyBlue');
break;
case 'решен':
requestRange.setBackgroundColor('LimeGreen');
break;
case 'новый':
default:
requestRange.setBackgroundColor('Tomato');
break;
}
ss.toast('Изменения успешно сохранена')
} catch(e) {
Logger.log(new Date() + " " + e);
ss.toast('В процессе сохранения изменений произошла ошибка');
}
return app;
}
Сохраняем изменения в файле Кнопки.gs, возвращаемся в созданный по-умолчанию файл Код.gs и в функцию openRequest() после кода создания кнопок добавляем код обработчиков событий для них.// обработчики событий для кнопок
var handlerCancel = app.createServerHandler('appClose');
btnCancel.addClickHandler(handlerCancel);
var handlerOK = app.createServerHandler('onBtnOKClick').addCallbackElement(vp);
btnOK.addClickHandler(handlerOK);
var handlerUp = app.createServerHandler('onUpClick').addCallbackElement(vp);
btnUp.addClickHandler(handlerUp);
var handlerDown = app.createServerHandler('onDownClick').addCallbackElement(vp);
btnDown.addClickHandler(handlerDown);
Сохраняем изменения в файле Код.gs. Возвращаемся в таблицу, тестим навигацию.Такой вот бэкэнд получился.
Переходим к фронтэнду. Создаем файл скрипта: Google Диск - Создать - Еще - Скрипт. Я назвал его TestFeedbackNoForm.
Переименуем myFunction() в doGet() и нарисуем интерфейс.
function doGet() {
var app = UiApp.createApplication().setTitle('Feedback');
var vpMain = app.createVerticalPanel().setWidth('550').setHeight('260');
app.add(vpMain);
// рисуем интерфейс
var lblName = app.createLabel('Пользователь:');
var lblMail = app.createLabel('E-mail:');
var lblContent = app.createLabel('Содержание:');
var txtName = app.createTextBox().setName('txtName').setId('txtName').setWidth(190).setStyleAttribute('border', '1px solid grey');
var txtMail = app.createTextBox().setName('txtMail').setId('txtMail').setWidth(190).setStyleAttribute('border', '1px solid grey');
var txtContent = app.createTextArea().setName('txtContent').setId('txtContent').setWidth(325).setHeight(128).setStyleAttribute('border', '1px solid grey');
var btnSend = app.createButton('Отправить').setWidth('100').setStyleAttribute('margin', '0');
var btnCheck = app.createButton('Проверить').setWidth('100').setStyleAttribute('margin', '0');
var txtQueryNumber = app.createTextBox().setName('txtQueryNumber').setId('txtQueryNumber').setStyleAttribute('border', '1px solid grey');
var lblQueryNumber = app.createLabel('Вопрос №:');
var btnRegister = app.createButton('Регистрация').setWidth(100).setStyleAttribute('margin', '0');
var lblPhoneNumber = app.createLabel('Телефон: ');
var txtPhoneNumber = app.createTextBox().setName('txtPhoneNumber').setId('txtPhoneNumber').setStyleAttribute('border', '1px solid grey');
// имя пользователя и пароль
var vp1 = app.createVerticalPanel().setStyleAttributes({border: 'solid 1px grey', margin: '5', padding: '5'});
vpMain.add(vp1);
var grid1 = app.createGrid(1, 4).setWidget(0, 0, lblName).setWidget(0, 1, txtName).setWidget(0, 2, lblMail)
.setWidget(0, 3, txtMail).setWidth(530);
vp1.add(grid1);
var hp1 = app.createHorizontalPanel();
vpMain.add(hp1);
// отправить
var vp3 = app.createVerticalPanel()
.setStyleAttributes({border: 'solid 1px grey', margin: '5', padding: '5'}).setWidth(350);
hp1.add(vp3);
var grid3 = app.createGrid(3, 1).setWidget(0, 0, lblContent).setWidget(1, 0, txtContent).setWidget(2, 0, btnSend);
vp3.add(grid3);
var vp2 = app.createVerticalPanel();
hp1.add(vp2);
// регистрация
var vp4 = app.createVerticalPanel().setStyleAttributes({border: 'solid 1px grey', margin: '5', padding: '5'}).setWidth(182);
vp2.add(vp4);
var grid4 = app.createGrid(3, 1).setWidget(0, 0, btnRegister).setWidget(1, 0, lblPhoneNumber).setWidget(2, 0, txtPhoneNumber)
.setStyleAttribute(0, 0, 'text-align', 'center');
vp4.add(grid4)
// проверить
var vp3 = app.createVerticalPanel().setStyleAttributes({border: 'solid 1px grey', margin: '5', padding: '5'}).setWidth(182);
vp2.add(vp3);
var grid2 = app.createGrid(3, 1).setWidget(0, 0, lblQueryNumber).setWidget(1, 0, txtQueryNumber).setWidget(2, 0, btnCheck)
.setStyleAttribute(2, 0, 'text-align', 'center');
vp3.add(grid2);
return app;
}
Сохраняем изменения в файле Код.gs, сохраняем версию файла: Файл - Версии - Сохранить новую - ОК.Публикуем приложение: Публикация - Развернуть как веб-приложение - Кто имеет доступ к приложению: Все, включая анонимных пользователей - Развернуть.
Тестируем приложение: В окне "Развертывание как веб-приложения" нажимаем на ссылку "последнюю версию кода".
В функцию doGet() перед строкой "return app;" добавим код валидации на клиенте:
// валидация на клиенте
var maxNameLength = 30; // максимум символов имени
var validateNotName = app.createClientHandler().validateNotLength(txtName, 1, maxNameLength)
.forTargets(txtName).setStyleAttribute('border-color', 'red');
var validateName = app.createClientHandler().validateLength(txtName, 1, maxNameLength)
.forTargets(txtName).setStyleAttribute('border-color', 'grey');
// валидация E-mail
var validateNotMail = app.createClientHandler().validateNotEmail(txtMail)
.forTargets(txtMail).setStyleAttribute('border-color', 'red');
var validateMail = app.createClientHandler().validateEmail(txtMail)
.forTargets(txtMail).setStyleAttribute('border-color', 'grey');
var maxNumberLength = 10; // максимум символов номера вопроса
var validateNotNumber = app.createClientHandler().validateNotLength(txtQueryNumber, 1, maxNameLength)
.forTargets(txtQueryNumber).setStyleAttribute('border-color', 'red');
var validateNumber = app.createClientHandler().validateLength(txtQueryNumber, 1, maxNameLength)
.forTargets(txtQueryNumber).setStyleAttribute('border-color', 'grey');
var maxContentLength = 500; // максимум символов содержания вопроса
var validateNotContent = app.createClientHandler().validateNotLength(txtContent, 1, maxContentLength)
.forTargets(txtContent).setStyleAttribute('border-color', 'red');
var validateContent = app.createClientHandler().validateLength(txtContent, 1, maxContentLength)
.forTargets(txtContent).setStyleAttribute('border-color', 'grey');
btnCheck.addClickHandler(validateName).addClickHandler(validateMail).addClickHandler(validateNumber)
.addClickHandler(validateNotName).addClickHandler(validateNotMail).addClickHandler(validateNotNumber);
btnSend.addClickHandler(validateName).addClickHandler(validateMail).addClickHandler(validateContent)
.addClickHandler(validateNotName).addClickHandler(validateNotMail).addClickHandler(validateNotContent);
var minPhoneLength = 5; // минимальное количество символов номера телефона
var maxPhoneLength = 20; // максимальное количество символов номера телефона
var validateNotPhone = app.createClientHandler().validateNotLength(txtPhoneNumber, minPhoneLength, maxPhoneLength)
.forTargets(txtPhoneNumber).setStyleAttribute('border-color', 'red');
var validatePhone = app.createClientHandler().validateLength(txtPhoneNumber, minPhoneLength, maxPhoneLength)
.forTargets(txtPhoneNumber).setStyleAttribute('border-color', 'grey');
btnRegister.addClickHandler(validateName).addClickHandler(validateMail).addClickHandler(validatePhone)
.addClickHandler(validateNotName).addClickHandler(validateNotMail).addClickHandler(validateNotPhone);
Сохраняем изменения, обновляем вкладку браузера с интерфейсом приложения и тестим наши кнопки.
Еще немного подредактируем код функции doGet(). После валидации добавим код обработчиков событий нажатия на кнопки.
// на сервере
var handlerSend = app.createServerHandler('checkNAddRequests')
.validateLength(txtName, 1, maxNameLength).validateEmail(txtMail).validateLength(txtContent, 1, maxContentLength).addCallbackElement(vpMain);
btnSend.addClickHandler(handlerSend);
var handlerCheck = app.createServerHandler('checkNAddRequests')
.validateLength(txtName, 1, maxNameLength).validateEmail(txtMail).validateLength(txtQueryNumber, 1, maxNameLength).addCallbackElement(vpMain);
btnCheck.addClickHandler(handlerCheck);
var handlerRegister = app.createServerHandler('registerUser')
.validateLength(txtName, 1, maxNameLength).validateEmail(txtMail).validateLength(txtPhoneNumber, minPhoneLength, maxPhoneLength).addCallbackElement(vpMain);
btnRegister.addClickHandler(handlerRegister);
Теперь необходимо написать сами функции: добавления либо проверки состояния вопроса - checkNAddRequests(), и регистрации пользователя - registerUser().Функции будут обращаться к таблице по ID (в адресной строке браузера - между "key=" и "#"), поэтому для хранения этого самого ID предлагаю использовать глобальную переменную. Можно использовать свойства скрипта и вытаскивать значение с помощью метода ScriptProperties.getProperty(), но глобальная переменная по-моему лучше, так как у свойств скрипта есть ограничения по количеству обращений (не знаю с чем это связано - вопросы к Google).
Итак, создаем новый файл скрипта: Файл - Создать - Скрипт. Назовем его "Переменные". Удаляем созданную по-умолчанию функцию myFunction() и пишем нашу переменную.
var tableID = '0AkYcK5KeNe1tdHVSV00UWRCSUR3TE02Yk5mUUEUzTk9'; // как-то такСоздадим еще один файл скрипта для хранения функций работы с пользователями, назовем его "Пользователи". Пишем нехитрые функции.
// получаем всех пользователей со свойствами name и mail
function getUsersObject(range) {
var rowObjects = [];
for (var i = 1; i < range.length; i++) {
var row = {name: range[i][1], mail: range[i][2]};
rowObjects.push(row);
}
return rowObjects;
}
// проверяем валидность пользователя
function checkUserIsValid(user, users) {
for (var i = 0; i < users.length; i++) {
if (users[i].name == user.name && users[i].mail == user.mail)
return true;
}
return false;
}
// проверяем существование имени пользователя или E-mail
function checkUserExists(user, users) {
for (var i = 0; i < users.length; i++) {
if (users[i].name == user.name || users[i].mail == user.mail)
return true;
}
return false;
}
После нажатия на кнопки наше приложение будет отображать информационные сообщения.Создадим еще один файл скрипта, назовем его "Сообщения" и разместим в нем код функций, отображающих сообщения.
// создаем информационную панель для отображения информации об ошибках
function createInfoGrid(app) {
var btnOK = app.createButton('OK').setId('btnOK').setWidth('100').setStyleAttribute('border-radius','10px 10px 10px 10px')
.setStyleAttribute('font-weight', 'bold');
var infoGrid = app.createGrid(3,1).setId('infoGrid').setWidget(2, 0, btnOK)
.setStyleAttributes({top:'60', left:'130', position:'fixed', opacity: '0.85'})
.setStyleAttribute('border-radius','10px 10px 10px 10px').setWidth('300')
.setStyleAttribute('text-align', 'center').setStyleAttribute('font-weight', 'bold');
app.add(infoGrid);
var handlerOK = app.createServerHandler('hideInfoGrid').addCallbackElement(infoGrid);
btnOK.addClickHandler(handlerOK);
}
// прячем информационное окно
function hideInfoGrid(e) {
var app = UiApp.getActiveApplication();
app.getElementById('infoGrid').setVisible(false);
return app;
}
function msgNotValidUser(app) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid red', background:'Moccasin'});
app.getElementById('infoGrid').setText(0, 0, 'Пожалуйста, введите Ваше имя пользователя и Ваш E-mail.')
.setText(1, 0, 'Для работы с системой, пожалуйста, зарегистрируйтесь.')
.setStyleAttributes({border:'2px solid red', background:'Moccasin'});
}
function msgNotValidRequest(app, requestNumber) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid red', background:'Moccasin'});
app.getElementById('infoGrid').setText(0, 0, 'Информация о вопросе № ' + requestNumber + ' не найдена.')
.setText(1, 0, 'Если Вы уверены в том, что отправляли и регистрировали вопрос под номером ' + requestNumber +
', пожалуйста, обратитесь к администратору.')
.setStyleAttributes({border:'2px solid red', background:'Moccasin'});
}
function msgError(app, msg1, msg2) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid red', background:'Moccasin'});
app.getElementById('infoGrid').setText(0, 0, 'В процессе ' + msg1 +' произошла ошибка.')
.setText(1, 0, 'Пожалуйста, попробуйте ' + msg2 + ' позднее.')
.setStyleAttributes({border:'2px solid red', background:'Moccasin'});
}
function msgGotRegister(app) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid blue', background:'PaleTurquoise'});
app.getElementById('infoGrid').setText(0, 0, 'Благодарим Вас за регистрацию.')
.setText(1, 0, 'На Ваш e-mail отправлено сообщение, содержащее информацию, необходимую для завершения регистрации.')
.setStyleAttributes({border:'2px solid blue', background:'PaleTurquoise'});
}
function msgGotRequest(app, intRequestNumber) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid blue', background:'PaleTurquoise'});
app.getElementById('infoGrid').setText(0, 0, 'Ваш вопрос получен и зарегистрирован под номером ' + intRequestNumber + '.')
.setText(1, 0, 'На Ваш e-mail отправлено сообщение, содержащее номер вопроса.')
.setStyleAttributes({border:'2px solid blue', background:'PaleTurquoise'});
}
function msgValidUser(app, user) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid red', background:'Moccasin'});
app.getElementById('infoGrid').setText(0, 0, 'Пожалуйста, введите Ваше имя пользователя и Ваш E-mail.')
.setText(1, 0, 'Пользователь с именем ' + user.name + ' или адресом E-mail ' + user.mail + ' уже зарегистрирован.')
.setStyleAttributes({border:'2px solid red', background:'Moccasin'});
}
function msgValidUser2(app, user) {
createInfoGrid(app);
app.getElementById('btnOK').setStyleAttributes({border:'2px solid red', background:'Moccasin'});
app.getElementById('infoGrid').setText(0, 0, 'Пожалуйста, проверьте Ваш E-mail.')
.setText(1, 0, 'Пользователь с именем ' + user.name + ' или адресом E-mail ' + user.mail + ' ожидает подтверждения регистрации.')
.setStyleAttributes({border:'2px solid red', background:'Moccasin'});
}
// создаем информационную панель для отображения информации о найденном вопросе
function createInfoGrid2(app, request) {
var btnOK = app.createButton('OK').setId('btnOK').setWidth('100')
.setStyleAttributes({border:'2px solid blue', background:'PaleTurquoise', margin:'0 0 0 10'})
.setStyleAttribute('border-radius','10px 10px 10px 10px').setStyleAttribute('font-weight', 'bold');
var txtContent_ = app.createTextArea().setWidth('250').setHeight('65').setText(request.content)
.setStyleAttribute('background', 'PaleTurquoise').setEnabled(false);
var txtResponse_ = app.createTextArea().setWidth('250').setHeight('65').setText(request.response)
.setStyleAttribute('background', 'PaleTurquoise').setEnabled(false);
var infoGrid = app.createGrid(7,2).setId('infoGrid').setText(0, 0, 'Вопрос №:').setText(1, 0, 'Дата поступления:')
.setText(2, 0, 'Статус:').setText(3, 0, 'Дата изменения:').setText(4, 0, 'Содержание:')
.setText(5, 0, 'Ответ:').setText(0, 1, request.number)
.setText(1, 1, Utilities.formatDate(new Date(request.dateIn), "GMT+4", "yyyy-MM-dd' 'HH:mm:ss")).setText(2, 1, request.status)
.setText(3, 1, Utilities.formatDate(new Date(request.dateChanged), "GMT+4", "yyyy-MM-dd' 'HH:mm:ss"))
.setWidget(4, 1, txtContent_).setWidget(5, 1, txtResponse_)
.setWidget(6, 1, btnOK)
.setStyleAttribute(4, 0, 'display', 'inline-block').setStyleAttribute(4, 0, 'vertical-align', 'top')
.setStyleAttribute(5, 0, 'display', 'inline-block').setStyleAttribute(5, 0, 'vertical-align', 'top')
.setStyleAttributes({top:'40', left:'80', position:'fixed', opacity: '0.85', border:'2px solid blue', background:'PaleTurquoise'})
.setStyleAttribute('border-radius','10px 10px 10px 10px').setWidth('400').setStyleAttribute('font-weight', 'bold');
app.add(infoGrid);
var handlerOK = app.createServerHandler('hideInfoGrid').addCallbackElement(infoGrid);
btnOK.addClickHandler(handlerOK);
}
Для работы с нашими вопросами-ответами нам понадобится еще пара функций. Создадим файл скрипта, назовем его "Вопросы". Пишем функции.// получаем обект - вопросы со свойствами - заголовками столбцов
function getRequestsObject(range) {
var rowObjects = []; //объект
for (var i = 1; i < range.length; i++) {
var row = {dateIn: range[i][0], number: range[i][1],
user: range[i][2], content: range[i][3], status: range[i][4],
dateChanged: range[i][5], response: range[i][6]};
rowObjects.push(row);
}
return rowObjects;
}
// ищем вопрос пользователя
function findRequest(requestNumber, requests, userName) {
for (var i = 0; i < requests.length; i++) {
if (requests[i].number == requestNumber && requests[i].user == userName)
return requests[i];
}
}
Создадим еще один файл скрипта, назовем его "Кнопки". Добавим в него функцию очистки текстовых полей.// очищаем текстовые поля
function clearTxt(app) {
app.getElementById('txtQueryNumber').setValue('');
app.getElementById('txtContent').setValue('');
app.getElementById('txtPhoneNumber').setValue('');
}
Теперь можно приступать к обработчикам событий нажатия на кнопки. Начнем с добавления либо проверки состояния вопроса. Пишем в тот же файл.function checkNAddRequests(e) {
var app = UiApp.getActiveApplication();
//получаем книгу
var ss = SpreadsheetApp.openById(tableID);
// получаем лист по имени
var shUsers = ss.getSheetByName('Пользователи');
// получаем диапазон с данными
var range = shUsers.getDataRange().getValues();
// получаем пользователей из таблицы
var users = getUsersObject(range);
// получаем пользователя из текстовых полей
var user = {name: e.parameter.txtName, mail: e.parameter.txtMail};
// проверяем пользователя на валидность
if (!checkUserIsValid(user, users)){
msgNotValidUser(app); //отображаем сообщение
return app;
}
//получаем лист с сообщениями
var shRequests = ss.getSheetByName('Сообщения');
// проверяем источник события нажатия на кнопку
if (e.parameter.source == 'btnCheck')
checkRequest(e, shRequests, user, app); // отправляем в функцию проверки
else
addRecord(e, shRequests, user, app); // отправляем в функцию отправки
// чистим текстовые поля
clearTxt(app);
return app;
}
В процессе выполнения наша функция проверяет пользователя на валидность и, в случае положительного ответа, вызывает либо функцию проверки состояния вопроса, либо функцию отправки нового вопроса, в зависимости от e.parameter.source - источника события нажатия на кнопку.Добавляем функцию проверки.
// проверяем состояние вопроса
function checkRequest(e, shRequests, user, app) {
try {
//var x = y;
// получаем диапазон с данными
var range = shRequests.getDataRange().getValues();
// получаем вопросы из таблицы
var requests = getRequestsObject(range);
// ищем вопрос
var request = findRequest(e.parameter.txtQueryNumber, requests, user.name);
if (!request) {
msgNotValidRequest(app, e.parameter.txtQueryNumber);
return;
}
// отображаем информацию
createInfoGrid2(app, request);
} catch(e) {
Logger.log(new Date() + " " + e);
msgError(app, 'проверки вопроса', 'проверить Ваш вопрос');
}
}
Сохраняем файл, перезагружаем приложение, проверяем Вопрос №2.Теперь проверим несуществующий Вопрос №4.
А теперь попробуем ввести несуществующего пользователя.
Наконец, предлагаю раскомментировать строку //var x = y; и проверить как отработает наш код в случае ошибки.
Продолжаем разговор. Закомментируем проверочную строку обратно. Пишем функцию отправки нового вопроса.
function addRecord(e, shRequests, user, app) {
try {
var intRow = shRequests.getLastRow();
// добавляем строку
shRequests.getRange(intRow + 1, 1, 1, 7).setValues([[new Date(), intRow, user.name, e.parameter.txtContent, 'новый', new Date(), '']])
.setBackgroundColor('Tomato');
// отправляем e-mail
var txt = 'Ваш вопрос получен и зарегистрирован под номером ' + intRow +
'.\n\nСпасибо за обращение. ' + 'Пожалуйста, не отвечайте на это сообщение.'
MailApp.sendEmail(user.mail, 'Вопрос на сайте www.mysupersite.ru', txt,{name: 'noreply@mysupersite.ru', noReply: true});
msgGotRequest(app, intRow);
} catch(e) {
Logger.log(new Date() + " " + e);
msgError(app, 'отправки вопроса', 'отправить Ваш вопрос');
}
}
Перед тестированием функции отправки вопроса не мешало бы открыть лист "Пользователи" таблицы TestFeedbackNoForm и изменить e-mail единственного пользователя на e-mail текущего аккаунта Google, так как функция кроме всего прочего отправяет уведомление о регистрации вопроса на e-mail пользователя.После этого обновляем приложение... и получаем ошибку: "Для выполнения этого действия необходима авторизация.". Это случилось из-за того, что в коде функции addRecord() мы упомянули службу MailApp, а также пытаемся добавить значения в таблицу: shRequests.getRange(intRow + 1, 1, 1, 7).setValues(бла-бла-бла). Можете попробовать закомментировать строку MailApp.sendEmail(бла-бла-бла); и/или shRequests.getRange(intRow + 1, 1, 1, 7).setValues(бла-бла-бла), ничего не изменится - ошибка.
Короче надо авторизовать приложение для работы со службами SpreadsheetApp и MailApp.
Для этого можно выбрать функцию addRecord в списке выбора функций и, например, нажать паучка - "Отладка", или стрелочку - "Выполнить".
Нажимаем на кнопочку "Авторизовать".
Обновляем приложение, отправляем сообщение.
Таким образом мы получили еще один вопрос пользователя в нашу таблицу.
Кроме того мы отправили уведомление на e-mail пользователя о получении и регистрации его вопроса.
Подведем промежуточный итог. На текущем этапе наша система обратной связи вполне работоспособна, за исключением того, что пользователей пока необходимо забивать вручную.
Решаем вопрос. Добавляем в таблицу TestFeedbackNoForm лист, назовем его "Регистрация". Пишем заголовки столбцов.
Регистрацию пользователей реализуем следующим образом: сохраняем данные о пользователе в только что созданном листе "Регистрация", в поле key пишем случайную последовательность символов - ключ регистрации, отправляем на e-mail пользователя ссылку на скрипт подтверждения регистрации.
Займемся этим самым скриптом. Идем на Google Диск - Создать - Еще - Скрипт, назовем его TestFeedbackNoFormReg.
Переименуем созданную по-умолчанию функцию в doGet(e), пишем код функции.
// скрипт регистрации пользователей
function doGet(e) {
// если нет ключа - выходим
if (!e.parameter.key)
return;
var app = UiApp.createApplication().setTitle('Регистрация').setStyleAttribute('margin', '10px');
var ss = SpreadsheetApp.openById('0AkYcK5KeNe1tdHVSV00UWRCSUR3TE02Yk5mUUEUzTk9');
var shReg = ss.getSheetByName('Регистрация');
var range = shReg.getDataRange().getValues();
var user = getUserObject(range, e.parameter.key);
// если нет такого пользователя - выходим
if (!user)
return app;
// добавляем пользователя
var shUsers = ss.getSheetByName('Пользователи');
shUsers.getRange(shUsers.getLastRow() + 1, 1, 1, 5).setValues([[new Date(), user.name, user.mail, user.phone, user.key]]);
// удаляем запись
shReg.deleteRow(user.rowIndex);
SpreadsheetApp.flush(); // сохраняем изменения
// отправляем e-mail
var txt = 'Благодарим Вас за регистрацию.' +
'\n\nТеперь Вы можете отправить вопрос, а также контролировать процесс его решения, используя Ваши учетные данные:\n\n' +
'Пользователь: ' + user.name + '\n' + 'E-mail: ' + user.mail + '\n\n' +
'Пожалуйста, не отвечайте на это сообщение.\n';
MailApp.sendEmail(user.mail, 'Завершение регистрации на сайте www.mysupersite.ru', txt,{name: 'noreply@www.mysupersite.ru', noReply: true});
var hp = app.createHorizontalPanel();
hp.add(app.createLabel('Благодарим Вас за регистрацию.').setStyleAttribute('margin', '1px 5px 0px 0px'));
app.add(hp).add(app.createLabel('На Ваш e-mail отправлено сообщение, содержащее Ваши регистрационные данные.'));
return app;
}
Добавляем в скрипт функцию извлечения пользователя.// получаем пользователя
function getUserObject(range, key) {
for (var i = 1; i < range.length; i++) {
if (range[i][4] == key) {
var obj = {name: range[i][1], mail: range[i][2], phone: range[i][3], key: range[i][4], rowIndex: i+1};
return obj;
}
}
}
Сохраняем изменения в файле Код.gs, сохраняем версию файла: Файл - Версии - Сохранить новую - ОК.Публикуем приложение: Публикация - Развернуть как веб-приложение - Кто имеет доступ к приложению: Все, включая анонимных пользователей - Развернуть.
Тестируем... снова необходима авторизация. В окне редактора скриптов выбираем функцию doGet() и запускаем ее.
Нажимаем "Авторизовать". Обновляем веб-приложение в окне браузера...
"Скрипт был выполнен, но ничего не возвратил.". Что и требовалось, так как мы не прописали в адресной строке ключ.
Открываем лист "Пользователи" таблицы TestFeedbackNoForm, вырезаем данные о нашем единственном пользователе user1 и вставляем их на лист "Регистрация". Добавим ключ 123.
Переходим на вкладку со скриптом регистрации, в адресной строке в самом конце адреса после "/dev" добавляем параметр скрипта - "?key=123".
Адрес должен выглядеть приблизительно так: "https://script.google.com/macros/s/AKfycbykejcoOVMcJJyEyf6feUjLndcA172Lv86AFKk-_2Y/dev?key=123"
Обновляем страницу.
Возвращаемся в таблицу TestFeedbackNoForm. Наш пользователь исчез с листа "Регистрация" и вернулся на лист "Пользователи". В поле "Ключ" у него появилось значение - "123".
Проверяем почту.
Примерно такое же сообщение будут получать наши пользователи после завершения регистрации.
Остается добавить в скрипт TestFeedbackNoForm функцию обработчик события нажатия на кнопку "Регистрация", которая будет формировать ссылку на скрипт регистрации со случайным ключем, отправлять эту ссылку пользователю и одновременно добавлять данные о регистрации на лист "Регистрация" таблицы TestFeedbackNoForm.
Для того, чтобы сформировать правильную ссылку, которая будет общедоступна, переходим в редактор кода скрипта регистрации TestFeedbackNoFormReg - Публикация - Развернуть как веб-приложение, копируем ссылку из поля "Текущий URL веб-приложения" и сохраняем ее в каком-нибудь блокноте.
Адрес должен выглядеть приблизительно так: "https://script.google.com/macros/s/AKfycmPby5uqw6D9S0Xl5JN77pPbYBCYJtbChnYZLlgqLH7W6ibUKSY/exec". Что называется "почувствуйте разницу", между ссылкой для разработки и ссылкой на рабочее приложение.
Возвращаемся в редактор кода скрипта TestFeedbackNoForm, открываем файл Кнопки.gs, пишем функцию.
function registerUser(e) {
var app = UiApp.getActiveApplication();
try {
//получаем книгу
var ss = SpreadsheetApp.openById(tableID);
// получаем лист по имени
var ssUsers = ss.getSheetByName('Пользователи');
// получаем диапазон с данными
var range = ssUsers.getDataRange().getValues();
// получаем пользователей из таблицы
var users = getUsersObject(range);
// получаем пользователя из текстовых полей
var user = {name: e.parameter.txtName, mail: e.parameter.txtMail};
// проверяем существование имени пользователя или E-mai в списке зарегистрированных пользователей
// на листе "Пользователи", если такой уже есть - выходим
if (checkUserExists(user, users)){
msgValidUser(app, user); //отображаем сообщение
return app;
}
var shReg = ss.getSheetByName('Регистрация');
range = shReg.getDataRange().getValues();
var usersReg = getUsersObject(range);
// проверяем существование имени пользователя или E-mai в списке пользователей, ожидающих подтверждения регистрации
// на листе "Регистрация", если такой уже есть - выходим
if (checkUserExists(user, usersReg)){
msgValidUser2(app, user); //отображаем сообщение
return app;
}
// получаем случайную строку
var randomString = getRandomString();
// пишем данные пользователя, ожидающего подтверждение регистрации на лист "Регистрация"
shReg.getRange(shReg.getLastRow() + 1, 1, 1, 5)
.setValues([[new Date(), e.parameter.txtName, e.parameter.txtMail, e.parameter.txtPhoneNumber, randomString]])
// формируем строку
var txt = 'Благодарим Вас за регистрацию.' +
'\n\nДля завершения процесса регистрации, пожалуйста, перейдите по ссылке:\n' +
'https://script.google.com/macros/s/AKfycmPby5uqw6D9S0Xl5JN77pPbYBCYJtbChnYZLlgqLH7W6ibUKSY/exec?key=' + randomString +
'\n\nЕсли переход не работает, пожалуйста, скопируйте приведённую выше ссылку в адресную строку браузера.\n\n' +
'Ссылка будет действительна в течение часа.\n\nПожалуйста, не отвечайте на это сообщение.'
// отправляем e-mail
MailApp.sendEmail(user.mail, 'Подтверждение регистрации на сайте www.mysupersite.ru', txt,{name: 'noreply@www.mysupersite.ru', noReply: true});
// чистим текстовые поля
clearTxt(app);
// отображаем сообщение
msgGotRegister(app);
} catch(e) {
Logger.log(new Date() + " " + e);
msgError(app, 'регистрации', 'зарегистрироваться');
}
return app;
}
Не забываем добавить функцию формирования случайной строки. На мой взгляд 33-х знаков вполне достаточно.function getRandomString() {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz1234567890_-";
var sLength = 33;
var rString = '';
for (var i=0; i<sLength; i++) {
var rnum = Math.floor(Math.random() * chars.length);
rString += chars.substring(rnum,rnum+1);
}
return rString;
}
Обновляем веб-приложение TestFeedbackNoForm, пытаемся зарегистрировать пользователя user1.Законное возражение. Открываем лист "Пользователи" нашей таблицы, удаляем информацию о пользователе user1, возвращаемся на вкладку приложения, нажимаем на кнопку "Регистрация" еще раз.
Проверяем лист "Регистрация" таблицы TestFeedbackNoForm.
Проверяем почту.
Все в елочку. В третий раз пытаемся зарегистрировать пользователя user1.
Что и следовало ожидать. Возвращаемся в только что полученное почтовое сообщение, переходим по ссылке.
Открываем лист "Регистрация" нашей таблицы - данные исчезли. Открываем лист "Пользователи".
Проверяем почту.
В завершение напишем функцию, которая будет удалять истекшие запросы на регистрацию. Открываем редактор скриптов таблицы TestFeedbackNoForm: Инструменты - Редактор скриптов, создаем файл скрипта: Файл - Создать - Скрипт. Назовем его Триггер. Пишем код функции.
// удаляем истекшие запросы на регистрацию
function clearInvalid() {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Регистрация');
var lastRowIndex = sh.getDataRange().getLastRow() + 1;
var currentDate = new Date();
for (i=1; i<lastRowIndex; i++) {
if (parseInt((currentDate.getTime() - new Date(sh.getRange(i, 1).getValue()).getTime())/60000) > 60) {
sh.deleteRows(i);
SpreadsheetApp.flush(); // сохраняем изменения
i--;
}
}
}
Как вы наверняка догадались, затем необходимо добавить триггер: Ресурсы - Триггеры текущего проекта - Добавить триггер - Выбираем из списка функцию clearInvalid() - Динамический - Часовой таймер - Каждый час - Сохранить.Таким образом запросы на регистрацию, существующие дольше часа будут удаляться.
Очевидно, что утверждая в e-mail сообщении о том, что ссылка будет действительна в течение часа, мы немного лукавим, так как, принимая во внимание свойства созданного триггера, иногда ссылка может оказаться валидной и почти через два часа. В случае необходимости очищать лист "Регистрация" чаще, вопрос решается понятным образом.
Feedback готов. Осилившим дорогу респект. За сим позвольте откланяться.




































Вначале -
ОтветитьУдалитьДля реализации функционала кнопок создадим новый файл скрипта: Редактор скриптов - Файл - Создать - Скрипт. Назовем его Кнопки.gs.
Несколько ниже -
Создадим еще один файл скрипта, назовем его "Кнопки".
Наверное это ошибка?
Вначале: создавали файл скрипта в Spreadsheet Intergrated скрипте - в таблице.
УдалитьНесколько ниже: в Standalone скрипте - совсем другой скрипт. Ошибки нет.
Здравствуйте Анатолий. Не могу определится с
ОтветитьУдалить// получаем объект - пользователей со свойствами name, mail и phone
function getUsersObject(range) куда вставить-постоянный конфликт.
Заранее спасибо.
функция getUsersObject может находиться в любом файле скрипта таблицы (TestFeedbackNoForm), в рассмотренном примере находится в файле Код.gs под функцией openRequest
Удалить