Update part10

This commit is contained in:
Richard Feldman
2018-05-05 05:41:45 -04:00
parent e6ec9a6584
commit af4ad8e46b
20 changed files with 132 additions and 73 deletions

View File

@@ -19,7 +19,7 @@
"evancz/url-parser": "2.0.1 <= v < 3.0.0", "evancz/url-parser": "2.0.1 <= v < 3.0.0",
"lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0", "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
"mgold/elm-date-format": "1.3.0 <= v < 2.0.0", "mgold/elm-date-format": "1.3.0 <= v < 2.0.0",
"rtfeldman/elm-validate": "2.0.0 <= v < 3.0.0", "rtfeldman/elm-validate": "3.0.0 <= v < 4.0.0",
"rtfeldman/selectlist": "1.0.0 <= v < 2.0.0" "rtfeldman/selectlist": "1.0.0 <= v < 2.0.0"
}, },
"elm-version": "0.18.0 <= v < 0.19.0" "elm-version": "0.18.0 <= v < 0.19.0"

View File

@@ -1,5 +1,5 @@
{ {
"rtfeldman/elm-validate": "2.0.0", "rtfeldman/elm-validate": "3.0.0",
"rtfeldman/selectlist": "1.0.0", "rtfeldman/selectlist": "1.0.0",
"elm-lang/navigation": "2.1.0", "elm-lang/navigation": "2.1.0",
"elm-lang/virtual-dom": "2.0.4", "elm-lang/virtual-dom": "2.0.4",

View File

@@ -16,7 +16,7 @@ type alias Model =
{ name : String, email : String, age : String, selections : List String } { name : String, email : String, age : String, selections : List String }
modelValidator : Validator Model String modelValidator : Validator String Model
modelValidator = modelValidator =
Validate.all Validate.all
[ ifBlank .name "Please enter a name." [ ifBlank .name "Please enter a name."
@@ -28,8 +28,7 @@ modelValidator =
validate modelValidator validate modelValidator
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] } { name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
== [ "Please enter an email address.", "Age must be a whole number." ] --> [ "Please enter an email address.", "Age must be a whole number." ]
--> True
``` ```
You can represent your errors however you like. One nice approach is to use You can represent your errors however you like. One nice approach is to use
@@ -56,8 +55,7 @@ type alias Model =
validate modelValidator validate modelValidator
{ name = "Sam", email = "", age = "abc", selections = [ "cats" ] } { name = "Sam", email = "", age = "abc", selections = [ "cats" ] }
== [ ( Email, "Please enter an email address." ) --> [ ( Email, "Please enter an email address." )
, ( Age, "Age must be a whole number." ) --> , ( Age, "Age must be a whole number." )
] --> ]
--> True
``` ```

View File

@@ -1,5 +1,5 @@
{ {
"version": "2.0.0", "version": "3.0.0",
"summary": "Convenience functions for validating data.", "summary": "Convenience functions for validating data.",
"repository": "https://github.com/rtfeldman/elm-validate.git", "repository": "https://github.com/rtfeldman/elm-validate.git",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",

View File

@@ -4,6 +4,7 @@ module Validate
, all , all
, any , any
, firstError , firstError
, fromErrors
, ifBlank , ifBlank
, ifEmptyDict , ifEmptyDict
, ifEmptyList , ifEmptyList
@@ -21,7 +22,7 @@ module Validate
{-| Convenience functions for validating data. {-| Convenience functions for validating data.
import Validate exposing (ifBlank, ifNotInt, validate) import Validate exposing (Validator, ifBlank, ifNotInt, validate)
type Field = Name | Email | Age type Field = Name | Email | Age
@@ -49,7 +50,7 @@ module Validate
# Creating validators # Creating validators
@docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse @docs ifBlank, ifNotInt, ifEmptyList, ifEmptyDict, ifEmptySet, ifNothing, ifInvalidEmail, ifTrue, ifFalse, fromErrors
# Combining validators # Combining validators
@@ -86,7 +87,7 @@ type Validator error subject
{-| Return an error if the given predicate returns `True` for the given {-| Return an error if the given predicate returns `True` for the given
subject. subject.
import Validate exposing (ifBlank, ifNotInt, validate) import Validate exposing (Validator, ifBlank, ifNotInt, validate)
type Field = Name | Email | Age type Field = Name | Email | Age
@@ -116,7 +117,7 @@ validate (Validator getErrors) subject =
{-| Return an error if the given `String` is empty, or if it contains only {-| Return an error if the given `String` is empty, or if it contains only
whitespace characters. whitespace characters.
import Validate exposing (ifBlank, ifNotInt) import Validate exposing (Validator, ifBlank)
modelValidator : Validator Model String modelValidator : Validator Model String
modelValidator = modelValidator =
@@ -132,10 +133,31 @@ ifBlank subjectToString error =
{-| Return an error if the given `String` cannot be parsed as an `Int`. {-| Return an error if the given `String` cannot be parsed as an `Int`.
import Validate exposing (Validator, ifNotInt)
modelValidator : Validator Model String
modelValidator =
Validate.all
[ ifNotInt .followers (\_ -> "Please enter a whole number for followers.")
, ifNotInt .stars (\stars -> "Stars was \"" ++ stars ++ "\", but it needs to be a whole number.")"
]
-} -}
ifNotInt : (subject -> String) -> error -> Validator error subject ifNotInt : (subject -> String) -> (String -> error) -> Validator error subject
ifNotInt subjectToString error = ifNotInt subjectToString errorFromString =
ifFalse (\subject -> isInt (subjectToString subject)) error let
getErrors subject =
let
str =
subjectToString subject
in
if isInt str then
[]
else
[ errorFromString str ]
in
Validator getErrors
{-| Return an error if a `List` is empty. {-| Return an error if a `List` is empty.
@@ -167,16 +189,65 @@ ifNothing subjectToMaybe error =
{-| Return an error if an email address is malformed. {-| Return an error if an email address is malformed.
import Validate exposing (Validator, ifBlank, ifNotInt)
modelValidator : Validator Model String
modelValidator =
Validate.all
[ ifInvalidEmail .primaryEmail (\_ -> "Please enter a valid primary email address.")
, ifInvalidEmail .superSecretEmail (\email -> "Unfortunately, \"" ++ email ++ "\" is not a valid Super Secret Email Address.")
]
-} -}
ifInvalidEmail : (subject -> String) -> error -> Validator error subject ifInvalidEmail : (subject -> String) -> (String -> error) -> Validator error subject
ifInvalidEmail subjectToEmail error = ifInvalidEmail subjectToEmail errorFromEmail =
ifFalse (\subject -> isValidEmail (subjectToEmail subject)) error let
getErrors subject =
let
email =
subjectToEmail subject
in
if isValidEmail email then
[]
else
[ errorFromEmail email ]
in
Validator getErrors
{-| Create a custom validator, by providing a function that returns a list of
errors given a subject.
import Validate exposing (Validator, fromErrors)
modelValidator : Validator Model String
modelValidator =
fromErrors modelToErrors
modelToErrors : Model -> List String
modelToErrors model =
let
usernameLength =
String.length model.username
in
if usernameLength < minUsernameChars then
[ "Username not long enough" ]
else if usernameLength > maxUsernameChars then
[ "Username too long" ]
else
[]
-}
fromErrors : (subject -> List error) -> Validator error subject
fromErrors toErrors =
Validator toErrors
{-| Return an error if a predicate returns `True` for the given {-| Return an error if a predicate returns `True` for the given
subject. subject.
import Validate exposing (ifTrue) import Validate exposing (Validator, ifTrue)
modelValidator : Validator Model String modelValidator : Validator Model String
modelValidator = modelValidator =
@@ -199,7 +270,7 @@ ifTrue test error =
{-| Return an error if a predicate returns `False` for the given {-| Return an error if a predicate returns `False` for the given
subject. subject.
import Validate exposing (ifFalse) import Validate exposing (Validator, ifFalse)
modelValidator : Validator Model String modelValidator : Validator Model String
modelValidator = modelValidator =
@@ -226,7 +297,7 @@ ifFalse test error =
{-| Run each of the given validators, in order, and return their concatenated {-| Run each of the given validators, in order, and return their concatenated
error lists. error lists.
import Validate exposing (ifBlank, ifNotInt) import Validate exposing (Validator, ifBlank, ifNotInt)
modelValidator : Validator Model String modelValidator : Validator Model String
modelValidator = modelValidator =
@@ -253,7 +324,7 @@ all validators =
{-| Run each of the given validators, in order, stopping after the first error {-| Run each of the given validators, in order, stopping after the first error
and returning it. If no errors are encountered, return `Nothing`. and returning it. If no errors are encountered, return `Nothing`.
import Validate exposing (ifBlank, ifInvalidEmail, ifNotInt) import Validate exposing (Validator, ifBlank, ifInvalidEmail, ifNotInt)
type alias Model = type alias Model =
@@ -337,7 +408,7 @@ isBlank str =
Regex.contains lacksNonWhitespaceChars str Regex.contains lacksNonWhitespaceChars str
{-| Returns `True` if the email is malformed. {-| Returns `True` if the email is valid.
[`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood. [`ifInvalidEmail`](#ifInvalidEmail) uses this under the hood.

View File

@@ -3,7 +3,7 @@ module Data.Article.Author exposing (Author, decoder)
import Data.User as User exposing (Username) import Data.User as User exposing (Username)
import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Data.UserPhoto as UserPhoto exposing (UserPhoto)
import Json.Decode as Decode exposing (Decoder) import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (custom, decode, required) import Json.Decode.Pipeline exposing (custom, decode, optional, required)
decoder : Decoder Author decoder : Decoder Author
@@ -12,7 +12,7 @@ decoder =
|> required "username" User.usernameDecoder |> required "username" User.usernameDecoder
|> required "bio" (Decode.nullable Decode.string) |> required "bio" (Decode.nullable Decode.string)
|> required "image" UserPhoto.decoder |> required "image" UserPhoto.decoder
|> required "following" Decode.bool |> optional "following" Decode.bool False
type alias Author = type alias Author =

View File

@@ -4,7 +4,7 @@ import Data.AuthToken as AuthToken exposing (AuthToken)
import Data.UserPhoto as UserPhoto exposing (UserPhoto) import Data.UserPhoto as UserPhoto exposing (UserPhoto)
import Html exposing (Html) import Html exposing (Html)
import Json.Decode as Decode exposing (Decoder) import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (decode, required) import Json.Decode.Pipeline exposing (decode, optional, required)
import Json.Encode as Encode exposing (Value) import Json.Encode as Encode exposing (Value)
import Json.Encode.Extra as EncodeExtra import Json.Encode.Extra as EncodeExtra
import UrlParser import UrlParser
@@ -16,8 +16,6 @@ type alias User =
, username : Username , username : Username
, bio : Maybe String , bio : Maybe String
, image : UserPhoto , image : UserPhoto
, createdAt : String
, updatedAt : String
} }
@@ -33,8 +31,6 @@ decoder =
|> required "username" usernameDecoder |> required "username" usernameDecoder
|> required "bio" (Decode.nullable Decode.string) |> required "bio" (Decode.nullable Decode.string)
|> required "image" UserPhoto.decoder |> required "image" UserPhoto.decoder
|> required "createdAt" Decode.string
|> required "updatedAt" Decode.string
encode : User -> Value encode : User -> Value
@@ -45,8 +41,6 @@ encode user =
, ( "username", encodeUsername user.username ) , ( "username", encodeUsername user.username )
, ( "bio", EncodeExtra.maybe Encode.string user.bio ) , ( "bio", EncodeExtra.maybe Encode.string user.bio )
, ( "image", UserPhoto.encode user.image ) , ( "image", UserPhoto.encode user.image )
, ( "createdAt", Encode.string user.createdAt )
, ( "updatedAt", Encode.string user.updatedAt )
] ]

View File

@@ -39,7 +39,15 @@ photoToUrl : UserPhoto -> String
photoToUrl (UserPhoto maybeUrl) = photoToUrl (UserPhoto maybeUrl) =
case maybeUrl of case maybeUrl of
Nothing -> Nothing ->
"https://static.productionready.io/images/smiley-cyrus.jpg" defaultPhotoUrl
Just "" ->
defaultPhotoUrl
Just url -> Just url ->
url url
defaultPhotoUrl : String
defaultPhotoUrl =
"/assets/images/smiley-cyrus.jpg"

View File

@@ -4,7 +4,7 @@ import Data.Article as Article exposing (Article, Body)
import Data.Session exposing (Session) import Data.Session exposing (Session)
import Data.User exposing (User) import Data.User exposing (User)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (attribute, class, disabled, href, id, placeholder, type_, value) import Html.Attributes exposing (attribute, class, defaultValue, disabled, href, id, placeholder, type_)
import Html.Events exposing (onInput, onSubmit) import Html.Events exposing (onInput, onSubmit)
import Http import Http
import Page.Errored exposing (PageLoadError, pageLoadError) import Page.Errored exposing (PageLoadError, pageLoadError)
@@ -102,26 +102,26 @@ viewForm model =
[ class "form-control-lg" [ class "form-control-lg"
, placeholder "Article Title" , placeholder "Article Title"
, onInput SetTitle , onInput SetTitle
, value model.title , defaultValue model.title
] ]
[] []
, Form.input , Form.input
[ placeholder "What's this article about?" [ placeholder "What's this article about?"
, onInput SetDescription , onInput SetDescription
, value model.description , defaultValue model.description
] ]
[] []
, Form.textarea , Form.textarea
[ placeholder "Write your article (in markdown)" [ placeholder "Write your article (in markdown)"
, attribute "rows" "8" , attribute "rows" "8"
, onInput SetBody , onInput SetBody
, value model.body , defaultValue model.body
] ]
[] []
, Form.input , Form.input
[ placeholder "Enter tags" [ placeholder "Enter tags"
, onInput SetTags , onInput SetTags
, value (String.join " " model.tags) , defaultValue (String.join " " model.tags)
] ]
[] []
, button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ] , button [ class "btn btn-lg pull-xs-right btn-primary", disabled model.isSaving ]

View File

@@ -133,37 +133,12 @@ update session msg model =
) )
Just user -> Just user ->
let user.token
-- TODO |> Request.Profile.toggleFollow
-- 1) head over to the Request.Profile module and look up
-- what arguments the `toggleFollow` function takes.
--
-- 2) call Request.Profile.toggleFollow here,
-- to get back a Request.
--
-- 3) pass that Request to Http.send to get a Cmd.
-- Use that Cmd here.
--
-- Here's the documentation for Http.send:
-- http://package.elm-lang.org/packages/elm-lang/http/1.0.0/Http#send
--
-- Here are some hepful values that are in scope:
--
-- user.token : Maybe AuthToken
--
-- profile : Profile [look in the Data.Profile module!]
--
-- FollowCompleted : Result Http.Error Profile -> Msg
--
cmd : Cmd Msg
cmd =
Request.Profile.toggleFollow
profile.username profile.username
profile.following profile.following
user.token
|> Http.send FollowCompleted |> Http.send FollowCompleted
in |> pair model
( model, cmd )
FollowCompleted (Ok newProfile) -> FollowCompleted (Ok newProfile) ->
( { model | profile = newProfile }, Cmd.none ) ( { model | profile = newProfile }, Cmd.none )

View File

@@ -186,10 +186,23 @@ modelValidator =
Validate.all Validate.all
[ ifBlank .username ( Username, "username can't be blank." ) [ ifBlank .username ( Username, "username can't be blank." )
, ifBlank .email ( Email, "email can't be blank." ) , ifBlank .email ( Email, "email can't be blank." )
, ifBlank .password ( Password, "password can't be blank." ) , Validate.fromErrors passwordLength
] ]
minPasswordChars : Int
minPasswordChars =
6
passwordLength : Model -> List Error
passwordLength { password } =
if String.length password < minPasswordChars then
[ ( Password, "password must be at least " ++ toString minPasswordChars ++ " characters long." ) ]
else
[]
errorsDecoder : Decoder (List String) errorsDecoder : Decoder (List String)
errorsDecoder = errorsDecoder =
decode (\email username password -> List.concat [ email, username, password ]) decode (\email username password -> List.concat [ email, username, password ])

View File

@@ -3,4 +3,4 @@ module Request.Helpers exposing (apiUrl)
apiUrl : String -> String apiUrl : String -> String
apiUrl str = apiUrl str =
"https://conduit.productionready.io/api" ++ str "/api" ++ str