Skip to content

Commit

Permalink
Initial commit to this repository
Browse files Browse the repository at this point in the history
**NB!** Most of the development of this module and its
related usage has occurred in elm-mdl. Particularly
debois/elm-mdl#179

Special thanks to @debois for contributing and helping with the
development of this package
  • Loading branch information
vipentti committed Sep 8, 2016
0 parents commit 14f2b16
Show file tree
Hide file tree
Showing 8 changed files with 726 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
elm-stuff
.*.sw?
*~
elm.js
index.html
docs.json
documentation.json
.vscode/
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Copyright (c) 2016, Ville Penttinen
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the elm-dispatch nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# elm-dispatch

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

I can't find it again, but I'm pretty certain there was a guideline that packages should not begin with elm-. It's too late for elm-mdl, but dispatch is pretty cool on its own?


Makes it easier to dispatch multiple messages from a single `Html.Event`.

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

"Dispatch multiple messages in response to a single Html.Event."

("Makes it easier" is too vague for my taste.)


The library was developed for the purpose of allowing UI component libraries, such as [elm-mdl](http://package.elm-lang.org/packages/debois/elm-mdl/latest), to have stateful components that perform some internal actions on `Html.Events` such as `click`, `focus` and `blur` while still allowing users to have their own event handlers for those particular events as well.

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

More to the point:

"UI libraries in elm sometimes need to perform internal action on Html.Events such as onFocus, while at the same time allowing library clients to also respond to those events. This library provides such multiple dispatch.


This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

The above (headline, first two paragraphs) is what github displays on mobile, so it should preferably (1) say exactly what the library does and (2) say all the positive things you can think of.

At this point, which users read only after clicking "View all of README.md", first say that the library is a "central component in" or has "proven useful in" elm-mdl so users know this is already used in a major elm library.

Then explain in a little more detail why you need this. Here' a rough outline of how I'd do it:

  1. Setup the problem: My button needs onMouseLeave, but user also needs onMouseLeave. (We want to be concrete.)
  2. Can't just merge together attributes, last handler wins.
  3. Can't just write special onMouseLeave option which dispatches a message using OutMsg or takes a Msg argument, because pretty soon we're wrapping all of Html.Events for every component in the library. This happened in elm-mdl and was the original motivation for dispatch.
  4. So, Dispatch, a library for combining Decoders.

You may want to put all this in a subsection such as ## Motivation or ## Why?


## Install

```shell
elm package install vipentti/elm-dispatch
```

## Examples

To see the library in action see [elm-mdl](http://package.elm-lang.org/packages/debois/elm-mdl/latest) specifically [Material.Options.Internal](https://github.com/debois/elm-mdl/blob/master/src/Material/Options/Internal.elm).

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Invert this: The elm-mdl usage of Dispatch is beautiful but subtle and complex, and also not an easy to follow example because it is inextricably linked with the elm-mdl Option mechanics.

I suggest putting the example in the repo front and center, then adding a link to elm-mdl as an afterthought. Also, link a specific commit; Internal.elm might go away in master someday.


An example may also be found in `examples/`

## Basic Usage

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

You want to move this into an actual example and remove it from README.me. Refer to examples/ instead.

Dispatch has two uses: Provide multiple dispatch or a single event (this example) and writing UI library APIs (example1.elm). This example is the less interesting case, since for this particular example it'd likely be easier to just do it all in update.


To add support for Dispatch:

Add a dispatch message to your `Msg`
```elm
type Msg
= ...
| Dispatch (List Msg)
...
```

Add call to `Dispatch.forward` in update
```elm
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
...

Dispatch messages ->
model ! [ Dispatch.forward messages ]

...
```

Add a call to `Dispatch.on` on an element
```elm
view : Model -> Html Msg
view model =
let
decoders =
[ Json.Decode.succeed Click
, Json.Decode.succeed PerformAnalytics
, Json.Decode.map SomeMessage

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

import Json.Decode as Json to make this simpler. (I like as Decode better, but nobody else seems to do that.)

Also, maybe MeasureWidth rather than SomeMessage?

(Json.at ["target", "offsetWidth"] Json.float) ]
in
Html.button
[ Dispatch.on "click" Dispatch decoders ]
[ text "Button" ]
```

For more advanced use see `examples/`.
18 changes: 18 additions & 0 deletions elm-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "1.0.0",
"summary": "Allow for dispatching multiple messages from a single Html event",

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

"Dispatch multiple messages in response to a single Html.Event."

(Or whatever, but make it the exact same as the first paragraph in README.md.)

"repository": "https://github.com/vipentti/elm-dispatch.git",
"license": "BSD3",

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Apache?

"source-directories": [
"src",
"examples"
],
"exposed-modules": [
"Dispatch"
],
"dependencies": {
"elm-lang/core": "4.0.5 <= v < 5.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0"
},
"elm-version": "0.17.1 <= v < 0.18.0"
}
206 changes: 206 additions & 0 deletions examples/FancyButton.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
module FancyButton
exposing
( Msg
, Model
, Property
, model
, update
, view
, clickCount
, any
, on
, on1
, onClick
)

import Html exposing (..)
import Html.Attributes exposing (style)
import Dispatch
import Json.Decode as Json


-- MODEL
{-| Model is opaque as it contains internal state.
-}
type Model
= Model
{ focused : Bool
, clickCount : Int

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Consider dropping the clickCount, the focused bit is enough.

}

{-| Initialize the model
-}
model : Model
model =
Model
{ focused = False
, clickCount = 0
}


{-| Utility function to access some internal state
-}
clickCount : Model -> Int
clickCount (Model { clickCount }) =
clickCount



-- Properties

{-| FancyButton only accepts specific
types of properties
-}
type Property msg

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Maybe call it Option, which is slightly more descriptive.

You need to someplace explain why this is necessary. I can tell that it's like a Material.Option.Property super-light, but I don't think that trick is generally recognised. So you'll have to say that since we can't take attributes directly, we'll need someway to distinguish event handlers and attributes.

Come to think of it (I'm writing this after the suggestions for FancyButton.view), maybe just give up and supply handlers as a separate argument:

type alias Handler = 
  Decoder String (Json.Decoder msg)

view : (Msg msg -> msg) -> Model -> List (Handler msg) -> List (Attribute msg) -> List (Html msg) -> Html msg

Yeah, this is better: no point in Dispatch taking a stance on how options should be supplied.

= Decoder String (Json.Decoder msg)
| Any (Html.Attribute msg)


{-| Add an `Html.Event` handler
-}
on : String -> Json.Decoder msg -> Property msg

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Examples should be as short as possible. Lose on and onClick.

on =
Decoder


{-| Add an `Html.Event` handler.
Equivalent to `FancyButton.on "event" (Json.succeed msg)`
-}
on1 : String -> msg -> Property msg
on1 evt msg =
on evt (Json.succeed msg)


{-| Add an onClick handler.
-}
onClick : msg -> Property msg
onClick msg =
on1 "click" msg


{-| Map from Html.Attribute to a FancyButton.Property
-}
any : Html.Attribute msg -> Property msg
any =
Any



-- UPDATE


type Msg msg
= Click
| Focus
| Blur
{- This message tells Dispatch how to
convert a list of messages to a single message
-}

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

I don't think a comment is necessary here. The one you've got is slightly confusing. (It's a message, and Dispatch doesn't know about it.)

Also, consider using a different name. The module is already called Dispatch, which might confuse someone. Events, Forward, or Batch, maybe?

| Dispatch (List msg)


update : Msg msg -> Model -> ( Model, Cmd msg )
update msg (Model model) =
case msg of
{- Forward all the messages produced by handlers with multiple decoders
attached to them
-}
Dispatch msg' ->
Model model ! [ Dispatch.forward msg' ]

Click ->
Model { model | clickCount = model.clickCount + 1 } ! []

Focus ->
Model { model | focused = True } ! []

Blur ->
Model { model | focused = False } ! []



-- VIEW


view : (Msg msg -> msg) -> Model -> List (Property msg) -> List (Html msg) -> Html msg
view lift (Model model) props content =
let
{- We want to perform internal actions on these events

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Good.

-}
defaultListeners =
[ on1 "mouseenter" (lift Focus)
, on1 "mouseleave" (lift Blur)
, on1 "click" (lift Click)
]

{- Setup the Dispatch configuration using user provided events as well as

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

"user-provided"

our own internal events

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Also, maybe be precise and say "event-handlers" rather than "events".

-}
config =

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

MAYBE IGNORE, SEE ABOVE

The config and attributes definitions looks difficult on first glance. I'd suggest factoring them out in a collect or attributes function, which users can copy-paste into their own code if they think its a good idea.

attributes : List (Property msg) -> List (Attributes msg)
attributes props = 
  let (attrs, handlers) = 
    List.foldl toAttr 
      ( [], (Dispatch.setMsg (Dispatch >> lift) Dispatch.defaultConfig) )
      props
  in
    attrs ++ (Dispatch.toAttributes handlers)    -- (Production code would avoid the `++`)


toAttr prop (attrs, handlers) = 
  case prop of 
    Decoder evt d -> 
      (attrs, Dispatch.add evt Nothing d handlers)

    Any attr -> 
      (attr :: attrs, handlers)
List.foldl
(\prop acc ->
case prop of
Decoder evt d ->
Dispatch.add evt Nothing d acc

Any attribute ->
acc
)
(Dispatch.setMsg (Dispatch >> lift) Dispatch.defaultConfig)
(props ++ defaultListeners)

{- Don't add listeners here,
they are already added in the config
-}
attributes =
List.map
(\prop ->
case prop of
Decoder _ _ ->
Nothing

Any a ->
Just a
)
props
|> List.filterMap identity
in
button
([ normal
, if model.focused then
focused
else
style []
]
++ attributes
++ (Dispatch.toAttributes config)
)
content



-- STYLES


normal : Attribute a
normal =
style
[ ( "display", "inline-block" )
, ( "margin", "0 10px 0 0" )
, ( "padding", "15px 15px" )
, ( "font-size", "16px" )
, ( "line-height", "1.8" )
, ( "appearance", "none" )
, ( "box-shadow", "none" )
, ( "border-radius", "0" )
]


focused : Attribute a

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Maybe hover? We're not actually focused.

Also, to make this example not doable exclusively by CSS, consider changing the color a bit on each hover. (So everytime you hover, the button background turns a bit darker.)

focused =
style
[ ( "background-color", "#b6d8e4" )
, ( "text-shadow", "-1px 1px #27496d" )
, ( "outline", "none" )

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Maybe leave out the change in outline, which makes the surrounding text jump on hover.

]
16 changes: 16 additions & 0 deletions examples/elm-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "1.0.0",
"summary": "elm-dispatch examples",
"repository": "https://github.com/vipentti/elm-dispatch.git",
"license": "BSD3",

This comment has been minimized.

Copy link
@debois

debois Sep 9, 2016

Contributor

Apache?

"source-directories": [
".",
"../src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "4.0.5 <= v < 5.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0"
},
"elm-version": "0.17.1 <= v < 0.18.0"
}
Loading

0 comments on commit 14f2b16

Please sign in to comment.