Purescript Halogen вручную запускает проверку ввода вне формы - PullRequest
0 голосов
/ 24 апреля 2018

У меня есть поля ввода, которые я пометил атрибутом required, но не могу найти способ вызвать проверку проверки (я не работаю внутри формы, поэтому использование действия кнопки отправки по умолчанию, выигранного '' не работает для меня).

Быстрый поиск показывает много функций валидности для основных типов элементов HTML, но я не уверен, как применить их к галогенам.

Есть ли способ вызвать эффект DOM, чтобы проверить все необходимые данные на странице и получить результат обратно?

Вот пример компонента, показывающего, чего я пытаюсь достичь

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
    }

1 Ответ

0 голосов
/ 26 апреля 2018

Я не думаю, что проверка формы HTML является наиболее эффективным способом проверки входных данных в приложении Halogen. Но я предполагаю, что у вас есть свои причины, и в любом случае представлю ответ.


Перво-наперво, если мы хотим иметь дело с элементами DOM, нам нужен способ их извлечения. Вот purecript версия 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

(Пока не беспокойтесь о новом импорте, в конце есть полный модуль)

Эта getElementById функция принимает read* функцию (вероятно, из DOM.HTML.Types), чтобы определить тип возвращаемого элемента и идентификатор элемента в виде строки.

Чтобы использовать это, нам нужно добавить дополнительное свойство к вашему HH.input:

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

В сторону: тип суммы с экземпляром Show будет более безопасным, чем жестко закодированные строковые идентификаторы везде. Я оставлю это тебе.

Cool. Теперь нам нужно вызвать это из ContinueClicked ветви вашей eval функции:

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

Это дает нам Maybe HTMLInputElement для игры. И это HTMLInputElement должно иметь свойство validity типа ValidityState, которое содержит информацию, которую мы ищем.

DOM.HTML.HTMLInputElement имеет функцию validity, которая даст нам доступ к этому свойству. Затем нам нужно будет выполнить некоторые манипуляции с внешними значениями, чтобы получить нужные нам данные. Для простоты давайте просто попробуем вытащить поле valid:

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

И с помощью этого маленького помощника мы можем завершить ветку 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"

А потом, сложив все вместе, мы получаем ...

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
...