День 4
Сегодня я работаю над html и javascript. Очень мало Go. Я добавляю ссылку для переключения режима редактирования и параметр запроса для установки режима редактирования.
У меня будут элементы с классом «editelement» и переключатель для установки класса «edithidden».
HTML:
<a id="toggleeditor" title="toggle edit mode">
<i class="fas fa-user-edit" tooltip="toggle edit mode"></i>Toggle Edit Mode
</a>
<div class="editelement"><p>I show up in edit mode</p></div>
CSS
".edithidden {
display: none;
}"
JS:
// editments is my shorthand for edit mode elements
let editments = document.getElementsByClassName("editelement")
let queryParams = new URLSearchParams(window.location.search);
function toggleeditmode() {
for (let i = 0; i < editments.length; i++) {
editments[i].classList.toggle("edithidden")
}
}
function hideeditments() {
console.log("hide edit mode")
for (let i = 0; i < editments.length; i++) {
editments[i].classList.add("edithidden")
}
}
function showeditments() {
console.log("edit mode enabled");
for (let i = 0; i < editments.length; i++) {
editments[i].classList.remove("edithidden")
}
}
function initialeditmode() {
editmode = queryParams.get("editmode",);
// This is the way I know to turn ?editmode=something
// into a boolean. If there is a better way, let me
// know.
if (editmode === null) { editmode = "false"; }
if (JSON.parse(editmode.toLowerCase())) {
showeditments();
} else {
hideeditments();
}
}
document.addEventListener("load", initialeditmode());
document.getElementById('toggleeditor').addEventListener('click', toggleeditmode);
Создание формы и модели для быстрой записи в сеть.
Я создаю форму вручную для быстрого входа в сеть. Она будет иметь поля netcontrol, opened, closed, early_checkins и regular_checkins. Эта форма будет отображаться только в том случае, если для данной сети нет записей об активности.
<%= if (len(activities) < 1) { %>
<%= partial("quicknetform.plush.html") %>
<% } %>
Теперь я попробую использовать сгенерированную модель для настройки валидации и парсинга. Основная идея заключается в том, что данные формы будут использоваться для создания ряда отдельных записей об активности.
Я буду использовать buffalo для генерации модели, пропуская миграции.
buffalo pop generate model -s quicknet
Теперь мы просмотрим Binding Docs, чтобы поработать над обработчиком.
Я отредактирую модель так, чтобы в ней были только поля json/form, которые находятся в моей форме. Я также создаю новый Validate без требования tx/pop. Из моего опыта работы с netcreate, я не думаю, что мне действительно нужна вариация формы, но я добавлю ее для полноты картины.
// Quicknet is not tied to any database table
type Quicknet struct {
Opened time.Time `json:"opened" form:"opened"`
Closed time.Time `json:"closed" form:"closed"`
NetControl string `json:"netcontrol" form:"netcontrol"`
EarlyCheckins string `json:"early_checkins" form:"early_checkins"`
RegularCheckins string `json:"regular_checkins" form:"regular_checkins"`
}
func (q *Quicknet) Validate() (*validate.Errors, error) {
return validate.NewErrors(), nil
}
Теперь в обработчике я должен иметь возможность привязать форму и записать значение netcontrol.
func QuickNetHandler(c buffalo.Context) error {
qn := &models.Quicknet{}
if err := c.Bind(qn); err != nil {
return err
}
logrus.Info("netcontrol", qn.NetControl)
net := models.Net{}
if err := models.DB.Find(&net, c.Param("id")); err != nil {
return errors.WithStack(err)
}
return c.Redirect(302, "/nets/%s?editmode=true", net.ID)
И это действительно работает
time="2022-06-23T13:35:49-04:00" level=info msg=netcontrolsdfas
Теперь я вернусь к моему Validate и добавлю две проверки.
func (q *Quicknet) Validate() (*validate.Errors, error) {
verrs := validate.NewErrors()
if q.NetControl == "" {
verrs.Add("netcontrol", "Netcontrol must not be blank")
}
if q.Opened.IsZero() {
verrs.Add("opened", "Opened must not be empty")
}
//verrs.Add(&validators.StringIsPresent{Field: n.Name, Name: "Name", Message: "Name can not be blank"})
return verrs, nil
}
Итак, в итоге я сделал много работы, съездил навестить семью, а затем вернулся. Так что давайте посмотрим, смогу ли я запечатлеть основные моменты. Во-первых, я переделал свою форму, чтобы использовать встроенные теги и помощники формы Buffalo. Я также сделал так, чтобы форма отображалась напрямую, а не перенаправлялась. Это привело к тому, что у меня практически идентичный код между NetHandler и QuickNetHandler. Скоро я смогу объединить их вместе и сделать проверку, чтобы определить, была ли форма QuickNet отправлена или нет.
Пока же я перенес весь контекстный набор из NetHandler и QuickNetHandler в общую функцию под названием _LearnNet.
func _LearnNet(c buffalo.Context, net models.Net) buffalo.Context {
activities := models.Activities{}
query := models.DB.Where("net = (?)", net.ID)
query.Order("time_at desc").All(&activities)
c.Set("activities", activities)
c.Set("opened", GetOpen(net.ID))
c.Set("closed", GetClose(net.ID))
c.Set("netcontrols", NetControls(net.ID))
c.Set("participants", NetParticipants(net.ID))
return c
}
Мой QuickNetHandler стал довольно большим, поэтому я буду рассматривать его по частям.
Сначала мы запрашиваем сеть из базы данных и устанавливаем ее в контекст. Затем мы устанавливаем и привязываем quicknet к контексту, после чего запускаем его через Validate. Обратите внимание, что в этом валидате нет соединения с базой данных Pop Tx. Если возникают ошибки валидации, мы снова отображаем шаблон.
func QuickNetHandler(c buffalo.Context) error {
net := models.Net{}
if err := models.DB.Find(&net, c.Param("id")); err != nil {
return errors.WithStack(err)
}
c.Set("net", net)
quicknet := &models.Quicknet{}
c.Set("quicknet", quicknet)
if err := c.Bind(quicknet); err != nil {
return err
}
// Validate the data from the html form
verrs, err := quicknet.Validate()
if err != nil {
return errors.WithStack(err)
}
if verrs.HasAny() {
c = _LearnNet(c, net)
logrus.Info("quicknet has verrs")
// Make the errors available inside the html template
c.Set("errors", verrs)
//c.Flash().Add("alert", "verrs")
return c.Render(422, r.HTML("home/netedit.plush.html"))
}
logrus.Info("quicknet has no verrs, continuing")
В моем html-шаблоне используется form_for. У меня есть текстовые области для обеих проверок, за которыми следует управление сетью, открытие и закрытие. Я могу установить значения по умолчанию с помощью формата iso на основе запланированного начала и закрытия. Я не разобрался, но форма не модифицирует группу форм с ошибками валидации, как это происходит в моем шаблоне netnew.plush.html
. Я вижу ошибки в контексте, и я играл с идентификаторами ввода и идентификаторами валидации. Я действительно хочу заставить это работать, но пока что я просто сбрасываю необработанные ошибки.
<%= form_for( quicknet, {action: quicknetPath({id: net.ID}), method: "POST"}) { %>
<%= if (errors) { %>
<%= errors %>
<% } %>
<div class="row ">
<div class="col-md-3">
<%= f.TextAreaTag("EarlyCheckins", {rows:7}) %>
</div>
<div class="col-md-3">
<%= f.TextAreaTag("RegularCheckins", {rows:7 }) %>
</div>
<div class="col-md-6">
<%= f.InputTag("NetControl") %>
<div class="form-group">
<label for="quicknet-opened">Open Net At</label>
<%= f.DateTimeTag("Opened", {value: net.PlannedStart.Format("2006-01-02T15:04")}) %>
</div>
<div class="form-group">
<label for="quicknet-closed">Close Net At</label>
<%= f.DateTimeTag("Closed", {value: net.PlannedEnd.Format("2006-01-02T15:04")}) %>
</div>
<div class="form-group">
<button class="btn btn-success" role="submit">Update</button>
</div>
</div>
</div>
<% } %>
Теперь, возвращаясь к моему QuickNetHandler, если мы пройдем все проверки, мы начнем создавать записи активности. Мы уже проверили, что NetControl не пуст, и что Opened имеет значение NotZero.
Я хочу принять контроль над сетью за 5 минут до ее открытия. GoLang’s time.Time позволяет вам довольно легко .Add
длительности. Существует метод .Sub
, но он не противоположен Add. Чтобы вычесть, вы .Add
отрицательную длительность. Я пропускаю все виды проверки ошибок здесь, потому что я доверяю своему коду, что он будет правильным, я уже знаю, что net.ID существует. Очевидно, что могут возникнуть некоторые условия гонки, но поскольку я еще даже не написал возможность удаления сети, меня это устраивает. Кроме того, я надеюсь когда-нибудь перенести эти операции из QuickNetHandler в собственный сервисный слой.
// Assume Net Control
_ = models.DB.Create(&models.Activity{
//ID: ncr_id,
Net: net.ID,
Action: "netcontrol",
Name: quicknet.NetControl,
TimeAt: quicknet.Opened.Add(-time.Minute * 5),
Description: "Assumed Net Control",
})
Далее мы открываем сеть.
// Open the net
_ = models.DB.Create(&models.Activity{
Net: net.ID,
Action: "open",
Name: quicknet.NetControl,
TimeAt: quicknet.Opened,
Description: "Opened the net",
})
Теперь я хочу закрыть сеть, если они указали время закрытия. Для этой быстрой формы я придерживаюсь мнения, что сеть длится не менее 5 минут. Если quicknet.Closed ненулевое, но меньше, чем 5 минут после quicknet.Opened, я установлю его равным 5 минутам после quicknet.Opened. Я добавлю это в функцию Validate модели.
// force Closed (if set) to be at least 5 minutes after Open
if !q.Closed.IsZero() && !q.Opened.IsZero() {
if q.Closed.Before(q.Opened.Add(time.Minute * 5)) {
q.Closed = q.Opened.Add(time.Minute * 5)
}
}
Затем в обработчике мы можем закрыть сеть.
// Close the net if set
if !quicknet.Closed.IsZero() {
// In validate, we forced quicknet.Closed to be at least 5
// minutes after the quicknet.Open
_ = models.DB.Create(&models.Activity{
Net: net.ID,
Action: "close",
Name: quicknet.NetControl,
TimeAt: quicknet.Closed,
Description: "Opened the net",
})
}
Я протестировал эти параметры со всевозможными входными значениями, и пока все работает отлично. Если я убираю open или оставляю netcontrol пустым, я получаю ошибки. Если я убираю «закрыто», не создается никакой активности Closed. Если я устанавливаю закрытие на более раннее время или на то же время, что и открытие, то время автоматически корректируется.
Побочное замечание — я до сих пор не затронул временные зоны. Эти действия создаются в базе данных со строкой типа «2022-06-23 15:40:29.099776-04:00», которая показывает местное смещение -4. В общем, я пока игнорирую это, планируя вернуться в будущем и исправить все мои временные метки за один раз.
Массовые проверки
Мне все еще нужно обработать массовые проверки. Я собираюсь создать функцию в моей модели, которая будет анализировать поле checkins и возвращать массив имен checkin.
Замечательное построение strings.FieldsFunc
Я предполагаю, что пользователи будут вставлять данные из различных редакторов. В основном с разделением пробелами, но возможно CSV или другие. Я искал способы разделения на запятую или пробел и обнаружил strings.FieldFunc, а основная документация делает именно то, что я хочу.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", strings.FieldsFunc(" name foo1;bar2,baz3..., bag,base", f))
}
// Output: Fields are: ["name" "foo1" "bar2" "baz3" "bag" "base"]
Я могу довольно легко сделать функцию ParseNames, которая удаляет большинство мусора и создает список уникальных имен.
func ParseNames(raw string) []string {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fields := strings.FieldsFunc(raw, f)
set := make(map[string]struct{})
for _, f := range fields {
_, isPresent := set[f]
if !isPresent {
// If the name is just a number, skip
// ex: "1. John" => ["1", "John"]
_, err := strconv.Atoi(f)
if err != nil {
set[f] = struct{}{}
}
}
}
keys := make([]string, 0, len(set))
for k := range set {
keys = append(keys, k)
}
return keys
}
Затем, чтобы закончить все это, я делаю записи активности для различных _checkins.
// now do the early checkins
if quicknet.EarlyCheckins != "" {
early_names := ParseNames(quicknet.EarlyCheckins)
early_time := quicknet.Opened.Add(-time.Second * 60)
for _, n := range early_names {
_ = models.DB.Create(&models.Activity{
Net: net.ID,
Action: "checkin",
Name: n,
TimeAt: early_time,
Description: "Early checkin",
})
}
}
if quicknet.RegularCheckins != "" {
regular_names := ParseNames(quicknet.RegularCheckins)
regular_time := quicknet.Opened.Add(time.Second * 30)
for _, n := range regular_names {
_ = models.DB.Create(&models.Activity{
Net: net.ID,
Action: "checkin",
Name: n,
TimeAt: regular_time,
Description: "Regular checkin",
})
}
}
Подведение итогов четвертого дня
Мой таймер показывает 16 часов 16 минут. Поскольку я закончил вчера около 10 часов, похоже, что сегодня я провел почти полный «рабочий день». Игра с html, javascript и другими программами настолько итеративна, что я действительно попал в поток. У меня было много перерывов в течение дня, но в целом это больше времени, чем я планировал потратить сегодня. Я также надеялся к этому времени перейти к более динамичному ajax (htmx) процессу редактирования метаданных сети и отдельных записей активности.
Мой QuickNetHandler раздулся в размерах. Если бы я увидел это в одном из наших проектов на python, я бы скривился. Я также уверен, что ряд моих циклов «for … range» можно сделать гораздо более сложным способом, и если бы я спросил об этом на stackoverflow, то, вероятно, получил бы комментарии вроде «почему бы вам просто не воспользоваться встроенной функцией map.unique.splitfield и не сэкономить себе 100 строк повторяющегося кода?».
Несмотря на все это, я действительно доволен своим прогрессом. Мне кажется, я начинаю «чувствовать» golang. С помощью довольно быстрого заполнения формы я могу быстро заполнить активность сети с разумными приближениями. Когда я добавлю встроенное редактирование активности, это станет еще лучше.
На этих двух снимках экрана показано заполнение формы с рядом «гуманизмов» в том, как я ввожу список позывных. Я также «случайно» оставил время закрытия раньше времени начала. Второй снимок экрана показывает, как эта форма заполнила записи о деятельности.
Наполненный