Passing JS readonly values to a port

I aim to use my Xbox controller as an input to my Elm program. I am having trouble converting the value to a JSON value, however. The JavaScript is as follows, and is specified here.

navigator.getGamepads(); // Get a list of connected gamepads

When I connect my controller, the function results a list with one object, which represents my controller. However, if I pass it through an Elm ports that accepts either a String or a Value, it always gives me this:

[{}]

For some reason, the attributes like buttons (a list of buttons) axes (a list of joystick axes), id (the controller’s id` and other values are all readonly values that aren’t direct attributes to the object, but rather getter functions that will give you the latest info when you ask for the information.

Is there a short way to get all the keys (recursively) passed through the port into the Elm program?

I don’t believe json decoders can enumerate like that. If you know the names in advance I think you could decode them. Perhaps you could also enumerate them in JS and convert to a pure JSON object for elm. You may also be able to use a Dict decoder in elm to get a list keys.

Anything dynamic like this sounds like it will be awkward with Elm’s static types, but perhaps once you understand the structure (I’d do that in JS or documentation) you could get things like List Button which while unknown in number, are at least known in type.

I’m not sure I understand what you’re trying to do and what’s not working. There are some JS hacks of a sort that might help but it depends on what you’re trying to do.

I did read up a tiny bit on the game pad API on MDN and it seems that each browser behaves a little differently when using this API. That also might be related to what you’re trying to do.

Javascript has some distinction between two types of properties. In one case, they are “real properties”, and will be listed by Object.keys() and serialized by JSON.stringify(). In the other case, they’re “synthetic properties” that will not do either of those things. I’ve put those names in quotes because I don’t know the real names for them nor much of the technical details.

If you use JSON.stringify() to pass an object through a port, you obviously won’t get the “synthetic properties”. If you pass the value, Elm does not do a serialization step, and you should be able to access the “synthetic properties” by name, but not list them via Json.Decode.keyValuePairs.

You can loop over the object and convert it to a normal object on js-side and pass that to elm?

There are many ways to iterate over all the keys and build a new object:

Yes, let me clarify.

In order to get the state of my gamepads, I wanted to run something like the following:

function getGamepads() {
    app.ports.receiveGamepads.send(
        navigator.getGamepads()
    );
}

setInterval(getGamepads, 1000 / 60);

The port receives the latest state of my gamepad(s) 60 times a second, and I would be able to update my Model accordingly.

However, even though the browsers I’ve tested it on (Chrome & Firefox) both render something like the following:

[
    {
        "id": "my-game-controller-id",
        "buttons": [ 
            { "touched": false, "pressed": false, "value": 0.0 },
            { "touched": false, "pressed": false, "value": 0.0 },
            { "touched": false, "pressed": false, "value": 0.0 },
            ...
        ],
        "axes": [ 0.0, 0.0, 0.0, 0.0 ],
        "other-keys": "..."
    }
]

Running a Debug.log on the Value on the Elm side after going through the port receiveGamepads shows that the Json.Encode.Value looks like [{}].

As @dta pointed out correctly, the readonly values are “synthetic properties” that get the value when you specifically access them. I aim to run some script on the JavaScript side that takes all keys and turn them into “real properties” so that I can feed the object into the port.

@Atlewee That looks like a great idea! My main challenge, however, is that the keys are readonly recursively, so I’ll need to iterate over each property as well. Do you know if there’s a way to implement that as well?

I tried iterating over the keys, but then the buttons field showed as an empty list [] because I did not explicitly iterate over every button in the list of buttons.

That started to become a lot of work - so I decided to post here with the thought of “surely someone else has had this problem while trying to inject JS values into an Elm port.”.

I was able to create an Ellie example that decodes the gamepad data from a port here https://ellie-app.com/mDTxzrhCg8Qa1

The example only decodes the id and buttons of each gamepad but I imagine you should be able to change the decoders to get the other values you mention.

I did try to just get the key/value pairs on the gamepad object in elm but because gamepad does not work with Object.keys this doesn’t work.

What I find very confusing is that your solution doesn’t seem to have the problem at all. Even though a Debug.log does still seem to render a [{}], it does somehow manage to get the relevant values.

Perhaps it has to do with the fact that you’re decoding before passing it on to a Msg constructor? Perhaps Elm still allows access to readonly values before it is flattened down to a Msg type, but not after.

When I simply add a Value -> Msg type to the port in subscriptions, it always fails with the decoder.

If you look at how Json.Decode.keyValuePairs is implemented it uses a call to {object}.hasOwnProperty(key) to check if the key exists on a given object. For some reason this returns false for a Gamepad objects keys and therefore keyValuePairs is unable to decode any of the values for each key. Gamepad seems to not work like a classic JS object. Calling Object.keys(gamepad) is also unable to return the keys of the object. With my implementation because it decodes specific named keys it works as expected.

I think you misunderstood what went wrong for me, @RGBboy, but I think you helped me solve it nevertheless. I didn’t use Json.Decode.keyValuePairs, I used Json.Decode.field like your implementation does.

Upon debugging to decipher why yours works and mine doesn’t, I think I’ve found the difference. Your subscriptions function looks like this:

subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.batch
        [ Browser.Events.onAnimationFrame (always Tick)
        , Decode.decodeValue decodeGampads
            |> receiveGamepads
            |> Sub.map GamepadUpdate
        ]

Whereas mine looks like this

subscriptions : Model -> Sub Msg
subscriptions _ =
    Ports.receiveGamepads ReceiveGamepads

The main difference is that you decode the Value before constructing the Msg type, whereas I constructed the Msg type and then decode the Value in update. For some reason, the readonly attributes are still readable in subscriptions, but they disappear when trying to decode them in update.

I’m not sure whether this is intentional behaviour! However, decoding the Json.Encode.Value type in the subscriptions function seems to be the solution here.

Apologies for the misunderstanding. Someone mentioned Json.Decode.keyValuePairs so I assumed this was the approach you were taking.

I adjusted the example I made to test out constructing the Msg type with the raw Decode.Value and decoding the Decode.Value within update. I managed to get it working: https://ellie-app.com/mFbxdYbmbxsa1 . Perhaps something was slightly off with the decoders you originally had?

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.