Home > Enterprise >  Elm not allowing html `script` node in virtual DOM
Elm not allowing html `script` node in virtual DOM

Time:12-06

I try to integrate to my ELM page a "login with" widget (the one from Telegram https://core.telegram.org/widgets/login)

I try to build the appropriate node, which is of type <script>, but when the page is rendered, the type is replaced by <p>. I guess this is a security feature. But how can I build this node ?

What I want:

<script
    async
    src="https://telegram.org/js/telegram-widget.js?21"
    data-telegram-login="botname"
    data-onauth="onTelegramAuth(user)"
></script>

What I do:

telegram : Html Model
telegram =
    node "script"
        [ attribute "async" ""
        , attribute "src" "https://telegrami.org/js/telegram-widget.js?21"
        , attribute "data-telegram-login" "botname"
        , attribute "data-onauth" "onTelegramAuth(user)"
        ]
        []

What I get:

<p
    async=""
    src="https://telegrami.org/js/telegram-widget.js?21"
    data-telegram-login="botname"
    data-onauth="onTelegramAuth(user)"
></p>

Thank for your help :)

CodePudding user response:

This is intentional. Elm doesn't allow script tags for security reasons. If you want that kind of functionality, wrap it around in a web component and import the web component in Elm. You can listen for Custom Events on Elm side in order to pass data from the web component to Elm and you can set attributes to the web component in order to pass data from Elm to the web component.

CodePudding user response:

Thanks to @pdamoc, I've managed to make it work like this:

Web component: telegram-button.js

export default class TelegramButton extends HTMLElement {
  constructor() {
    const self = super();

    self.onauth = (user) => {
      this.dispatchEvent(new CustomEvent('on-telegram-auth', {detail: user}))
    }

    return self;
  }

  connectedCallback() {
    const script = document.createElement('script');

    script.src = 'https://telegram.org/js/telegram-widget.js?21';
    script.async = true;

    const attributes = {
        'data-telegram-login': this.getAttribute('data-telegram-login'),
        'data-size': this.getAttribute('data-size'),
        'data-radius': this.getAttribute('data-radius'),
        'data-request-access': this.getAttribute('data-request-access'),
        'data-onauth': 'onTelegramAuth(user)',
    };

    for (const [k, v] of Object.entries(attributes)) {
        v !== undefined && script.setAttribute(k, `${v}`);
    }
    this.appendChild(script);
  }
}

const onTelegramAuth = (user) => {
    const button = document.querySelector("telegram-button")
    button.onauth(user)
}

if (!window.customElements.get('telegram-button')) {
    window.TelegramButton = TelegramButton
    window.customElements.define('telegram-button', TelegramButton)
    window.onTelegramAuth = onTelegramAuth
}

Import it inside your index.js

Then in Elm

button : Html Msg
button =
  Html.node "telegram-button"
    [ attribute "data-telegram-login" "OtoDavarBot"
    , attribute "data-size" "large"
    , attribute "data-radius" "6"
    , attribute "data-request-access" "write"
    , onTelegramAuthChange OnTelegramAuth
    ] []

onTelegramAuthChange : Msg -> Attribute Msg
onTelegramAuthChange toMsg =
    telegramDataDecoder
        |> Decode.map toMsg
        |> Html.Events.on "on-telegram-auth"

type alias TelegramAuth =
    { id : Int }

telegramDataDecoder : Decode.Decoder TelegramAuth
telegramDataDecoder =
    Decode.map TelegramAuth
        (Decode.at ["detail", "id"] Decode.int)
  • Related