Replace strings in a rendered html (or before rendering HTML)

I need some help to clear my thoughts.

I need to render another HTML page within a page but will like to replace some tags within be it while rendering OR before render.

<div>
    <%= String.replace(render("greeting.html"), ["<h1"], fn "<h1" -> "<h2 onclick='console.log('yo')" end) %>
</div>

May I know your thoughts how it can be done?

And second question, will the rendered file have access to the javascript functions in the parent/containing page?

Thank you!

Using regex to transform html is not what I would do… maybe use Floki, and set attributes wherever needed.

I would try to separate html from js. You can do it using js, and attach event handler to the dom.

Maybe passing some parameters, like the js to run, to the child would be good too.

And maybe use some helpers, You should avoid logic in the template.

1 Like

Many thanks for the suggestiom. I agree Floki is an incredibly powerful library but as the raw_html generated may not always be an EXACT copy, it may not be suitable.

Good suggestion regarding the seperation of HTML from JS. Will need to refactor later on.

Will love your thoughts on this

page_template.html.eex
<div>
    <%= raw rendertem() %>
</div>
page_view.ex
defmodule TestWeb.PageView do
  use TestWeb, :view

  def rendertem do
              {:ok, htmlfile} = File.read("lib/test_web/templates/page/template.html.eex")
              String.replace(htmlfile, ["<div"], fn "<div" -> ~s("<div onclick="console.log\('Clicked'\);") end)
  end
end

Many thanks.

This problem is usually solved by having the template contain EEX code that does whatever behavior you’re looking for. Where is that file coming from that you’d need to transform it this way?

The file is an user uploaded HTML file (here its called template.html - which i admit is misleading) that is loaded as part of a main page and some interactivity (such as the js onclick event) must be dynamically loaded along with it.

So how is the above? Is there a better way? I tried EEx.eval_file instead of File.read but it was very slow.

You’re serving untrusted HTML to end users?

1 Like

This is just an experiment.

The template is created internally.

So any insight on how one can replace the html tags to include an onclick event while rendering within a parent page? Thanks. And i agree its good practice to ensure it is sanitized prior.

When mutating HTML, definitely use something like Floki to get escaping and quoting right. For instance, both of the examples in this thread create broken HTML:

<%= String.replace(render("greeting.html"), ["<h1"], fn "<h1" -> "<h2 onclick='console.log('yo')" end) %>
  • replaces the h1 opening tag with an h2 but doesn’t change the closing tag
  • creates an invalid attribute with the ' inside onclick='...'
String.replace(htmlfile, ["<div"], fn "<div" -> ~s("<div onclick="console.log\('Clicked'\);") end)
  • replaces <div with "<div ..." with extra double-quotes

Alternatively, consider alternative approaches:

  • generate the HTML with the needed attribute

  • bind a click event listener to the element dynamically; for instance, render the template inside a div and use that to attach a handler:

# in the HTML
<div class="template-content">
  <%= render the template %>
</div>

# in the page JS
$(".template-content h2").click(...)

The rendered file’s contents will be seamlessly included - there’s no sandboxing / isolation, the bytes from the template will be presented as part of the parent page.

I am so grateful to you for such a well thought out and very reasonable reply.

What i am trying to actually is to append an alpinejs x-on:click attribute to specific html tags.

Besides the typo where h2 was supposed to read h1…

My concern is floki and other html libraries rewrite the html. My need is that all formatting must be preserved with just x-on attributes injected whereever required. And as x-on is being used, it obliviates the need for a seperate event listener.

Thank you and I hope i dont appear argumentative because i actually agree with all your suggestions. Just i am operating within constraints. Hope to hear your views.

Hmmm, yeah Alpine seems to take some pretty vigorous liberties with HTML structure:

<div x-data="{ open: false }">
    <button @click="open = true">Open Dropdown</button>

    <ul
        x-show="open"
        @click.away="open = false"
    >
        Dropdown Body
    </ul>
</div>

However, if the template already contains Alpine-flavored markup it seems simplest to write the handlers directly there, instead of trying to insert them at runtime. :thinking:

1 Like

Exactly. Alpinejs doesnt make using a HTML parser easy.

Sorry what do you mean by writing the handlers directly there?

Do you mean, editing the template prior to runtime?

Many thanks

Sorry what do you mean by writing the handlers directly there?
Do you mean, editing the template prior to runtime?

I mean adding the handler when writing the template - if the template has Alpine code in it, presumably whatever / whoever is authoring it is OK writing Javascript.

1 Like