使用菜单组件处理用户输入的用户输入
#go #codenewbie #100devs #tui

在上一个教程中,我们执行了一个“ Hello World”应用程序,并且仅处理了一些用户输入(“按CTRL+C到退出”)。

但是,我们并没有真正感觉到实际使用用户输入来更改模型数据的感觉,进而更改我们在应用程序中看到的内容。因此,在本教程中,我们将创建一个菜单组件,使我们可以在按钮之间移动。

ð定义我们的数据

对于任何气泡茶组件,我们需要的第一件事是我们的模型负责的数据。如果您记得在我们的简单页面模型中,数据只是我们显示的文本:

type simplePage struct { text string }

在我们的菜单中,我们需要做的是:

  • 显示我们的选项
  • 显示选择哪个选项
  • 此外,让用户按ENTER转到另一个页面。但是我们将在以后的教程中添加。
    • 目前,我们仍然可以通过使用OnPress函数来告诉我们,如果用户按Enter。

因此,我们的模型的数据看起来像这样;如果您正在关注,请将其写在名为menu.go的文件中。

type menu struct {
    options       []menuItem
    selectedIndex int
}

type menuItem struct {
    text    string
    onPress func() tea.Msg
}

菜单由menuitems组成,每个menuitem都有文本和一个函数处理按Enter。在本教程中,我们将在全帽和全羊皮之间进行应用程序切换,因此至少在做某事。

它返回tea.Msg,因为我们能够根据本用户输入更改数据。我们将在下一节中了解为什么在实现Model接口时。

ð§实施模型接口

如果您回想起,让我们将模型用作UI组件,则需要实现此接口:

type Model interface {
    Init() Cmd
    Update(msg Msg) (Model, Cmd)
    View() string
}

首先让我们写下初始功能。

func (m menu) Init() tea.Cmd { return nil }

再次,我们仍然没有任何初始Cmd我们需要运行,因此我们可以返回nil

对于View函数,让我们用箭头做一个老式菜单,以告诉我们当前选择了哪个项目。

func (m menu) View() string {
    var options []string
    for i, o := range m.options {
        if i == m.selectedIndex {
            options = append(options, fmt.Sprintf("-> %s", o.text))
        } else {
            options = append(options, fmt.Sprintf("   %s", o.text))
        }
    }
    return fmt.Sprintf(`%s

Press enter/return to select a list item, arrow keys to move, or Ctrl+C to exit.`,
    strings.Join(options, "\n"))
}

正如上一个教程中提到的那样,使泡泡茶真正可以学习的一件事是,UI的显示基本上是一个大字符串。因此,在menu.View中,我们制作了一片字符串,其中所选选项具有箭头,而未选择的选项具有领先空间。然后,我们将它们一起加入在一起,并将我们的污垢添加到底部。

最后,让我们编写我们的更新方法来处理用户输入。

func (m menu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg.(type) {
    case tea.KeyMsg:
        switch msg.(tea.KeyMsg).String() {
        case "ctrl+c":
            return m, tea.Quit
        case "down", "right", "up", "left":
            return m.moveCursor(msg.(tea.KeyMsg)), nil
        }
    }
    return m, nil
}

func (m menu) moveCursor(msg tea.KeyMsg) menu {
    switch msg.String() {
    case "up", "left":
        m.selectedIndex--
    case "down", "right":
        m.selectedIndex++
    default:
        // do nothing
    }

    optCount := len(m.options)
    m.selectedIndex = (m.selectedIndex + optCount) % optCount
    return m
}

Update方法是该应用程序中最复杂的部分,所以让我们分解。

case "ctrl+c":
    return m, tea.Quit

像以前一样,我们正在处理KeyMsg类型,而我们是CTRL+C按键,通过退回cmd来戒烟。

case "down", "right", "up", "left":
    return m.moveCursor(msg.(tea.KeyMsg)), nil

对于箭头键,我们使用辅助函数moveCursor,该功能返回更新的模型。

func (m menu) moveCursor(msg tea.KeyMsg) menu {
    switch msg.String() {
    case "up", "left":
        m.selectedIndex--
    case "down", "right":
        m.selectedIndex++
    default:
        // do nothing
    }

    optCount := len(m.options)
    m.selectedIndex = (m.selectedIndex + optCount) % optCount
    return m
}

向上和左keymsg字符串用作我们的“导航”键,向下和右侧的键将我们导航,减少和递增m.selected

然后,我们使用mod运算符来确保m.selected是我们选项的指标之一。

最后,随着模型的更新,moveCursor返回了Update返回的模型,而新模型最终通过我们的View方法处理。

不过,在我们继续处理Enter密钥之前,我们应该看到应用程序运行。因此,让我们将新的menu组件放入main函数并运行它。

func main() {
    m := menu{
        options: []menuItem{
            menuItem{
                text: "new check-in",
                onPress: func() tea.Msg { return struct{}{} },
            },
            menuItem{
                text: "view check-ins",
                onPress: func() tea.Msg { return struct{}{} },
            },
        },
    }

    p := tea.NewProgram(m)
    if err := p.Start(); err != nil {
        panic(err)
    }
}

目前,OnPress只是一个返回空结构的无开机。现在,让我们运行我们的应用程序。

go build
./check-ins

您应该看到这样的东西:

List of options you can select, with an arrow pointed to the selected one, and instructions at the bottom

酷!现在,菜单可以切换选择的内容!现在让我们处理该用户输入。

处理Enter键并查看CMD类型的实际功能

到目前为止,我们还没有真正仔细研究tea.Cmd类型。这是Update方法的两个返回值之一,但是我们只使用它来退出应用程序。让我们仔细看看它的类型签名。

type Cmd func() tea.Msg

a Cmd是某种功能,可以做一些事情,然后给我们回到tea.Msg。该功能可能是时间传递,可以是I/O喜欢检索一些数据,实际上一切都会发生! tea.Msg反过

因此,处理用户按Enter键,然后运行任意的ONPRESS函数,是使用CMD的一种方式。因此,让我们从Enter Button处理程序开始。

  case tea.KeyMsg:
      switch msg.(tea.KeyMsg).String() {
      case "q":
          return m, tea.Quit
      case "down", "right", "up", "left":
          return m.moveCursor(msg.(tea.KeyMsg)), nil
+     case "enter", "return":
+         return m, m.options[m.selectedIndex].onPress
      }

请注意,当用户按Enter按ENTER时,我们将返回模型,但我们还返回所选项目的onPress函数。如果您还记得我们定义menuItem类型时,其onPress字段的类型为func() tea.Msg。换句话说,与Cmd类型别名!

完全匹配

不过,在Update方法中我们还需要做另一件事。现在,我们只处理tea.KeyMsg类型。我们要返回的用于切换所选物品的大写字母的类型将是一种全新的ot tea.Msg,因此我们需要定义它,然后在我们的更新方法中添加一个案例。首先,让我们定义结构。

type toggleCasingMsg struct{}

我们不需要传递任何数据,因此我们的MSG只是一个空结构。如果您还记得的话,tea.Msg类型只是一个空接口,因此我们可以在需要的数据中包含一个或很少的数据。

现在回到更新方法中,让我们添加一个toggleCasingMsg

的案例

首先添加方法toggleSelectedItemCase

func (m menu) toggleSelectedItemCase() tea.Model {
    selectedText := m.options[m.selectedIndex].text
    if selectedText == strings.ToUpper(selectedText) {
        m.options[m.selectedIndex].text = strings.ToLower(selectedText)
    } else {
        m.options[m.selectedIndex].text = strings.ToUpper(selectedText)
    }
    return m
}

然后将其添加到Update方法中。

  func (m menu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
      switch msg.(type) {
+     case toggleCasingMsg:
+         return m.toggleSelectedItemCase(), nil
      case tea.KeyMsg:
          // our KeyMsg handlers here

在togglecasingmsg上,我们更新了所选菜单项的外壳,然后返回更新的模型。

最后,在app.go中,让我们使用我们的togglecasingmsg

  menuItem{
      text:    "new check-in",
-     onPress: func() tea.Msg { return struct{}{} },
+     onPress: func() tea.Msg { return toggleCasingMsg{} },
  },
  menuItem{
      text:    "view check-ins",
-     onPress: func() tea.Msg { return struct{}{} },
+     onPress: func() tea.Msg { return toggleCasingMsg{} },
  },

现在让我们尝试我们的应用程序!

go build
./check-ins

该应用现在应该看起来像这样:

List of options you can select, with an arrow pointed to the selected one that's now in all caps because the user had pressed enter, and instructions at the bottom

注意,顺便说一下,在应用程序的这个阶段,这不是我们可以处理的唯一方法;我们还可以完全处理更新功能中的所有切换,而不是必须使用CMD处理。我选择使用CMD的原因是:

  • 在泡泡茶中显示一个简单的用例
  • 通过使用CMD,我们可以将任意事件处理程序功能传递到我们的组件中,如果您在React中进行了编码,则可以将类似的模式传递到我们的组件中。

接下来,我们有一个菜单,但是还不是很浮华。在下一个教程中,我们将看到如何使用泡泡茶使我们的应用看起来很酷。首先,用泡泡茶的CSS般的唇彩包装!