Buttons as Finite Automata
web.stanford.edu
web.stanford.edu
Buttons get even more fun when you add a "long push" to it. I can not recall how often I have had discussions with customers about the intended behavior of physical buttons on devices; it takes some effort and patience to explain how requirements like "Perform action A when the button is pushed, or perform action B when the button is pushed for a 3 seconds" simply can not be implemented. "Oh, so you mean, perform action A when the button us released within 3 seconds?". "Well, no I want it do A when I push the button".
And then I draw a little picture on the white board showing the circles and arrows.
Customer: "No, I only want action B when it is held for 3 seconds"
You: "Todays technology can't do that"
Customer: "What. Wait. Why?"
You: "Because at the point when action A would be triggered we would have to know the future to tell whether the button will have been held for 3 seconds or not."
(Under no circumstances should you show them this: https://www.youtube.com/watch?v=BKorP55Aqvg)
Requirements capture is part of every eng job I've had.
I usually managed to convince those people to come up together with a new plan while making sure they still feel like their original work is somewhat in there — after all I was the expert they came to with their issue, would be a bit idiotic to not pay for my expertise..
Sometimes this does not work, then I usually just told them I won't take that project. And projects like these don't make any sense, the customer will complain about their own planning mistakes as if it was your fault, you get angry, they get angry, everybody loses.
The best customers are those who know the problem they want to solve very well, as well as having some idea how a potential solution could look, but who thank you if you have an even better solution.
It depends on the lie, but I think it probably fits human-centric timescale.
The alternative is to work on the partially ordered set of events {A, B, undo-A} and convert to {A, undo-A}, {B} and then transform to {no-op, B}
Hysteresis/Queue is your friend.
Of course now you have to accept the button doing nothing if it's held for longer than 300ms but shorter than 3s, but making it slowly fill up with another colour or something is probably enough to communicate the long press behaviour.
https://en.wikipedia.org/wiki/Deterministic_finite_automaton...
Ideally no string is a prefix of another string. Even more ideally any infinite sequence of As and Bs can be decoded into a sequence of actions.
It seems like what's gone wrong here is not that the customer wants something impossible, but that they don't understand what you mean when you say "perform action A when the button is released".
If you show one of those people a device with the behavior you describe -- e.g. "toggle a light if the button is released within 0.5 seconds, but toggle the other light, instead, if it's pressed and held for 3 seconds" -- would they agree that it does what they say they want?
Almost everyone understands "pushing a button" to include the action of releasing the button.
I added a `#button:active` style to see if it was actually leaving it activated after dismissing the context menu, but apparently no. Not sure what the cause is.
Still a crazy cool demo though. Having worked on ui for my hobby games, I always knew it was more complex than just onClick, but this makes me understand it perfectly. I'm probably going to implement a model based on this in my current project.
Small suggestion for improvement: There are really two boolean variables (inside the button area, or not; mouse button down, or not). They give rise to more than 4 states because the order of actions is important.
But the diagram might be made more intuitive by placing the states within a 2x2 chess board corresponding to in/out, up/down. Not much change is necessary (for example: shift "held outside" left, "pressed" down, and "fire" up).
I know this is nitpicky, and yes, I see that "fire!" is an accepting state, but maybe you could have a special transition from "fire!" to "hover" labelled "reset".
You've made a couple mistakes here.
1. You've made the unfounded leap that the diagram intends to represent state after the button has fired. That's manifestly incorrect.
2. You've made the incorrect assumption that a full reset of the diagram is accomplished by clicking the button. But that's not what it says. Obviously, you also need to move the mouse out of the button to return to the start state. So you could criticized this for incomplete instruction, however, since the context is a demo for a college course, omitting over-instruction of obvious and irrelevant details is most likely the best decision to reach the overall instructional goal.
If you want to be pedantic on the internet, you need to go a bit deeper than that. ;-)
...how do I get the button into the "idle, down" state? (i.e. how do you press a button without first hovering over it? I tried with [Tab] and spacebar to activate it, but that didn't do anything).
------
I'm thinking this page could likely be reimplemented without any JavaScript, using only CSS' interaction state pseudo-classes (`:active, :hover, :focus`, etc) as proxies for the button's state (with the sibling `~` selector to control the state-diagram image). Hmm, though the "held outside" state might be difficult (perhaps `body:hover button:not(:hover):active ~ #stateImage` would work for that?).
However, the page shouldn't be doing that because the state of the <button> is not actually affected by the mouse-pointer's state, so that entire "idle, down" state node shouldn't be there at all (nor "hover, pressed", as browsers use the same `:hover:not(:active)` state for the <button> as "hover" in that situation).
If you're referring to not wanting the `mouseup` event to fire, you are correct - however web-pages should not be using `mouseup` / `mousedown` to detect <button> clicks in the first place: they should be listening to only the `click` event, which is not raised when the user releases a held-down mouse button anyway. And the `click` event is also raised when the <button> is activated by other means, such as the spacebar which makes it more accessible too.
When you have the page open, open your console and run this:
`document.querySelector('input[type=button]').addEventListener('click', e => console.log("button 'click' event") )`
and then do the mousedown-and-hover-and-release described and you won't see anything logged until you really do click on it.
Click and hold outside of the button, and drag over it.
Physically speaking, we generally have either:
1. a "push" button, which activates when you press it down fully, or
2. a "hold" button, which activates continuously when you hold it down.
In either case, the "action" occurs at the instant you press the button down, and perhaps continues until you let go.
But the standard HTML button works differently, and from a physical perspective, quite weirdly: when you press it, it only gets "cocked", and when you release it, it activates. That's why its state representation feels complex and unintuitive!
Rather than the behavior of a "push" or "hold" button, the standard HTML button behavior is more like extracting an SD card: you press the card in and then release it to get it out.
(I'm sure there are physical buttons out there somewhere that activate on release. And you can also make a great model of a "push" or "hold" button with custom JS. But I think it's fair to say that the default HTML button doesn't work like real-world experience would lead anyone to expect.)
However it's also worth noting that IRL buttons tend to require more than a feather's touch to detect a press, whereas there is no such margin on monitors/pointing devices.
If you right click it, it gets stuck thinking it's "pressed" until you click somewhere.
As much as I like FSM in principle, I feel like substantially more than half that I've encountered in software have either failed to accurately model how a system actually behaves, or have been missing core states that are a natural fit for real-world use. It has grown to become a fairly accurate predictor for me that a system is going to have problems due to over-simplification and lack of flexibility.
Which is not a FSM problem obviously, but the correlation is astoundingly strong. I'm not sure why. They obviously work out fine in many places. Maybe software uses I've run across are just way more complicated than can be accurately understood from a visual diagram? This one certainly seems reasonable and complete, yet...
Nowadays I imagine only a small proportion of people who deploy "radio buttons" have even seen such a car radio, so the metaphor is now empty, like the floppy disk icon for "save".
https://www.knowahead.in/wp-content/uploads/2012/05/car-radi...
https://41.media.tumblr.com/tumblr_mbyb9qOw8H1rg5mmto1_1280....
https://i.pinimg.com/originals/10/d6/86/10d686376ae39772ac4e...
Basically a "radio button" interface is a mechanical XOR. So for example to route a signal to destination A, B, or C you'd want to push the A button and be sure B and C weren't selected. Often it mechanically latched, so you could also see at a glance which option was selected, rather than needing to have an indicator (typically a small incandescent bulb).
Radios themselves implemented that mechanism slightly differently. Each button had a stop (like a tab stop, not an organ stop). When you pushed the button it disabled all the other stops and then either a spring pulled the arm right to the stop or your own muscle power pulled it left. A pully rotated the tuner dial as the arm moved right or left. I believe the buttons were rectangular in order to have enough surface area for your finger to supply a firm press enough to pull the tuner arm all the way from right to left (the extrama case). If they'd been round they would have taken up too much vertical space.
The radios typically had five or six favorites, no more. The entire mechanism was mechanical.
I did have a tape player with buttons (play / rewind / stop / etc) that, when pushed, unpushed the other buttons. So I have a mental model for buttons of that kind. But I've never associated them with a radio.
Also, I've never associated the radio selector HTML element with a button (HTML or otherwise). Buttons are about triggering effects. But radio selectors are about selecting something; they have more in common with checkboxes.
It's funny that buttons are the "Hello World" of frontend components, because they're actually feverishly nuanced.
I wrote a proof of concept for one of these, using XState, a few months back.
My use case was a cross platform react native button -- which means there's technically a difference between "pressed" and "hover" -- and there's also a loading state, which required special handling for the a11y state.
https://codesandbox.io/s/fervent-shirley-dgesq?file=/src/Pre...
NOTE: this will open a popup (might have to enable popups) with the visualizer, and it defaults to the a11y machine, but there's a drop down to switch to the main button machine.
Here’s my first attempt. (Probably wrong, since I did it by hand.)
(enter,leave,|press,(enter,leave,)*release,|press,enter,release,leave,|enter,press,leave,(enter,leave,)*release,)*(enter|press,enter,(leave,enter,)*release),press,(leave,enter,)*,release
I’d never really thought about how many different sequences of events can lead to a button activating.1. keyboard navigation (Enter and space are ignored)
2. hover, then F5 to reload the page (this resets the state, and you get your cursor on the button without a state transition)
presses are completely ignored either way
lack of loading state in buttons is a huge unforced error that makes all ajax stuff way more verbose than it has to be
the dom has no idea of how client-server applications work, which is defensible because the dom is so old but doesn't portend well for either 1) the quality of the google-weighted standards process or 2) the future of the web as the platform for saas and non-mobile consumer
and if you dive into that video at 16:50 you'll see a very elegant description of minimal state immediate mode button logic; direct time link: https://youtu.be/Z1qyvQsjK5Y&t=16m50s
Note: I've not actually tried Elm only Elmish in F#.
I've played with some buttons that struck me as fairly standard and found that they have exactly the semantics implemented here (to fire, you need to press down inside the button area, hold down (leaving the area or not), and release inside the area).
This diagram is showing more checkbox behavior rather than button.
A button that has no "on click," so to speak, could be represented by having the fire state transition immediately back through "start."