<html><body>
<input id="el" type="number">
</body></html>
oldvalue = ""
// The value of the element is already set before this function gets called.
document.getElementById("el").oninput = function (e) {
if (e.target.value.length > 2)
e.target.value = oldvalue;
else
oldvalue = e.target.value;
}model = ""
update (Input s) model = if (String.length s > 2) then model else s
view model = [ input [ type "number", onInput Input, value model ] [] ]var oldval = "";
function validateTime(ev) { var s = ev.target.value;
if (isValid(s)) oldval = s; // Save value and don't modify input else { ev.target.value = oldval; // Restore last valid input ev.stopImmediatePropagation(); // Listeners (e.g. Elm) ignore bad input }
}attribute "oninput" "validateTime(event)"
However when I write this in Elm using onInput, the user's invalid input is visible for a few milliseconds before being fixed. To me this seems like a vDOM issue, but I really don't know.
import Html exposing (Html, Attribute, text, div, input)
import Html.App exposing (beginnerProgram)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String
main =
beginnerProgram { model = "", view = view, update = update }
-- UPDATE
type Msg = NewContent String
update (NewContent content) oldContent =
if (String.length content > 2) then oldContent else content
-- VIEW
view content =
input [ type' "number", onInput NewContent, value content ] []
Right the problem is that the input isn't sending a keypressed event that we can filter, but is internally updating it's state (opaque to Elm) and sending the new value.
Dan, I think that, architecturally, your solution is a step in the right direction. The validation needs to happen synchronously, internal to the element.
It would be nice to have a first-class way to inject this behavior into the html element. A few ideas:
1) Some sort of annotation to pass a reference to the post-compilation JS identifier as an event attribute.
2) Compile down to a HTML custom element where the validation is internal.
Is this a problem that React or other vdom-based rendering systems have? If so, what's their workaround? If not, why not?
--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/Juu6j827wyA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.
> It'd be better for Elm, I think, if the browser just told you that keystrokes happened and then let you decide when/how/whether to apply them, but that's not the way input elements and their events work today.
Putting on my mad scientist hay: One could do that by formatting a normal text element to look like an input, and listen to key events, right?
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
var App = React.createClass({
getInitialState: function() {
return {value: ''};
},
render: function() {
return <input type="number" onInput={this.handleInput}
value={this.state.value} />;
},
handleInput: function(e) {
var v = e.target.value;
if (v.length <= 2) {
this.setState({value: v});
}
}
});import Html exposing (Html, Attribute, text, div, input)
import Html.App exposing (beginnerProgram)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput, on)
import String
import Json.Decode as Json
main =
beginnerProgram { model = "", view = view, update = update }
-- UPDATE
type Msg
= NewContent String
| NoOp
update msg oldContent =
case msg of
NewContent content ->
content
NoOp ->
oldContent
-- VIEW
view content =
input [ type' "number", on "input" decodeInput, value content ] []
decodeInput : Json.Decoder Msg
decodeInput =
Json.at ["target", "value"] Json.string
|> Json.map (\s -> if String.length s <= 2 then NewContent s else NoOp)
Right, so long as the event needs to be sent "to the top" (it's an old analogy and maybe outdated since Signals went away) this problem will persist. And I think that this is the case so long as we can only attach effect actions to the DOM - basically it's a flaw not only with FRP, but TEA as well.
This kind of stuff needs to be handled out-of-band. We need a way to create a <lengthRestrictedInput> that doesn't send an action to the rest of the app when the invariants don't hold.
Interestingly the behavior that we want to inject into these "input constructors" looks a lot like AFRP.
--
view content =
input [ type' "number", onInput handleInput, value content ] []
handleInput : String -> Msg
handleInput s =
if String.length s <= 2 then NewContent s else NoOp
What if:
Html.Events.onInput : (String -> Maybe msg) -> Attribute msg
We could run the validation logic before the DOM event is sent to the global effect system.
--
Huh, is this the problem that the NoOp message is supposed to solve?
It's obviously not working.
In cases like this, we need to determine locally that the effect on the app is semantically "no operation", and ignore the event by synchronously rolling back the local state and terminate without propagating.
Interestingly, the underlying VirtualDom can prevent a message being sent...
http://package.elm-lang.org/packages/elm-lang/virtual-dom/1.0.2/VirtualDom#on
> If the decoder succeeds, it will produce a message and route it to yourupdate function.
But what happens if the decoder fails? Probably doesn't rollback in the same way as Dan's workaround... Turns out, nothing!
https://github.com/elm-lang/virtual-dom/blob/1.0.2/src/Native/VirtualDom.js#L451
Let's add some logic when the decoder fails to rollback the input to the old value, just like in Dan's workaround. Or maybe be conservative and use a Maybe, or a new type with three options Rollback | NoOp | Message msg.
Anyway I think this solves it :-)