GoNetNinja: День 4


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

На этих двух снимках экрана показано заполнение формы с рядом “гуманизмов” в том, как я ввожу список позывных. Я также “случайно” оставил время закрытия раньше времени начала. Второй снимок экрана показывает, как эта форма заполнила записи о деятельности.

Наполненный

Оцените статью
Procodings.ru
Добавить комментарий