Set Custom HTML in code?

Hey, I am curious what is the way to set a custom HTML file to a form programmatically? I can do a print(self.html) and see the file it is referencing, but can I set it by doing self.html = some_file.html ?

Yes. See the bottom of

Setting a formā€™s html property may make more sense for an HTML Form than for other Form subtypes.

What would be the best way to create a system (either a single form, or using hash routing, or a series of forms) that consists of a screen with some navigation buttons (forward, back, etc) which do not change, but display various HTML files depending on the button pressed?

For example, I have 5 HTML files and associated CSS files uploaded into my Assets folder, and Iā€™d like to have a ā€œNavigationā€ system consisting of a few Anvil Components, and when some of those components are pressed (buttons), the HTML file displayed changes? I know we can change the entire Formā€™s HTML, but how can we keep a small Nav overlay with Anvil components on it, and only change an inner frameā€™s HTML? Is it necessary to use an Iframe?

Hereā€™s a clone that shows setting custom HTML into a component. You could do what you describe with a single form with buttons that change the componentā€™s HTML.

Thanks. I tried modifying it a bit to open my html file from my assets, and it does mostly work, but it seems to parse some of the line-breaks as text:

My code:
def button_1_click(self, **event_args):
url = anvil.server.get_app_origin() + ā€œ/_/theme/component.htmlā€
file_contents = URLMedia(url).get_bytes()
self.html_component.html = file_contents

I also did it using an iFrame instead, however it seems not to load the CSS file when it is displayed in an iFrame. Maybe the reference to the CSS file in the assets changes when an HTML file in assets is displayed in an iFrame?

Youā€™re putting a binary string into the .html property, which wants a regular string. Maybe try decoding it first? Something along the lines of file_contents = URLMedia(url).get_bytes().decode()

1 Like

This did it:

    url = anvil.server.get_app_origin() + "/_/theme/component.html"
    file_contents = URLMedia(url).get_bytes()
    my_html=file_contents.decode('utf-8')
    self.html_component.html = my_html

thank you.

By the way, why is this way preferable to using an iframe to skip between the HTML files?

Generally speaking, the iframe isolates the embedded HTML from the enclosing pageā€™s CSS, while the HTML component does not. Depending on your situation, one method might work better than the other.

Hmmā€¦ I think in my case I would like the enclosing page and the iframe to have isolated CSS. However, when I displayed a public website (www.wired.co.uk), it displayed fine, but when I set the iframeā€™s URL to one of my asset files (component.html), it did not seem to load the CSS.
Would the CSS file need to be loaded separately if an HTML in the assets is the source for the iframe?

If youā€™re using an iframe, the embedded HTML needs to be a full page, but other than that Iā€™m not aware of any differences.

Can you provide a clone link for the app?

Sure:

That doesnā€™t have any extra HTML files in assets. Itā€™s just displaying the standard page template in an iframe. The standard page template is not an entire HTML file, just just the part of the body that is needed for the form.

Do you have a version that has an HTML page that links to other CSS files that you display in an iframe? Thatā€™s the situation you said didnā€™t work.

Ah sorry, Iā€™ve added an additional HTML and CSS file now (donā€™t mind any missing image files please).
Although whether it is linking to this new HTML file which references an stylesheet, or the default standard-page.html file with inline CSS, it does not seem to display the styles in the iframe.

It seems to work if you use a relative link to the stylesheet, e.g.:

<link rel="stylesheet" type="text/css" href="component.css" />

Interestingā€¦ so that means that from the iframe the files in Assets have different relative file paths than when linked from the designer. Because if I set the formā€™s HTML to an HTML path in my assets from the design view, all of the file paths (external style sheets, image filesā€¦) have to use the path ā€œ./_/theme/<file.txt>ā€

One other thing which still confuses me is the function to set the iframeā€™s content. In your example you used .appendTo(). This leaves a small border around the iframe. In order to change the iframeā€™s content, I had to use something like replaceAll(), as .appentTo() would just add the content below whatā€™s currently in the iframe. However it seems I can only call this method once, as subsequent calls do not replace the iframeā€™s content.

I updated the app link with these changes.

The iframeā€™s like displaying a page in a new browser tab. Any paths have to be relative to the file being displayed in the iframe. That is different from working inside the Anvil environment.

Youā€™re doing some JQuery manipulation by dynamically creating iframes and then replacing the Anvil content panelā€™s contents. Youā€™re not actually changing the source of a single iframe that exists somewhere.

Thatā€™s apparently causing the issue you mention. I canā€™t comment on how to fix that (I stay away from that level of Javascript), other than to point you toward an iframe component that reuses an existing iframe and suggest that maybe looking at how it does things might help: IFrame Custom Component: Embed other websites in your app

Thatā€™s also a custom component you can use directly in your app.

Thank for sharing the custom component. I took a look at it, but it created an issue as we saw before with the CSS files, which I wasnā€™t able to solve by removing the leading ā€œ./_/theme/ā€ path from the HTML file.

I am curious why I could not use replaceAll() on my button_click event handlers to set the content_panel with the new iframe, however I was able to get the behavior I wanted by first doing self.content_panel.clear() before calling iframe.appendTo() on each button press. Now the content_panel displays a new, single, iframe component with the local HTML file or web address I want.

Youā€™re mixing Javascript and Anvil calls to add content to the page. If youā€™ve found a way to do it that works, I wouldnā€™t worry too much about why it works or why it didnā€™t work before. Someone who understands how the Anvil and Javascript calls are interacting could explain it, but that person isnā€™t me.

1 Like

Yes, if there is anybody who might know why .replaceAll() seems like it cannot be called more than once to replace the contents of a content panel, AND why .appendTo() seems to leave a small border in the content panel, while replaceAll() does not, I would be more than happy to know :slight_smile:

The content panel is a ColumnPanel, which as a raw dom node isnā€™t as simple as a plain div element.

If youā€™re taking an anvil container and using jquery dom manipulation to change its inner elements youā€™re probably going to break something.

On the other hand, when you use .add_component() the container puts an element in the right place within its internal dom hierarchy.

Better to manipulate something thatā€™s much simpler. A Spacer is a good candidate. You can use standard anvil methods to add the spacer component to the content panel. Then use replaceAll on the spacer with your iframe.

1 Like