简体   繁体   English

Purescript Halogen在表单外部手动触发输入验证

[英]Purescript Halogen manually trigger input validation outside of a form

I have input fields which I have marked with a required attribute, but can't figure out a way to trigger a validation check (I am not working inside of a form, so using a default submit button action won't work for me). 我有一些用required属性标记的输入字段,但无法找到触发验证检查的方法(我不在表单内部工作,因此使用默认的提交按钮操作对我来说不起作用) 。

A quick pursuit search shows many validity functions for core html element types, but I'm not sure how to apply these to Halogen. 快速追踪搜索显示了许多针对html核心元素类型的有效性函数,但是我不确定如何将其应用于Halogen。

Is there some way to trigger a DOM effect to check all required inputs on the page and get a result back? 是否有某种触发DOM效果的方法来检查页面上所有必需的输入并返回结果?

Here is an example component showing what I'm trying to achieve 这是一个示例组件,显示了我要实现的目标

import Prelude

import Data.Maybe (Maybe(..))
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP

data Message = Void

type State =
  { textValue :: String
  , verified :: Boolean
  }

data Query a = ContinueClicked a | InputEntered String a

inputHtml :: State -> H.ComponentHTML Query
inputHtml state =
  HH.div [ HP.class_ $ H.ClassName "input-div" ]
         [ HH.label_ [ HH.text "This is a required field" ]
         , HH.input [ HP.type_ HP.InputText
                    , HE.onValueInput $ HE.input InputEntered
                    , HP.value state.textValue
                    , HP.required true
                    ]
         , HH.button [ HE.onClick $ HE.input_ ContinueClicked ]
                     [ HH.text "Continue"]
         ]

verifiedHtml :: H.ComponentHTML Query
verifiedHtml =
  HH.div_ [ HH.h3_ [ HH.text "Verified!" ] ]

render :: State -> H.ComponentHTML Query
render state = if state.verified then verifiedHtml else inputHtml state

eval :: forall m. Query ~> H.ComponentDSL State Query Message m
eval = case _ of
  InputEntered v next -> do
    H.modify $ (_ { textValue = v })
    pure next
  ContinueClicked next -> do
    let inputValid = false -- somehow use the required prop to determine if valid
    when inputValid $ H.modify $ (_ { verified = true })
    pure next

initialState :: State
initialState =
  { textValue : ""
  , verified : false
  }

component :: forall m. H.Component HH.HTML Query Unit Message m
component =
  H.component
    { initialState: const initialState
    , render
    , eval
    , receiver: const Nothing
    }

I don't think relying on HTML form validation is the most effective way of checking inputs within a Halogen application. 我认为依靠HTML表单验证不是检查Halogen应用程序中输入的最有效方法。 But I'll assume you have your reasons and present an answer anyway. 但我认为您有自己的理由,无论如何都要给出答案。


First things first, if we want to deal with DOM elements we need a way to retrieve them. 首先,如果要处理DOM元素,我们需要一种检索它们的方法。 Here's a purescript version of document.getElementById 这里的purescript版本document.getElementById

getElementById
    :: forall a eff
     . (Foreign -> F a)
    -> String
    -> Eff (dom :: DOM | eff) (Maybe a)
getElementById reader elementId =
    DOM.window
        >>= DOM.document
        <#> DOM.htmlDocumentToNonElementParentNode
        >>= DOM.getElementById (wrap elementId)
        <#> (_ >>= runReader reader)

runReader :: forall a b. (Foreign -> F b) -> a -> Maybe b
runReader r =
    hush <<< runExcept <<< r <<< toForeign

(Don't worry about the new imports for now, there's a complete module at the end) (现在不必担心新的导入,最后有一个完整的模块)

This getElementById function takes a read* function (probably from DOM.HTML.Types ) to determine the type of element you get back, and an element id as a string. getElementById函数采用read*函数(可能来自DOM.HTML.Types )来确定您返回的元素的类型,以及元素ID作为字符串。

In order to use this, we need to add an extra property to your HH.input : 为了使用它,我们需要向您的HH.input添加一个额外的属性:

HH.input [ HP.type_ HP.InputText
         , HE.onValueInput $ HE.input InputEntered
         , HP.value state.textValue
         , HP.required true
         , HP.id_ "myInput"  <-- edit
         ]

Aside: a sum type with a Show instance would be safer than hard-coding stringy ids everywhere. 除了:带Show实例的求和类型比在各处硬编码字符串id更安全。 I'll leave that one to you. 我将那个留给你。

Cool. 凉。 Now we need to call this from the ContinueClicked branch of your eval function: 现在,我们需要从eval函数的ContinueClicked分支中调用此函数:

ContinueClicked next ->
    do maybeInput <- H.liftEff $
            getElementById DOM.readHTMLInputElement "myInput"
    ...

This gives us a Maybe HTMLInputElement to play with. 这给我们提供了一个Maybe HTMLInputElement And that HTMLInputElement should have a validity property of type ValidityState , which has the information we're after. HTMLInputElement应该具有类型ValidityStatevalidity属性,该属性具有我们所需要的信息。

DOM.HTML.HTMLInputElement has a validity function that will give us access to that property. DOM.HTML.HTMLInputElement具有一个validity函数,该函数将使我们能够访问该属性。 Then we'll need to do some foreign value manipulation to try and get the data out that we want. 然后,我们需要进行一些外来的值操作,以尝试获取所需的数据。 For simplicity, let's just try and pull out the valid field: 为了简单起见,让我们尝试拉出valid字段:

isValid :: DOM.ValidityState -> Maybe Boolean
isValid =
    runReader (readProp "valid" >=> readBoolean)

And with that little helper, we can finish the ContinueClicked branch: 有了这个小帮手,我们就可以完成ContinueClicked分支:

ContinueClicked next ->
    do maybeInput <- H.liftEff $
            getElementById DOM.readHTMLInputElement "myInput"

       pure next <*
       case maybeInput of
            Just input ->
                do validityState <- H.liftEff $ DOM.validity input
                   when (fromMaybe false $ isValid validityState) $
                        H.modify (_ { verified = true })
            Nothing ->
                H.liftEff $ log "myInput not found"

And then putting it all together we have... 然后将所有内容放在一起...

module Main where

import Prelude

import Control.Monad.Aff (Aff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Except (runExcept)

import Data.Either (hush)
import Data.Foreign (Foreign, F, toForeign, readBoolean)
import Data.Foreign.Index (readProp)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Newtype (wrap)

import DOM (DOM)
import DOM.HTML (window) as DOM
import DOM.HTML.HTMLInputElement (validity) as DOM
import DOM.HTML.Types
    (ValidityState, htmlDocumentToNonElementParentNode, readHTMLInputElement) as DOM
import DOM.HTML.Window (document) as DOM
import DOM.Node.NonElementParentNode (getElementById) as DOM

import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
import Halogen.VDom.Driver (runUI)

main :: Eff (HA.HalogenEffects (console :: CONSOLE)) Unit
main = HA.runHalogenAff do
    body <- HA.awaitBody
    runUI component unit body

type Message
    = Void

type Input
    = Unit

type State
    = { textValue    :: String
      , verified     :: Boolean
      }

data Query a
    = ContinueClicked a
    | InputEntered String a

component
    :: forall eff
     . H.Component HH.HTML Query Unit Message (Aff (console :: CONSOLE, dom :: DOM | eff))
component =
    H.component
        { initialState: const initialState
        , render
        , eval
        , receiver: const Nothing
        }

initialState :: State
initialState =
  { textValue : ""
  , verified : false
  }

render :: State -> H.ComponentHTML Query
render state =
    if state.verified then verifiedHtml else inputHtml
  where
    verifiedHtml =
        HH.div_ [ HH.h3_ [ HH.text "Verified!" ] ]

    inputHtml =
        HH.div
            [ HP.class_ $ H.ClassName "input-div" ]
            [ HH.label_ [ HH.text "This is a required field" ]
            , HH.input
                [ HP.type_ HP.InputText
                , HE.onValueInput $ HE.input InputEntered
                , HP.value state.textValue
                , HP.id_ "myInput"
                , HP.required true
                ]
            , HH.button
                [ HE.onClick $ HE.input_ ContinueClicked ]
                [ HH.text "Continue" ]
             ]

eval
    :: forall eff
     . Query
    ~> H.ComponentDSL State Query Message (Aff (console :: CONSOLE, dom :: DOM | eff))
eval = case _ of
    InputEntered v next ->
        do H.modify (_{ textValue = v })
           pure next

    ContinueClicked next ->
        do maybeInput <- H.liftEff $
                getElementById DOM.readHTMLInputElement "myInput"

           pure next <*
           case maybeInput of
                Just input ->
                    do validityState <- H.liftEff $ DOM.validity input
                       when (fromMaybe false $ isValid validityState) $
                            H.modify (_ { verified = true })
                Nothing ->
                    H.liftEff $ log "myInput not found"

getElementById
    :: forall a eff
     . (Foreign -> F a)
    -> String
    -> Eff (dom :: DOM | eff) (Maybe a)
getElementById reader elementId =
    DOM.window
        >>= DOM.document
        <#> DOM.htmlDocumentToNonElementParentNode
        >>= DOM.getElementById (wrap elementId)
        <#> (_ >>= runReader reader)

isValid :: DOM.ValidityState -> Maybe Boolean
isValid =
    runReader (readProp "valid" >=> readBoolean)

runReader :: forall a b. (Foreign -> F b) -> a -> Maybe b
runReader r =
    hush <<< runExcept <<< r <<< toForeign

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM