I'm parsing a form in happstack but I don't know of a more concise way to gather the form variables on the server. Does anyone know if there are any tricks to reduce the amount of code needed here?
formHandler = do
method POST
delA1 <- look "address-01-delivery"
delA2 <- look "address-02-delivery"
delSuburb <- look "suburb-delivery"
delPostcode <- look "postcode-delivery"
delState <- look "state-delivery"
delCountry <- look "country-delivery"
bilA1 <- look "address-01-billing"
bilA2 <- look "address-02-billing"
bilSuburb <- look "suburb-billing"
bilPostcode <- look "postcode-billing"
bilState <- look "state-billing"
bilCountry <- look "country-billing"
Preface: This is clearly not going to reduce the amount of code that you posted above, but in the long run it may make things more maintainable as it provides a consistent way to perform validation and allows you to deal with strongly typed objects rather than large numbers of text values.
If you are going to do any amount of form processing, I would recommend using a forms library. I've personally used digestive-functors and have found it enjoyable to work with. As well, it comes with a Happstack backend which makes the Happstack integration very straight-forward.
This may be more than you care to invest in, but here is a sample of what your form code might look like using digestive functors. I didn't try compiling any of this, so it may or may not work, but hopefully it provides the general idea.
{-# LANGUAGE OverloadedStrings #-}
module Forms where
import Control.Applicative ((<$>), (<*>))
import Data.Text (Text)
import qualified Data.Text as T
import Happstack.Server
import Text.Digestive
data AddressState
= Alabama
| Alaska
| Arkansas
-- ...
deriving (Enum, Eq, Read)
data Country
= Canada
| Mexico
| UnitedStates
-- ...
deriving (Enum, Eq, Read)
data Address = Address
{ street1 :: Text
, street2 :: Maybe Text
, suburb :: Maybe Text
, postcode :: Int
, state :: AddressState
, country :: Country
}
data ShippingForm = ShippingForm
{ deliveryAddress :: Address
, billingAddress :: Address
}
addressForm :: (Monad m) => Form Text m Address
addressForm = Address
<$> "street1" .: nonEmptyText (text Nothing)
<*> "street2" .: optionalText Nothing
<*> "suburb" .: optionalText Nothing
<*> "postcode" .: stringRead "Please enter a valid postcode" Nothing
<*> "state" .: choice mkChoices Nothing
<*> "country" .: choice mkChoices Nothing
where
nonEmptyText = check "Cannot be empty" (not . T.null)
mkChoices = [(x, T.pack (show x)) | x <- [minBound .. maxBound]]
shippingForm :: (Monad m) => Form Text m ShippingForm
shippingForm = ShippingForm
<$> "delivery" .: addressForm
<*> "billing" .: addressForm
formHandler :: ServerPart Response
formHandler = do
method POST
decodeBody $ defaultBodyPolicy "/tmp" 4096 4096 4096
res <- runForm "shipping" shippingForm
case res of
(view, Nothing) -> do
-- setup your view
ok $ toResponse () -- <-- replace this with your view code
(_, Just formData) -> do
-- here, formData is a fully valid `ShippingForm`
let delStreet1 = (street1 . deliveryAddress) formData
-- do whatever you need to with this data (ie: persist it to a database, etc)
ok $ toResponse ()
For some good reading on digestive functors, you could check out the following links: