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()
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.
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
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.