Способ создания формы обратной связи с использованием инструмента "формы", рассмотренный в предыдущей статье, не лишен недостатков. В этот раз я расскажу, как написать более "продвинутый" 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
Удалить