Elm How to make custom event decoder to get mouse x/y position at mouse-wheel-move - dom

I am trying to get the x and y coordinates of the mouse during a mouse-wheel-move event in the Elm 0.19 programming language.
I attempt it with this package. See under "Advanced Usage":
https://package.elm-lang.org/packages/mpizenberg/elm-pointer-events/3.1.0/Html-Events-Extra-Wheel
The package itself did not describe a clear example so I looked for an example in a similar package.
See the example under "advanced usage" in this page:
https://package.elm-lang.org/packages/mpizenberg/elm-pointer-events/3.1.0/Html-Events-Extra-Mouse
This example is very similar to what I need, but I can also not get this to work. Get exactly the same problem.
Here is my code adapted from the example to fit with mouse wheel:
module WheelDecoder exposing(..)
import Html exposing (div, text)
import Html.Events.Extra.Wheel as Wheel
import Json.Decode as Decode
type alias WheelEventWithOffsetXY =
{ wheelEvent : Wheel.Event
, offsetXY : {x: Float, y: Float}
}
decodeWeelWithOffsetXY : Decode.Decoder WheelEventWithOffsetXY
decodeWeelWithOffsetXY =
Decode.map2 WheelEventWithOffsetXY
Wheel.eventDecoder
offsetXYDecoder
offsetXYDecoder : Decode.Decoder {x: Float, y: Float}
offsetXYDecoder =
Decode.map2 (\a b -> {x=a,y=b})
(Decode.field "offsetY" Decode.float)
(Decode.field "offsetY" Decode.float)
type Msg
= WheelOffsetXY {x: Float, y: Float}
view =
div
[ (onWheelOffsetXY (\wheelEvent -> WheelOffsetXY (wheelEvent.offsetXY))) ]
[ (text "mousewheel here") ]
onWheelOffsetXY : (WheelEventWithOffsetXY -> msg) -> Html.Attribute msg
onWheelOffsetXY tag =
let
options = { stopPropagation = True, preventDefault = True }
func = Decode.map tag decodeWeelWithOffsetXY
attribute = Wheel.onWithOptions options func
in
attribute
When I try to compile with "elm make" I get the following error:
-- TYPE MISMATCH -------------------------------------- src/Map/WheelDecoder.elm
The 2nd argument to `onWithOptions` is not what I expect:
39| attribute = Wheel.onWithOptions options func
^^^^
This `func` value is a:
Decode.Decoder msg
But `onWithOptions` needs the 2nd argument to be:
Wheel.Event -> msg
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
This error message makes sense as I can see there is a type mismatch, but I have no clue about how to solve it.

It seems like Wheel.eventDecoder was meant to work with Html.Events.on or Html.Events.onWithOptions rather than Wheel.onWithOptions. These were removed in 0.19 in favor of Html.Events.custom, however, which is slightly different. Replacing onWheelOffsetXY with this seems to work:
onWheelOffsetXY : (WheelEventWithOffsetXY -> msg) -> Html.Attribute msg
onWheelOffsetXY tag =
let
options message =
{ message = message
, stopPropagation = True
, preventDefault = True
}
decoder =
decodeWeelWithOffsetXY
|> Decode.map tag
|> Decode.map options
in
Html.Events.custom "wheel" decoder
PS: There's a typo in decodeWeelWithOffsetXY, btw. I've left the typo in place.
PPS: Also, you're looking at outdated documentation. Here's the documentation for the latest version.

Related

Receiving signals from a PureScript Flame application

I'm experimenting to understand PureScript channels and signals better, and specifically their use with the UI library Flame.
I've created the following as a small example:
module Main where
import Prelude
import Effect (Effect)
import Flame (Html, QuerySelector(..))
import Flame.Application.NoEffects as FAN
import Flame.HTML.Attribute as HA
import Flame.HTML.Element as HE
data Message
= ButtonOne
| ButtonTwo
type Model
= { buttonOneCount :: Int
, buttonTwoCount :: Int
}
init :: Model
init =
{ buttonOneCount: 0
, buttonTwoCount: 0
}
update :: Model -> Message -> Model
update model message = case message of
ButtonOne -> model { buttonOneCount = model.buttonOneCount + 1 }
ButtonTwo -> model { buttonTwoCount = model.buttonTwoCount + 1 }
view :: Model -> Html Message
view model =
HE.main "main"
[ HE.button [ HA.onClick ButtonOne ] "Button One"
, HE.p_ ("button one: " <> show model.buttonOneCount)
, HE.button [ HA.onClick ButtonTwo ] "Button Two"
, HE.p_ ("button two: " <> show model.buttonTwoCount)
]
main :: Effect Unit
main = do
-- flameChannel :: Channel (Array Message)
flameChannel <-
FAN.mount (QuerySelector "main")
{ init, update, view }
pure unit
I'm aware that any changes should be handled in update here, but as an experiment I'd like to subscribe to changes to flameChannel in main and run some code based on the Message Flame sends through that channel.
Just to get started, what could be added to make this output to the JavaScript console when the user clicks Button One or Button Two, using flameChannel in main?
I've tried playing around with various combinations of functions from purescript-signal: subscribe, runSignal, get, but got nowhere. I have also looked around at various games on GitHub that use the purescript-signal library, but they just seem to throw runSignal into the code, everything works and I don't understand why (also, those examples are a bit complex and far from what I'm trying to do).
ok let's see - as you've already noticed you get a Channel (from signal) back that will notify you about Messages
you should now be able to use Signal.Channel.subscribe to turn this into a Signal
Now the way Signal works is that you should provide a Signal signaling Effects (those will be executed/run) to runSignal - so you have to turn your Signal (Array Message) into a Signal (Effect Unit) first.
Luckily Signal is a Functor so you can use map or the provided ~> (flipped map) to do this. I would suggest using flattenArray first to make your life a bit easier:
logMessage :: Message -> Effect Unit
logMessage msg =
case msg of
ButtonOne -> log "button one"
ButtonTwo -> log "button two"
and in main:
main = do
flameChannel <-
FAN.mount (QuerySelector "main")
{ init, update, view }
let logSignal =
map logMessage $ flip flattenArray ButtonOne $ subscribe flameChannel
runSignal logSignal
this should work
disclaimer: I did not try to compile this - if you get any issues please report and I'll see to it

Current year with 4 digits in elm 0.19.1

How can I do a function to get the current year with 4 digits using ELM 0.19.1? I have read something but nothing works with 0.19.1.
Signature:
getCurrentYear : Int
Execution:
getCurrentYear => 2020
Edit:
Maybe executing new Date().getFullYear() javascript code?
The simplest way would be to pass the year in via flags when you start the app, since the current year isn't likely to change in the course of the application running. In that case, you can use the snippet of JavaScript you suggested (ellie example):
Elm.Main.init({
node: document.querySelector('main'),
flags: {
year: new Date().getFullYear(),
}
});
module Main exposing (main)
import Browser
import Html exposing (Html, p, text)
type alias Flags =
{ year : Int }
main : Program Flags Model Msg
main =
Browser.element
{ init = \flags -> ( Model flags.year, Cmd.none )
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
type alias Model =
{ year : Int }
type Msg
= NoOp
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
view : Model -> Html Msg
view model =
p [] [ text "The year is ", text (String.fromInt model.year) ]
Alternatively, you can use Time.now to request the current time, as Robin Zigmond's answer suggests, however that is pointing to Elm 0.18 documentation (for elm-lang/core instead of elm/time). For 0.19, you need both a Time.Posix and a Time.Zone in order to call Time.toYear. You can chain Time.now (a Task producing a Posix value) and Time.here (a Task producing a Zone with the current time zone offset) to retrieve those values in one Cmd. Here's an example (also on ellie)
module Main exposing (main)
import Browser
import Html exposing (Html, p, text)
import Task exposing (Task)
import Time
type alias Flags =
{ year : Int }
main : Program () Model Msg
main =
Browser.element
{ init = \() -> ( Model 0, whatYearIsIt |> Task.perform GotYear )
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
whatYearIsIt : Task x Int
whatYearIsIt =
Task.map2 Time.toYear Time.here Time.now
type alias Model =
{ year : Int }
type Msg
= GotYear Int
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotYear year ->
( { model | year = year }, Cmd.none )
view : Model -> Html Msg
view model =
p [] [ text "The year is ", text (String.fromInt model.year) ]
As I already said in my comment, it's impossible to define a function in Elm that returns the current year. You have to get such information from the Elm runtime system (which is basically JavaScript, but you don't have to write it yourself). This happens via commands, where you tell the runtime system to do something for you. But note that you can't simply retrieve the "return value" of that command and get it back into your Elm code. Instead you have to pass it into a function that can convert it into a "message" (see basic Elm Architecture tutorial here, it's fundamental to understand this before you can do anything with Elm) - this then allows you to store the value in your Model and thereby display it in your app.
These patterns do take some getting your head around, especially if you're not used to pure functional programming - but once you get used to it the benefits are huge, including a near guaranteed absence of runtime errors, and greatly enhanced ability to reason about your code.
For getting the year specifically, it looks like you need this library, which gives you (as now) a Task rather than a Cmd. You can use Task.perform to convert it to a command, which is documented here - in fact it even gives an example that matches your use case quite closely - I'll copy it here for posterity:
import Time -- elm install elm/time
import Task
type Msg
= Click
| Search String
| NewTime Time.Posix
getNewTime : Cmd Msg
getNewTime =
Task.perform NewTime Time.now
You'll have to fill this in to fit your own use case, in particular your own Msg type. But it gives a good basic outline. To get the user's current year, you need to replace the Time.Posix type with Int, and the Time.now command with (Task.map2 Time.toYear Time.here Time.now), as explained by #bdukes in his answer.

how to use prevent default on Elm Browser.Event.onKeyDown

Please Note: I could not a find a solution that could work here, so I used ports for the same.
I need to use Browser.Events.onKeyDown for which I have also written a decoder, I need to create some shortcuts for my web app thus I need to prevent default behavior of Meta key (on Mac) and Ctrl key (on other Os)
In my Subscription method I am using the following.
But there is no exposed way of using prevent Default.
let
decoder : Decode.Decoder Msg
decoder =
keyDecoder
|> Decode.andThen
(\( keyCode, ctrlKey ) ->
case keyCode of
39 ->
Decode.succeed <| ShortCutNext
37 ->
Decode.succeed <| ShortCutPrevious
_ ->
Decode.fail ""
)
in
Sub.batch
[ Browser.Events.onKeyDown decoder]
keyDecoder : Decode.Decoder ( Int, Bool )
keyDecoder =
Decode.map2 (\a -> \b -> ( a, b ))
(Decode.field "keyCode" Decode.int)
(Decode.field "metaKey" Decode.bool)
Note: The event is on the page itself and not on any element such as textarea thus Html.Events.custom "keydown" options decoder is not applicable.

How to initialize the model using Window.dimensions in Elm?

In a sliding puzzle game, I'd like set the initial tile size based on the initial window dimensions (to maximize the screen real estate).
In other words, I'd like to set initialModel based on the the initial value of Window.dimensions.
I couldn't find how to do this, and ended up using ports to get the initial window dimensions:
index.html
Elm.fullscreen(Elm.App, {
windowSize: [
document.documentElement.clientWidth,
document.documentElement.clientHeight
]
});
App.elm
port windowSize : (Int, Int)
initialModel =
-- some function of windowSize
model =
Signal.foldp update initialModel input
type Action
= WindowResize (Int, Int)
| ...
windowResize =
Signal.map WindowResize Window.dimensions
update action model =
case action of
WindowResize dimensions ->
{ model | some change based on dimensions }
...
Is there a way to achieve the same result without using ports?
You can use Signal.Extra.foldp' from the Apanatshka/elm-signal-extra package to inspect base the initial value of the model on the initial value of the input signal.
Full disclosure: I'm the author of that package.

Hiding Internal State of CoffeeScript Object

Looking at the following from CoffeeScript Ristretto:
QueueMaker = ->
do (queue = undefined) ->
array: []
head: 0
tail: -1
pushTail: (value) ->
queue.array[tail += 1] = value
pullHead: ->
unless queue.isEmpty()
do (value = queue.array[queue.head]) ->
queue.array[queue.head] = undefined
queue.head += 1
value
isEmpty: ->
queue.tail < queue.head
It's possible to mutate queue.head - http://jsfiddle.net/VQLNG/.
queue = QueueMaker()
queue.head = 666
console.log queue
How can I write the above function so that head isn't public?
JavaScript doesn't have private properties so CoffeeScript doesn't have them either.
However, you can simulate private properties in many cases by using function scopes to hide things and closures to access the hidden things.
A simple stack implementation should demonstrate the technique:
Stack = ->
stack = [ ]
push: (e) -> stack.push(e)
pop: -> stack.pop()
toArray: -> stack.slice()
stack is a local variable in the Stack function so it cannot be accessed or seen from outside Stack. The push and pop functions simply proxy to the stack array and the toArray function is the only way to see what stack looks like. Only those three functions have access to stack so it is effectively private and each time you call Stack, you get a new local stack.
Demo: http://jsfiddle.net/ambiguous/C8V5R/
Adapting your queue to use this technique to hide array, head and tail is left as an exercise.
The return value of QueueMaker is a JavaScript object, with head being one of it's fields. Object fields are mutable, with no option for protected status.
Even with QueueMaker rewritten as a CoffeeScript class, and head being an instance variable, it would still be mutable from outside the object scope.
CoffeeScript can only support the language level features of JavaScript, which does not support private/protected keywords. Sadly.
QueueMaker = ->
do (array = [], head = 0, tail = -1) ->
pushTail: (value) ->
array[tail += 1] = value
pullHead: ->
if tail >= head
do (value = array[head]) ->
array[head] = undefined
head += 1
value
isEmpty: ->
tail < head
With this version, array, head and tail are hidden. They are initialed when the queue is created, and remain in existence only as long as it exists.
coffee> queue = QueueMaker()
{ pushTail: [Function],
pullHead: [Function],
isEmpty: [Function] }
coffee> queue.head
undefined
But to be honest, this is the first version of QueueMaker on the Ristretto link. What you gave us was the “de-encapsulate” version, rewritten purposely to make these variables visible (in order to extend its behavior).
For reference, the "de-encapuslated" version is:
QueueMaker = ->
do (queue = undefined) ->
queue =
array: []
head: 0
tail: -1
pushTail: ...
pullHead: ...
Your question omitted the queue= line. Now the purpose of the do()-> should be clearer.