├── .gitignore ├── .travis.yml ├── elm.json ├── README.md ├── LICENSE ├── CHANGELOG.md └── src └── Html ├── Extra.elm ├── Attributes ├── Extra.elm └── Autocomplete.elm └── Events └── Extra.elm /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build or dist files 2 | /elm-stuff 3 | *~ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: elm 4 | 5 | script: elm make && elm-format --validate src 6 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm-community/html-extra", 4 | "summary": "Additional functions for working with Html", 5 | "license": "MIT", 6 | "version": "3.4.0", 7 | "exposed-modules": [ 8 | "Html.Extra", 9 | "Html.Attributes.Extra", 10 | "Html.Attributes.Autocomplete", 11 | "Html.Events.Extra" 12 | ], 13 | "elm-version": "0.19.0 <= v < 0.20.0", 14 | "dependencies": { 15 | "elm/core": "1.0.0 <= v < 2.0.0", 16 | "elm/html": "1.0.0 <= v < 2.0.0", 17 | "elm/json": "1.0.0 <= v < 2.0.0" 18 | }, 19 | "test-dependencies": {} 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Additional HTML-related Functions 2 | 3 | [](https://travis-ci.org/elm-community/html-extra) 4 | 5 | This package contains convenience functions for working with Html, beyond that 6 | which `elm-lang/html` provides. 7 | 8 | You may want to import this into the `Html` namespace: 9 | 10 | ```elm 11 | import Html.Extra as Html 12 | ``` 13 | 14 | Then you can do things like writing `Html.nothing` instead of `text ""`. 15 | 16 | There are many event handlers & decoders in `Html.Events.Extra`, such as 17 | `targetValueInt` or `onClickPreventDefault`. 18 | 19 | 20 | Feedback and contributions are very welcome. 21 | 22 | ## License 23 | 24 | MIT 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CircuitHub Inc., Elm Community members 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v3.4.0 4 | 5 | * Add an `empty` value to `Html.Attributes.Extra` to represent empty 6 | attributes. Add `attributeIf` & `attributeMaybe` functions for conditional 7 | inclusion of attributes. 8 | * Add a `targetSelectedOptions` decoder to `Html.Events.Extra` for grabbing the 9 | `selectedOptions` values from an event target. 10 | * Add the `onMultiSelect` attribute to `Html.Events.Extra` for handling 11 | `change` events on multiple-choice `select` elements. 12 | 13 | ## v3.3.0 14 | 15 | * Add a new `Html.Attributes.Autocomplete` module with types and helper 16 | functions for use with the new `Html.Attributes.Extre.autocomplete` function. 17 | This function allows consumers to build type-safe `autocomplete` attributes 18 | for `input` & `textarea` elements. 19 | 20 | ## v3.2.0 21 | 22 | * Add the `Html.Extra.viewMaybe` function to render Maybe values into HTML. 23 | 24 | ## v3.1.1 25 | 26 | * Properly expose the `Html.Events.Extra.charCode` decoder. 27 | 28 | ## v3.1.0 29 | 30 | * Add an `onChange` event attribute. 31 | 32 | ## v3.0.0 33 | 34 | * Remove the `innerHtml` Html Attribute since Elm 0.19 no longer supports using 35 | it. You should use ports instead - see https://github.com/elm/html/issues/172 36 | * Add `viewIf` & `viewIfLazy` function to conditionally render HTML. 37 | * Add the `nothing` value to represent an empty HTML node. 38 | * Upgrade library to Elm v0.19 39 | 40 | ## v2.2.0 41 | 42 | * Add onClick event handlers with default action and/or propagation prevented. 43 | * Add onEnter event handler. 44 | -------------------------------------------------------------------------------- /src/Html/Extra.elm: -------------------------------------------------------------------------------- 1 | module Html.Extra exposing (static, nothing, viewIf, viewIfLazy, viewMaybe) 2 | 3 | {-| Convenience functionality on 4 | [`Html`](http://package.elm-lang.org/packages/elm-lang/html/latest/Html#Html) 5 | 6 | @docs static, nothing, viewIf, viewIfLazy, viewMaybe 7 | 8 | -} 9 | 10 | import Html exposing (Html) 11 | 12 | 13 | {-| Embedding static html. 14 | 15 | The type argument 16 | [`Never`](http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#Never) 17 | in `Html Never` tells us that the html has no event handlers attached, 18 | it will not generate any messages. We may want to embed such static 19 | html into arbitrary views, while using types to enforce the 20 | staticness. That is what this function provides. 21 | 22 | _Note:_ To call this function, the argument need not be literally of type 23 | `Html Never`. It suffices if it is a fully polymorphic (in the message type) 24 | `Html` value. For example, this works: `static (Html.text "abcdef")`. 25 | 26 | -} 27 | static : Html Never -> Html msg 28 | static = 29 | Html.map never 30 | 31 | 32 | {-| Render nothing. 33 | 34 | A more idiomatic way of rendering nothing compared to using 35 | `Html.text ""` directly. 36 | 37 | -} 38 | nothing : Html msg 39 | nothing = 40 | Html.text "" 41 | 42 | 43 | {-| A function to only render html under a certain condition 44 | 45 | fieldView : Model -> Html Msg 46 | fieldView model = 47 | div 48 | [] 49 | [ fieldInput model 50 | , viewIf 51 | (not <| List.isEmpty model.errors) 52 | errorsView 53 | ] 54 | 55 | -} 56 | viewIf : Bool -> Html msg -> Html msg 57 | viewIf condition html = 58 | if condition then 59 | html 60 | 61 | else 62 | nothing 63 | 64 | 65 | {-| Just like `viewIf` except its more performant. In viewIf, the html is always evaluated, even if its not rendered. `viewIfLazy` only evaluates your view function if it needs to. The trade off is your view function needs to accept a unit type (`()`) as its final parameter 66 | 67 | fieldView : Model -> Html Msg 68 | fieldView model = 69 | div 70 | [] 71 | [ fieldInput model 72 | , viewIfLazy 73 | (not <| List.isEmpty model.errors) 74 | (\() -> errorsView) 75 | ] 76 | 77 | -} 78 | viewIfLazy : Bool -> (() -> Html msg) -> Html msg 79 | viewIfLazy condition htmlF = 80 | if condition then 81 | htmlF () 82 | 83 | else 84 | nothing 85 | 86 | 87 | {-| Renders `Html.nothing` in case of Nothing, uses the provided function in case of Just. 88 | 89 | viewMaybePost : Maybe Post -> Html Msg 90 | viewMaybePost maybePost = 91 | viewMaybe viewPost maybePost 92 | 93 | viewPost : Post -> Html Msg 94 | 95 | -} 96 | viewMaybe : (a -> Html msg) -> Maybe a -> Html msg 97 | viewMaybe fn maybeThing = 98 | maybeThing 99 | |> Maybe.map fn 100 | |> Maybe.withDefault nothing 101 | -------------------------------------------------------------------------------- /src/Html/Attributes/Extra.elm: -------------------------------------------------------------------------------- 1 | module Html.Attributes.Extra exposing 2 | ( static 3 | , empty, attributeIf, attributeMaybe 4 | , valueAsFloat, valueAsInt, autocomplete 5 | , role 6 | , low, high, optimum 7 | , volume 8 | , stringProperty 9 | , boolProperty 10 | , floatProperty 11 | , intProperty 12 | ) 13 | 14 | {-| Additional attributes for html 15 | 16 | 17 | # Embedding static attributes 18 | 19 | @docs static 20 | 21 | 22 | # Conditional attribute handling 23 | 24 | @docs empty, attributeIf, attributeMaybe 25 | 26 | 27 | # Inputs 28 | 29 | @docs valueAsFloat, valueAsInt, autocomplete 30 | 31 | 32 | # Semantic web 33 | 34 | @docs role 35 | 36 | 37 | # Meter element 38 | 39 | @docs low, high, optimum 40 | 41 | 42 | # Media element 43 | 44 | @docs volume 45 | 46 | 47 | # Custom Attributes 48 | 49 | @docs stringProperty 50 | @docs boolProperty 51 | @docs floatProperty 52 | @docs intProperty 53 | 54 | -} 55 | 56 | import Html exposing (Attribute) 57 | import Html.Attributes exposing (attribute, property) 58 | import Html.Attributes.Autocomplete as Autocomplete 59 | import Json.Encode as Json 60 | 61 | 62 | {-| Embedding static attributes. 63 | 64 | Works alike to [`Html.Extra.static`](Html-Extra#static). 65 | 66 | -} 67 | static : Attribute Never -> Attribute msg 68 | static = 69 | Html.Attributes.map never 70 | 71 | 72 | {-| A no-op attribute. 73 | 74 | Allows for patterns like: 75 | 76 | Html.div 77 | [ someAttr 78 | , if someCondition then 79 | empty 80 | 81 | else 82 | someAttr2 83 | ] 84 | [ someHtml ] 85 | 86 | instead of 87 | 88 | Html.div 89 | (someAttr 90 | :: (if someCondition then 91 | [] 92 | 93 | else 94 | [ someAttr2 ] 95 | ) 96 | ) 97 | [ someHtml ] 98 | 99 | This is useful eg. for conditional event handlers. 100 | 101 | --- 102 | 103 | The only effect it can have on the resulting DOM is adding a `class` attribute, 104 | or adding an extra trailing space in the `class` attribute if added after 105 | `Html.Attribute.class` or `Html.Attribute.classList`: 106 | 107 | -- side effect 1: 108 | --
109 | Html.div [ empty ] [] 110 | 111 | -- side effect 2: 112 | -- 113 | Html.div [ class "x", empty ] [] 114 | 115 | -- no side effect: 116 | -- 117 | Html.div [ empty, class "x" ] [] 118 | 119 | -- side effect 2: 120 | -- 121 | Html.div [ classList [ ( "x", True ) ], empty ] [] 122 | 123 | -- no side effect: 124 | -- 125 | Html.div [ empty, classList [ ( "x", True ) ] ] [] 126 | 127 | -} 128 | empty : Attribute msg 129 | empty = 130 | Html.Attributes.classList [] 131 | 132 | 133 | {-| A function to only render a HTML attribute under a certain condition 134 | -} 135 | attributeIf : Bool -> Attribute msg -> Attribute msg 136 | attributeIf condition attr = 137 | if condition then 138 | attr 139 | 140 | else 141 | empty 142 | 143 | 144 | {-| Renders `empty` attribute in case of Nothing, uses the provided function in case of Just. 145 | -} 146 | attributeMaybe : (a -> Attribute msg) -> Maybe a -> Attribute msg 147 | attributeMaybe fn = 148 | Maybe.map fn >> Maybe.withDefault empty 149 | 150 | 151 | {-| Create arbitrary string _properties_. 152 | -} 153 | stringProperty : String -> String -> Attribute msg 154 | stringProperty name string = 155 | property name (Json.string string) 156 | 157 | 158 | {-| Create arbitrary bool _properties_. 159 | -} 160 | boolProperty : String -> Bool -> Attribute msg 161 | boolProperty name bool = 162 | property name (Json.bool bool) 163 | 164 | 165 | {-| Create arbitrary floating-point _properties_. 166 | -} 167 | floatProperty : String -> Float -> Attribute msg 168 | floatProperty name float = 169 | property name (Json.float float) 170 | 171 | 172 | {-| Create arbitrary integer _properties_. 173 | -} 174 | intProperty : String -> Int -> Attribute msg 175 | intProperty name int = 176 | property name (Json.int int) 177 | 178 | 179 | {-| Uses `valueAsNumber` to update an input with a floating-point value. 180 | This should only be used on <input> of type `number`, `range`, or `date`. 181 | It differs from `value` in that a floating point value will not necessarily overwrite the contents on an input element. 182 | 183 | valueAsFloat 2.5 -- e.g. will not change the displayed value for input showing "2.5000" 184 | 185 | valueAsFloat 0.4 -- e.g. will not change the displayed value for input showing ".4" 186 | 187 | -} 188 | valueAsFloat : Float -> Attribute msg 189 | valueAsFloat value = 190 | floatProperty "valueAsNumber" value 191 | 192 | 193 | {-| Uses `valueAsNumber` to update an input with an integer value. 194 | This should only be used on <input> of type `number`, `range`, or `date`. 195 | It differs from `value` in that an integer value will not necessarily overwrite the contents on an input element. 196 | 197 | valueAsInt 18 -- e.g. will not change the displayed value for input showing "00018" 198 | 199 | -} 200 | valueAsInt : Int -> Attribute msg 201 | valueAsInt value = 202 | intProperty "valueAsNumber" value 203 | 204 | 205 | {-| Render one of the possible `Completion` types into an `Attribute`. 206 | -} 207 | autocomplete : Autocomplete.Completion -> Attribute msg 208 | autocomplete = 209 | Autocomplete.completionValue >> attribute "autocomplete" 210 | 211 | 212 | {-| Used to annotate markup languages with machine-extractable semantic information about the purpose of an element. 213 | See the [official specs](http://www.w3.org/TR/role-attribute/). 214 | -} 215 | role : String -> Attribute msg 216 | role r = 217 | attribute "role" r 218 | 219 | 220 | {-| The upper numeric bound of the low end of the measured range, used with the meter element. 221 | -} 222 | low : String -> Attribute msg 223 | low = 224 | stringProperty "low" 225 | 226 | 227 | {-| The lower numeric bound of the high end of the measured range, used with the meter element. 228 | -} 229 | high : String -> Attribute msg 230 | high = 231 | stringProperty "high" 232 | 233 | 234 | {-| This attribute indicates the optimal numeric value, used with the meter element. 235 | -} 236 | optimum : String -> Attribute msg 237 | optimum = 238 | stringProperty "optimum" 239 | 240 | 241 | {-| Audio volume, starting from 0.0 (silent) up to 1.0 (loudest). 242 | -} 243 | volume : Float -> Attribute msg 244 | volume = 245 | floatProperty "volume" 246 | -------------------------------------------------------------------------------- /src/Html/Attributes/Autocomplete.elm: -------------------------------------------------------------------------------- 1 | module Html.Attributes.Autocomplete exposing 2 | ( Completion(..), completionValue 3 | , DetailedCompletion(..), detailedValue 4 | , ContactType(..), contactTypeValue 5 | , ContactCompletion(..), contactValue 6 | ) 7 | 8 | {-| This module contains types and values you can use to build up a list of 9 | autocomplete tokens for form fields. Simple values like `On` or `Off` are 10 | available, as well as more complex, nestable values: `Detailed <| Section "red" 11 | <| Shipping <| Contact (Just Fax) Telephone`. 12 | 13 | You'll probably want to import this module with an alias: 14 | 15 | import Html exposing (input) 16 | import Html.Attributes.Autocomplete as Autocomplete 17 | import Html.Attributes.Extra exposing (autocomplete) 18 | 19 | myShippingEmailInput = 20 | input 21 | [ autocomplete <| 22 | Autocomplete.Detailed <| 23 | Autocomplete.Shipping <| 24 | Autocomplete.Contact Nothing <| 25 | Autocomplete.Email 26 | ] 27 | [] 28 | 29 | Check out the [HTML Spec][spec] for more information about the `autocomplete` 30 | attribute and the autocomplete detail tokens, such as their meaning, valid 31 | controls, and inputformat. 32 | 33 | [spec]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill 34 | 35 | @docs Completion, completionValue 36 | 37 | @docs DetailedCompletion, detailedValue 38 | 39 | @docs ContactType, contactTypeValue 40 | 41 | @docs ContactCompletion, contactValue 42 | 43 | -} 44 | 45 | 46 | {-| This is the generalized completion value. You explicitly toggle completion 47 | on or off, or use a more complex `DetailedCompletion`. 48 | -} 49 | type Completion 50 | = On 51 | | Off 52 | | Detailed DetailedCompletion 53 | 54 | 55 | {-| Build the attribute value for a `Completion`. 56 | -} 57 | completionValue : Completion -> String 58 | completionValue v = 59 | case v of 60 | On -> 61 | "on" 62 | 63 | Off -> 64 | "off" 65 | 66 | Detailed dv -> 67 | detailedValue dv 68 | |> String.join " " 69 | 70 | 71 | {-| The base type for detailed autocomplete attributes. Some of these are 72 | simple, like `Email`, while some allow nesting of autocomplete tokens, like 73 | `Billing Email`. 74 | -} 75 | type DetailedCompletion 76 | = Section String DetailedCompletion 77 | | Shipping DetailedCompletion 78 | | Billing DetailedCompletion 79 | | Contact (Maybe ContactType) ContactCompletion 80 | | Name 81 | | HonorificPrefix 82 | | GivenName 83 | | AdditionalName 84 | | FamilyName 85 | | HonorificSuffix 86 | | Nickname 87 | | Username 88 | | NewPassword 89 | | CurrentPassword 90 | | OneTimeCode 91 | | OrganizationTitle 92 | | Organization 93 | | StreetAddress 94 | | AddressLine1 95 | | AddressLine2 96 | | AddressLine3 97 | | AddressLevel4 98 | | AddressLevel3 99 | | AddressLevel2 100 | | AddressLevel1 101 | | Country 102 | | CountryName 103 | | PostalCode 104 | | CreditCardName 105 | | CreditCardGivenName 106 | | CreditCardAdditionalName 107 | | CreditCardFamilyName 108 | | CreditCardNumber 109 | | CreditCardExpiration 110 | | CreditCardExpirationMonth 111 | | CreditCardExpirationYear 112 | | CreditCardCSC 113 | | CreditCardType 114 | | TransactionCurrency 115 | | TransactionAmount 116 | | Language 117 | | Birthday 118 | | BirthdayDay 119 | | BirthdayMonth 120 | | BirthdayYear 121 | | Sex 122 | | Url 123 | | Photo 124 | 125 | 126 | {-| Build a list of autocomplete tokens for a `DetailedCompletion`. 127 | -} 128 | detailedValue : DetailedCompletion -> List String 129 | detailedValue v = 130 | case v of 131 | Section name rest -> 132 | ("section-" ++ name) :: detailedValue rest 133 | 134 | Shipping rest -> 135 | "shipping" :: detailedValue rest 136 | 137 | Billing rest -> 138 | "billing" :: detailedValue rest 139 | 140 | Contact maybeType completion -> 141 | List.filterMap identity 142 | [ Maybe.map contactTypeValue maybeType 143 | , Just <| contactValue completion 144 | ] 145 | 146 | Name -> 147 | List.singleton "name" 148 | 149 | HonorificPrefix -> 150 | List.singleton "honorific-prefix" 151 | 152 | GivenName -> 153 | List.singleton "given-name" 154 | 155 | AdditionalName -> 156 | List.singleton "additional-name" 157 | 158 | FamilyName -> 159 | List.singleton "family-name" 160 | 161 | HonorificSuffix -> 162 | List.singleton "honorific-suffix" 163 | 164 | Nickname -> 165 | List.singleton "nickname" 166 | 167 | Username -> 168 | List.singleton "username" 169 | 170 | NewPassword -> 171 | List.singleton "new-password" 172 | 173 | CurrentPassword -> 174 | List.singleton "current-password" 175 | 176 | OneTimeCode -> 177 | List.singleton "one-time-code" 178 | 179 | OrganizationTitle -> 180 | List.singleton "organization-title" 181 | 182 | Organization -> 183 | List.singleton "organization" 184 | 185 | StreetAddress -> 186 | List.singleton "street-address" 187 | 188 | AddressLine1 -> 189 | List.singleton "address-line1" 190 | 191 | AddressLine2 -> 192 | List.singleton "address-line2" 193 | 194 | AddressLine3 -> 195 | List.singleton "address-line3" 196 | 197 | AddressLevel4 -> 198 | List.singleton "address-level4" 199 | 200 | AddressLevel3 -> 201 | List.singleton "address-level3" 202 | 203 | AddressLevel2 -> 204 | List.singleton "address-level2" 205 | 206 | AddressLevel1 -> 207 | List.singleton "address-level1" 208 | 209 | Country -> 210 | List.singleton "country" 211 | 212 | CountryName -> 213 | List.singleton "country-name" 214 | 215 | PostalCode -> 216 | List.singleton "postal-code" 217 | 218 | CreditCardName -> 219 | List.singleton "cc-name" 220 | 221 | CreditCardGivenName -> 222 | List.singleton "cc-given-name" 223 | 224 | CreditCardAdditionalName -> 225 | List.singleton "cc-additional-name" 226 | 227 | CreditCardFamilyName -> 228 | List.singleton "cc-family-name" 229 | 230 | CreditCardNumber -> 231 | List.singleton "cc-number" 232 | 233 | CreditCardExpiration -> 234 | List.singleton "cc-exp" 235 | 236 | CreditCardExpirationMonth -> 237 | List.singleton "cc-exp-month" 238 | 239 | CreditCardExpirationYear -> 240 | List.singleton "cc-exp-year" 241 | 242 | CreditCardCSC -> 243 | List.singleton "cc-csc" 244 | 245 | CreditCardType -> 246 | List.singleton "cc-type" 247 | 248 | TransactionCurrency -> 249 | List.singleton "transaction-currency" 250 | 251 | TransactionAmount -> 252 | List.singleton "transaction-amount" 253 | 254 | Language -> 255 | List.singleton "language" 256 | 257 | Birthday -> 258 | List.singleton "bday" 259 | 260 | BirthdayDay -> 261 | List.singleton "bday-day" 262 | 263 | BirthdayMonth -> 264 | List.singleton "bday-month" 265 | 266 | BirthdayYear -> 267 | List.singleton "bday-year" 268 | 269 | Sex -> 270 | List.singleton "sex" 271 | 272 | Url -> 273 | List.singleton "url" 274 | 275 | Photo -> 276 | List.singleton "photo" 277 | 278 | 279 | {-| The optional contact types a `ContactCompletion` can be tagged with. 280 | -} 281 | type ContactType 282 | = Home 283 | | Work 284 | | Mobile 285 | | Fax 286 | | Pager 287 | 288 | 289 | {-| Transform a ContactType into it's autocomplete detail token. 290 | -} 291 | contactTypeValue : ContactType -> String 292 | contactTypeValue v = 293 | case v of 294 | Home -> 295 | "home" 296 | 297 | Work -> 298 | "work" 299 | 300 | Mobile -> 301 | "mobile" 302 | 303 | Fax -> 304 | "fax" 305 | 306 | Pager -> 307 | "pager" 308 | 309 | 310 | {-| Autocomplete tokens with the ability to be tagged with a `ContactType`. 311 | -} 312 | type ContactCompletion 313 | = Telephone 314 | | TelephoneCountryCode 315 | | TelephoneNational 316 | | TelephoneAreaCode 317 | | TelephoneLocal 318 | | TelephoneLocalPrefix 319 | | TelephoneLocalSuffix 320 | | TelephoneExtension 321 | | Email 322 | | IMPP 323 | 324 | 325 | {-| Transform a ContactCompletion into it's autocomplete detail token. 326 | -} 327 | contactValue : ContactCompletion -> String 328 | contactValue v = 329 | case v of 330 | Telephone -> 331 | "tel" 332 | 333 | TelephoneCountryCode -> 334 | "tel-country-code" 335 | 336 | TelephoneNational -> 337 | "tel-national" 338 | 339 | TelephoneAreaCode -> 340 | "tel-area-code" 341 | 342 | TelephoneLocal -> 343 | "tel-local" 344 | 345 | TelephoneLocalPrefix -> 346 | "tel-local-prefix" 347 | 348 | TelephoneLocalSuffix -> 349 | "tel-local-suffix" 350 | 351 | TelephoneExtension -> 352 | "tel-extension" 353 | 354 | Email -> 355 | "email" 356 | 357 | IMPP -> 358 | "impp" 359 | -------------------------------------------------------------------------------- /src/Html/Events/Extra.elm: -------------------------------------------------------------------------------- 1 | module Html.Events.Extra exposing 2 | ( charCode 3 | , targetValueFloat, targetValueInt, targetValueMaybe, targetValueMaybeFloat, targetValueMaybeInt 4 | , targetValueFloatParse, targetValueIntParse, targetValueMaybeFloatParse, targetValueMaybeIntParse 5 | , targetSelectedIndex, targetSelectedOptions 6 | , onClickPreventDefault, onClickStopPropagation, onClickPreventDefaultAndStopPropagation, onEnter, onChange, onMultiSelect 7 | ) 8 | 9 | {-| Additional decoders for use with event handlers in html. 10 | 11 | 12 | # Event decoders 13 | 14 | - TODO: `key` 15 | 16 | - TODO: `code` 17 | 18 | - TODO: `KeyEvent`, `keyEvent` 19 | 20 | @docs charCode 21 | 22 | 23 | # Typed event decoders 24 | 25 | @docs targetValueFloat, targetValueInt, targetValueMaybe, targetValueMaybeFloat, targetValueMaybeInt 26 | @docs targetValueFloatParse, targetValueIntParse, targetValueMaybeFloatParse, targetValueMaybeIntParse 27 | @docs targetSelectedIndex, targetSelectedOptions 28 | 29 | 30 | # Event Handlers 31 | 32 | @docs onClickPreventDefault, onClickStopPropagation, onClickPreventDefaultAndStopPropagation, onEnter, onChange, onMultiSelect 33 | 34 | -} 35 | 36 | import Html exposing (Attribute) 37 | import Html.Events exposing (..) 38 | import Json.Decode as Json 39 | 40 | 41 | 42 | -- TODO 43 | -- {-| Decode the key that was pressed. 44 | -- The key attribute is intended for users who are interested in the meaning of the key being pressed, taking into account the current keyboard layout. 45 | -- 46 | -- * If there exists an appropriate character in the [key values set](http://www.w3.org/TR/DOM-Level-3-Events-key/#key-value-tables), this will be the result. See also [MDN key values](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key#Key_values). 47 | -- * If the value is has a printed representation, it will be a non-empty Unicode character string consisting of the char value. 48 | -- * If more than one key is being pressed and the key combination includes modifier keys (e.g. `Control + a`), then the key value will still consist of the printable char value with no modifier keys except for 'Shift' and 'AltGr' applied. 49 | -- * Otherwise the value will be `"Unidentified"` 50 | -- 51 | -- Note that `keyCode`, `charCode` and `which` are all being deprecated. You should avoid using these in favour of `key` and `code`. 52 | -- Google Chrome and Safari currently support this as `keyIdentifier` which is defined in the old draft of DOM Level 3 Events. 53 | -- 54 | -- -} 55 | -- key : Json.Decoder String 56 | -- key = Json.oneOf [ Json.field "key" string, Json.field "keyIdentifier" string ] 57 | -- TODO: Waiting for proper support in chrome & safari 58 | -- {-| Return a string identifying the key that was pressed. 59 | -- `keyCode`, `charCode` and `which` are all being deprecated. You should avoid using these in favour of `key` and `code`. 60 | -- See [KeyboardEvent.keyCode](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode). 61 | -- -} 62 | -- code : Json.Decoder String 63 | -- code = 64 | -- Json.field "code" string 65 | -- TODO: Complete keyboard event 66 | -- keyEvent : Json.Decoder KeyEvent 67 | -- keyEvent = 68 | -- Json.oneOf [ Json.field "keyCode" int ] 69 | 70 | 71 | {-| Character code for key board events. 72 | This is being deprecated, but support for DOM3 Keyboard events is not yet present in most browsers. 73 | -} 74 | charCode : Json.Decoder (Maybe Char) 75 | charCode = 76 | Json.map (Maybe.map Tuple.first << String.uncons) (Json.field "charCode" Json.string) 77 | 78 | 79 | 80 | -- implementation taken from: https://groups.google.com/d/msg/elm-dev/Ctl_kSKJuYc/rjkdBxx6AwAJ 81 | 82 | 83 | customDecoder : Json.Decoder a -> (a -> Result String b) -> Json.Decoder b 84 | customDecoder d f = 85 | let 86 | resultDecoder x = 87 | case x of 88 | Ok a -> 89 | Json.succeed a 90 | 91 | Err e -> 92 | Json.fail e 93 | in 94 | Json.map f d |> Json.andThen resultDecoder 95 | 96 | 97 | maybeStringToResult : Maybe a -> Result String a 98 | maybeStringToResult = 99 | Result.fromMaybe "could not convert string" 100 | 101 | 102 | traverse : (String -> Maybe a) -> Maybe String -> Result String (Maybe a) 103 | traverse f mx = 104 | case mx of 105 | Nothing -> 106 | Ok Nothing 107 | 108 | Just x -> 109 | x |> f |> maybeStringToResult |> Result.map Just 110 | 111 | 112 | {-| Floating-point target value. 113 | -} 114 | targetValueFloat : Json.Decoder Float 115 | targetValueFloat = 116 | customDecoder (Json.at [ "target", "valueAsNumber" ] Json.float) <| 117 | \v -> 118 | if isNaN v then 119 | Err "Not a number" 120 | 121 | else 122 | Ok v 123 | 124 | 125 | {-| Integer target value. 126 | -} 127 | targetValueInt : Json.Decoder Int 128 | targetValueInt = 129 | Json.at [ "target", "valueAsNumber" ] Json.int 130 | 131 | 132 | {-| String or empty target value. 133 | -} 134 | targetValueMaybe : Json.Decoder (Maybe String) 135 | targetValueMaybe = 136 | customDecoder targetValue 137 | (\s -> 138 | Ok <| 139 | if s == "" then 140 | Nothing 141 | 142 | else 143 | Just s 144 | ) 145 | 146 | 147 | {-| Floating-point or empty target value. 148 | -} 149 | targetValueMaybeFloat : Json.Decoder (Maybe Float) 150 | targetValueMaybeFloat = 151 | targetValueMaybe 152 | |> Json.andThen 153 | (\mval -> 154 | case mval of 155 | Nothing -> 156 | Json.succeed Nothing 157 | 158 | Just _ -> 159 | Json.map Just targetValueFloat 160 | ) 161 | 162 | 163 | {-| Integer or empty target value. 164 | -} 165 | targetValueMaybeInt : Json.Decoder (Maybe Int) 166 | targetValueMaybeInt = 167 | customDecoder targetValueMaybe (traverse String.toInt) 168 | 169 | 170 | {-| Parse a floating-point value from the input instead of using `valueAsNumber`. 171 | Use this with inputs that do not have a `number` type. 172 | -} 173 | targetValueFloatParse : Json.Decoder Float 174 | targetValueFloatParse = 175 | customDecoder targetValue (String.toFloat >> maybeStringToResult) 176 | 177 | 178 | {-| Parse an integer value from the input instead of using `valueAsNumber`. 179 | Use this with inputs that do not have a `number` type. 180 | -} 181 | targetValueIntParse : Json.Decoder Int 182 | targetValueIntParse = 183 | customDecoder targetValue (String.toInt >> maybeStringToResult) 184 | 185 | 186 | {-| Parse an optional floating-point value from the input instead of using `valueAsNumber`. 187 | Use this with inputs that do not have a `number` type. 188 | -} 189 | targetValueMaybeFloatParse : Json.Decoder (Maybe Float) 190 | targetValueMaybeFloatParse = 191 | customDecoder targetValueMaybe (traverse String.toFloat) 192 | 193 | 194 | {-| Parse an optional integer value from the input instead of using `valueAsNumber`. 195 | Use this with inputs that do not have a `number` type. 196 | -} 197 | targetValueMaybeIntParse : Json.Decoder (Maybe Int) 198 | targetValueMaybeIntParse = 199 | customDecoder targetValueMaybe (traverse String.toInt) 200 | 201 | 202 | {-| Parse the index of the selected option of a select. 203 | Returns Nothing in place of the spec's magic value -1. 204 | -} 205 | targetSelectedIndex : Json.Decoder (Maybe Int) 206 | targetSelectedIndex = 207 | Json.at [ "target", "selectedIndex" ] 208 | (Json.map 209 | (\int -> 210 | if int == -1 then 211 | Nothing 212 | 213 | else 214 | Just int 215 | ) 216 | Json.int 217 | ) 218 | 219 | 220 | {-| Parse `event.target.selectedOptions` and return option values. 221 | -} 222 | targetSelectedOptions : Json.Decoder (List String) 223 | targetSelectedOptions = 224 | let 225 | options = 226 | Json.at [ "target", "selectedOptions" ] <| 227 | Json.keyValuePairs <| 228 | Json.field "value" Json.string 229 | in 230 | Json.map (List.map Tuple.second) options 231 | 232 | 233 | 234 | -- Event Handlers 235 | 236 | 237 | {-| Always send `msg` upon click, preventing the browser's default behavior. 238 | -} 239 | onClickPreventDefault : msg -> Attribute msg 240 | onClickPreventDefault msg = 241 | preventDefaultOn "click" <| Json.succeed ( msg, True ) 242 | 243 | 244 | {-| Always send `msg` upon click, preventing click propagation. 245 | -} 246 | onClickStopPropagation : msg -> Attribute msg 247 | onClickStopPropagation msg = 248 | stopPropagationOn "click" <| Json.succeed ( msg, True ) 249 | 250 | 251 | {-| Always send `msg` upon click, preventing the browser's default behavior and propagation 252 | -} 253 | onClickPreventDefaultAndStopPropagation : msg -> Attribute msg 254 | onClickPreventDefaultAndStopPropagation msg = 255 | custom "click" (Json.succeed { message = msg, stopPropagation = True, preventDefault = True }) 256 | 257 | 258 | {-| When the enter key is released, send the `msg`. 259 | Otherwise, do nothing. 260 | -} 261 | onEnter : msg -> Attribute msg 262 | onEnter onEnterAction = 263 | on "keyup" <| 264 | Json.andThen 265 | (\keyCode -> 266 | if keyCode == 13 then 267 | Json.succeed onEnterAction 268 | 269 | else 270 | Json.fail (String.fromInt keyCode) 271 | ) 272 | keyCode 273 | 274 | 275 | {-| An HTML Event attribute that passes the `event.target.value` to a `msg` 276 | constructor when an `input`, `select`, or `textarea` element has changed. 277 | -} 278 | onChange : (String -> msg) -> Attribute msg 279 | onChange onChangeAction = 280 | on "change" <| Json.map onChangeAction targetValue 281 | 282 | 283 | {-| Detect change events on multi-choice select elements. 284 | 285 | It will grab the string values of `event.target.selectedOptions` on any change 286 | event. 287 | 288 | Check out [`targetSelectedOptions`](#targetSelectedOptions) for more details on 289 | how this works. 290 | 291 | Note: [`onChange`](#onChange) parses `event.target.value` that doesn't work with 292 | multi-choice select elements. 293 | 294 | Note 2: 295 | [`selectedOptions`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions) 296 | is not supported by Internet Explorer. 297 | 298 | -} 299 | onMultiSelect : (List String -> msg) -> Attribute msg 300 | onMultiSelect tagger = 301 | stopPropagationOn "change" <| 302 | Json.map (\x -> ( x, True )) <| 303 | Json.map tagger targetSelectedOptions 304 | --------------------------------------------------------------------------------