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.
Related
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.
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.
The Click package allows a range of values to be selected from a list using the click.Choice method.
In my case the values are relatively long strings, so using:
choice_names = [u'Vulnerable BMC (IPMI)', u'IoT Vulnerability', u'SMBv1', u'BadHTTPStatus', u'Compromised']
#click.option('--category', prompt='\nPlease enter the category of incident.\n\n -- Options:\n{}\n\n'.format(
format_choices(choice_names)), type=click.Choice(choice_names))
will list the values as:
-> Vulnerable BMC (IPMI)
-> IoT Vulnerability
-> SMBv1
-> BadHTTPStatus
-> Compromised
This requires the user to enter the full string, which is inconvenient. Does Click provide a functionality to select a value using only a numeric identifier? So, the above options could be listed as:
-> Vulnerable BMC (IPMI) [1]
-> IoT Vulnerability [2]
-> SMBv1 [3]
-> BadHTTPStatus [4]
-> Compromised [5]
and to select the first option, the user would need to enter 1. This could be possible by defining a custom validation function, but I couldn't find any existing functionality offered by Click.
I came up with this:
class ChoiceOption(click.Option):
def __init__(self, param_decls=None, **attrs):
click.Option.__init__(self, param_decls, **attrs)
if not isinstance(self.type, click.Choice):
raise Exception('ChoiceOption type arg must be click.Choice')
if self.prompt:
prompt_text = '{}:\n{}\n'.format(
self.prompt,
'\n'.join(f'{idx: >4}: {c}' for idx, c in enumerate(self.type.choices, start=1))
)
self.prompt = prompt_text
def process_prompt_value(self, ctx, value, prompt_type):
if value is not None:
index = prompt_type(value, self, ctx)
return self.type.choices[index - 1]
def prompt_for_value(self, ctx):
# Calculate the default before prompting anything to be stable.
default = self.get_default(ctx)
prompt_type = click.IntRange(min=1, max=len(self.type.choices))
return click.prompt(
self.prompt, default=default, type=prompt_type,
hide_input=self.hide_input, show_choices=False,
confirmation_prompt=self.confirmation_prompt,
value_proc=lambda x: self.process_prompt_value(ctx, x, prompt_type))
#click.command()
#click.option('--hash-type', prompt='Hash', type=click.Choice(['MD5', 'SHA1'], case_sensitive=False), cls=ChoiceOption)
def cli(**kwargs):
print(kwargs)
Result:
> cli --help
Usage: cli [OPTIONS]
Options:
--hash-type [MD5|SHA1]
--help Show this message and exit.
> cli --hash-type MD5
{'hash_type': 'MD5'}
> cli
Hash:
1: MD5
2: SHA1
: 4
Error: 4 is not in the valid range of 1 to 2.
Hash:
1: MD5
2: SHA1
: 2
{'hash_type': 'SHA1'}
Edit May 25, 2020:
I recently came across questionary and integrated it with click
import click
import questionary
class QuestionaryOption(click.Option):
def __init__(self, param_decls=None, **attrs):
click.Option.__init__(self, param_decls, **attrs)
if not isinstance(self.type, click.Choice):
raise Exception('ChoiceOption type arg must be click.Choice')
def prompt_for_value(self, ctx):
val = questionary.select(self.prompt, choices=self.type.choices).unsafe_ask()
return val
#click.command()
#click.option('--hash-type', prompt='Hash', type=click.Choice(['MD5', 'SHA1'], case_sensitive=False), cls=QuestionaryOption)
def cli(**kwargs):
print(kwargs)
if __name__ == "__main__":
cli()
Since Click does not seem to provide a functionality of this kind, this custom validation function fulfills the purpose:
def validate_choice(ctx, param, value):
# Check if the passed value is an integer.
try:
index = int(value) - 1
# Return the value at the given index.
try:
return choice_names[index]
# If the index does not exist.
except IndexError:
click.echo('Please select a valid index.')
# If the value is of a different type, for example, String.
except (TypeError, ValueError):
# Return the value if it exists in the list of choices.
if value in choice_names:
return value
else:
click.echo('Please select a valid value from the choices {}.'.format(choice_names))
# Prompt the user for an input.
value = click.prompt(param.prompt)
return validate_choice(ctx, param, value)
#click.option('--category', prompt='\nPlease enter the category.\n\n -- Options:\n{}\n\n'.format(choice_names),
help='Category of the incident', callback=validate_category)
This allows a user to select a choice either by entering the choice name or by entering the index value. In case an invalid value is entered, the user is prompted again for an input.
I get an error which I can't resolve.
The snap application compiles without a problem and everything seems to be ok.
But when I render the relevant page in a browser I get this error:
A web handler threw an exception. Details:
expected ("code" :: Integer) in [ _id: 50b56f19208c2e9a09dccc2b, id: 1.0, code: "hdg435", name: "froggy"]
The code value is just a rendom string I picked for testing. I am not sure why an integer is expected?
These are the relevant parts of an example snap application.
getData :: IO [Document]
getData = do
pipe <- runIOE $ connect $ host "127.0.0.1"
let run act = access pipe master "test" act
result <- run (find (select [] "pcs") >>= rest)
close pipe
return $ either (const []) id result
mkSplice :: Document -> Splice AppHandler
mkSplice d = runChildrenWithText [dtp "id" d
,dtp "code" d
,dtp "name" d
]
dtp :: Text -> Document -> (Text,Text)
dtp tag d = (tag, T.pack $ show $ at tag d)
recSplice :: Splice AppHandler
recSplice = mapSplices mkSplice =<< liftIO getData
table :: Handler App App ()
table = heistLocal (bindSplice "rec" recSplice) $ render "table"
The relevant Heist template part of table.tpl is here:
<table>
<tbody>
<rec>
<tr><td><id/></td><td><code/></td><td><name/></td></tr>
</rec>
</tbody>
</table>
Please let me know what other parts of code need to be posted.
When I compile your dtp function I get:
import Data.Bson
import Data.Text (Text)
import qualified Data.Text as T
dtp :: Text -> Document -> (Text,Text)
dtp tag d = (tag, T.pack $ show $ at tag d)
Ambiguous type variable `a0' in the constraints:
(Show a0)
[...]
which makes perfect sense. It seems like in your case it's defaulting to Integer when you really want String. You can try adding a signature or better yet, just:
dtp tag d = (tag, at tag d)
If you want this to work for other types, you'll have to work harder.
UPDATE
Here's a GHCi session that illustrates the problem and how GHCi seems to default the Show instance to Integer:
Prelude Data.Bson> show $ at "hello" ["hello" =: "world"]
"*** Exception: expected ("hello" :: Integer) in [ hello: "world"]
The crux of the action is in this file:
https://github.com/gdoteof/exodus/blob/42c5ee09f09dcb718fa3bdfd79bfe5182c03faaa/Handler/GamingSession.hs
The general idea that I am going for is to accept POSTED input, and use that input, combined with the UTCTime from getCurrentTime to create a new GamingSession.
Then the GamingSession would be inserted into the database.
What's happening now at /session is a form that posts to /session, with prefilled in values. But I am getting an error
Prelude.read: no parse
(relevant config/routes: https://github.com/gdoteof/exodus/blob/d07bea21e7699b44739ceadf3c3a18533a9ef462/config/routes
)
When I have a form for persist keys, I usually prefer a drop down rather than manual entry. Try the code below. Also, try to follow the idiomatic style in the book, it will help.
gs <- runInputPost $ GamingSession
start
Nothing
<$> ireq (selectField (optionsPersistKey [] [] (toPathPiece . entityKey))) "player"
<*> ireq (selectField (optionsPersistKey [] [] (toPathPiece . entityKey))) "table"
<*> iopt intField "seat"
-- | The optionsPersist builtin to the Yesod.Forms package unfortunately only
-- works well with whole persist entities. We are only interested in the entity
-- id s which is why we add in this function here:
optionsPersistKey
:: (YesodPersist master
, PersistEntity a
, PersistQuery (YesodPersistBackend master) (GHandler sub master)
, PathPiece (Key (YesodPersistBackend master) a)
, RenderMessage master msg
, PersistEntityBackend a ~ YesodPersistBackend master)
=> [Filter a]
-> [SelectOpt a]
-> (Entity a -> msg)
-> GHandler sub master (OptionList (Key (PersistEntityBackend a) a))
optionsPersistKey filts ords toDisplay = fmap mkOptionList $ do
mr <- getMessageRender
pairs <- runDB $ selectList filts ords
return $ map (\(Entity key value) -> Option
{ optionDisplay = mr (toDisplay $ Entity key value)
, optionInternalValue = key
, optionExternalValue = toPathPiece key
}) pairs
You're getting a read fail b/c you're trying to read values like "4f6150251c21230c78000000" to PersistInt64. The MongoDB backend does not use PersistInt64s for key values so that code won't build a proper key for your setup. I'd try PersistText.
The error you're getting means the input does not look like a normal integer. What do the prefilled values actually look like? The strings from ireq textField "player" and ireq textField "table" have to just contain a number possibly padded with whitespace.
Also, to make handling bad parses easier, you should look at the safe package. This contains a version of read (and similar functions) that returns a Maybe, so you get a Nothing instead of an exception if it can't parse the input.
Ok, try this:
import Data.Text (unpack)
...
(player, table, seat) <- runInputPost $ (,,)
<$> (ireq textField "player")
<*> (ireq textField "table")
<*> iopt intField "seat"
playerId <- maybe (invalidArgs ["couldn't parse: ", player]) return $ fromPathPiece player
tableId <- maybe (invalidArgs ["couldn't parse: ", table]) return $ formPathPiece table
let gs = GamingSession start Nothing playerId tableId seat
...
changing
textToKey = Key . PersistText . read . unpack
to
textToKey a = fromJust . fromPathPiece $ a
fixed it for me