I'm adding some reagent to an application in which some hiccup rendering is already done on the server from a database. Obviously react then hooks into the html that it sees & mounts.
I want to do some progressive enhancement, i.e., a react/reagent component should mount but I also need the original text of the div it's going to mount on, this is put there by the database. So to react it's in the static html it is about to overwrite when it mounts. I want to save that element's contents before it's "deleted".
Here's what's not working
(defn by-id [id] (js/document.getElementById id))
(when-let [el (by-id "lesson-app")] ; only mount when this el is present
(let [lesson-text (by-id "lesson-app")] ; try to get el contents before mount
(console.log lesson-text) ; <- this logs contents *after* mount!!
(rdom/render [lesson-app] el))) ; mount the component
What's logged to console here is the dom element after mount. Why is that?
Was wondering if I could use a form-2 component and grab the contents in the let. That's not working for me though, interesting to know why if anyone can explain.
Update
I've succeeded in passing in text as an html attribute, as described here. But that means for progressive enhancement I need to render the text twice, once as an attr & once as the normal text content.
Update 2
I'm getting the feeling it may be for reasons of convention or encouraged idiom and/or perhaps speed that react does not let you pass in html element content (it clearly doesn't violate the laws of physics to do so). The slight cost if this is correct is that for a progressive app we must duplicate the text as both html element content and a data- attribute, but that's not terrible at least for my current case.
This functionality isn't supported by react.
We use an html data- attribute instead.
For example:
(defn ^:export run []
(when-let [el (by-id "lesson-app")]
(let [data-text (.getAttribute el "data-text")]
(rdom/render [lesson-app data-text] el))))
Related
Consider the following HTML code:
<script>setTimeout(() => document.querySelector('link').remove(), 0);</script>
<link rel="stylesheet" href="http://localhost:8080/test.php">
where http://localhost:8080/test.php is a link to a simple PHP script which just waits 5 seconds (<?php sleep(5);).
The script removes the link tag as expected, but browser doesn't abort the request to the stylesheet. This doesn't make sense, because when the request is complete, browser doesn't apply the styles anyway. Is this a browser bug, or is there an explanation for this behavior in the specification?
This happens in Chrome and Firefox; I didn't test other browsers.
In a browser, the layout engine always parses the HTML from top to bottom, sequentially. However, the request to get CSS happens in parallel because CSS never changes the DOM Tree, there is nothing to worry.
Since style sheets don't change the DOM tree, there is no reason to
wait for them and stop the document parsing
Resource: Read Parsin Scripts > The order of processing scripts and style sheets
The main reason not to abort the CSS request is because it causes no harm. The effort to abort it would be much more painful.
However, note that:
Webkit blocks scripts only when they try to access for certain style
properties that may be effected by unloaded style sheets.
Image: WebKit Layout Engine. Credits - http://taligarsiel.com
Iv'e been reading up on the HTML Parser and your case seems a bit like the chicken and the egg story because naturally a <script> is supposed to block the parsing execution but there are exceptions to this (async & defer attributes)...
what you are doing is abit different ...
by addressing the document in the script tag the browser is forced to stop parsing and create a document with the tag you are addressing without executing the script itself again in the new document and thus requesting its related content before the DOM is able to remove the element ...
the DOM parsing process in the browser consists of a number of steps :
*If the document.write() method was called from script executing inline (i.e., executing because the parser parsed a set of script tags), then this is a reentrant invocation of the parser.
Edit :
A 'script' element is processed as follows:
If the 'script' element's "already processed" flag is true or if the element is not in the document tree, then no action is performed and these steps are ended.
If the 'script' element references external script content, then the external script content using the current value of the 'xlink:href' attribute is fetched. Further processing of the 'script' element is dependent on the external script content, and will block here until the resource has been fetched or is determined to be an invalid IRI reference.
The 'script' element's "already processed" flag is set to true.
If the script content is inline, or if it is external and was fetched successfully, then the script is executed. Note that at this point, these steps may be re-entrant if the execution of the script results in further 'script' elements being inserted into the document.
Note that a load event is dispatched on a 'script' element once it has been processed, unless it referenced external script content with an invalid IRI reference and 'externalResourcesRequired' was set to 'true'.
The Load Event - The event is triggered at the point at which the user agent (Browser) finishes loading the element and any dependent resources (such as images, style sheets, or scripts). In the case the element references a script, the event will be raised only after an attempt to interpret the script has been made. Dependent resources that fail to load will not prevent this event from firing if the element that referenced them is still in the document tree unless they are designated as externalResourcesRequired. The event is independent of the means by which the element was added to DOM tree.
If you are asking why the event loop is built that way I can't give you a definitive answer nor can I suggest a better way for it to operate but in the comments you asked what the specifications say about this condition and the specifications state that it is due to historical reasons as stated below :
HTML 5.1 W3C Recommendation, 1 November 2016
7. Web application APIs
7.1.4.2. Processing model
Some of the algorithms in this specification, for historical reasons, require the user agent to pause while running a task until a condition goal is met. This means running the following steps:
If necessary, update the rendering or user interface of any Document or browsing context to reflect the current state.
Wait until the condition goal is met. While a user agent has a paused task, the corresponding event loop must not run further tasks, and any script in the currently running task must block. User agents should remain responsive to user input while paused, however, albeit in a reduced capacity since the event loop will not be doing anything.
Elm does not appear to support HTML document's head node, <head>..</head>.
The question is why not support the complete HTML document with suitable functions. It would seem such an inclusion would allow expedient use of external resources such as style sheets.
Apart from DOCTYPE, HTML tags are uniformly, tagName attrList elmList. Perhaps a set of appendAttr and appendElm functions could be concocted to allow flexibility for specifying a more comprehensive VirtualDom.
Am I missing something?
By the time that your Elm code has loaded and starts running, the browser has already read in the <head> of the HTML page that contains the Elm code, so it's too late to influence the contents of the <head>.
Elm can be embedded into an element in the page, or run full-screen (which appears to add a child to the <body>). Elm can only manipulate content within its container, not outside of it. In particular, all elements that Elm generates will be contained within the <body> of the document, whereas <head> is a sibling of <body>.
It's possible to generate HTML elements with any name you like, using Html.node "elementName". So it's possible to create a <head> element in Elm. However, a <head> element created this way would end up inside <body>, and I would expect browsers to ignore it.
Luke's answer is of course perfectly correct, but nothing prevents you updating the head of a document using javascript via a port. Here is an example to update the title (the tab name).
In Elm
port module Ports exposing (..)
port title : String -> Cmd msg
with this sort of update function
update message model =
case message of
SetTitle name ->
(model, Ports.title name)
In Javascript
var elm = Main.fullscreen();
elm.ports.title.subscribe( title => {
document.title = title;
});
Elm is a (mostly) purely functional language, meaning it attempts to minimize all side effects that could lead to errors. As the elm compiler has no way to know if a CSS file will exist at runtime, it cannot safely say whether or not including a CSS file will result in an error. Therefore, it is not something that will likely be included in the future.
That being said, Rtfeldman of NoRedInk has created the repo https://github.com/rtfeldman/elm-css that allows for elm code to mirror CSS, ensuring safety as it goes.
To make your body have a background color with a certain color, this elm code suffices:
[ body
[ backgroundColor (rgb 200 128 64)
, color (hex "CCFFFF")
]
]
and will compile to a .css file for you.
In Objective C to build a Mac OSX (Cocoa) application, I'm using the native Webkit widget to display local files with the file:// URL, pulling from this folder:
MyApp.app/Contents/Resources/lang/en/html
This is all well and good until I start to need a German version. That means I have to copy en/html as de/html, then have someone replace the wording in the HTML (and some in the Javascript (like with modal dialogs)) with German phrasing. That's quite a lot of work!
Okay, that might seem doable until this creates a headache where I have to constantly maintain multiple versions of the html folder for each of the languages I need to support.
Then the thought came to me...
Why not just replace the phrasing with template tags like %CONTINUE%
and then, before the page is rendered, intercept it and swap it out
with strings pulled from a language plist file?
Through some API with this widget, is it possible to intercept HTML before it is rendered and replace text?
If it is possible, would it be noticeably slow such that it wouldn't be worth it?
Or, do you recommend I do a strategy where I build a generator that I keep on my workstation which builds each of the HTML folders for me from a main template, and then I deploy those already completed with my setup application once I determine the user's language from the setup application?
Through a lot of experimentation, I found an ugly way to do templating. Like I said, it's not desirable and has some side effects:
You'll see a flash on the first window load. On first load of the application window that has the WebKit widget, you'll want to hide the window until the second time the page content is displayed. I guess you'll have to use a property for that.
When you navigate, each page loads twice. It's almost not noticeable, but not good enough for good development.
I found an odd quirk with Bootstrap CSS where it made my table grid rows very large and didn't apply CSS properly for some strange reason. I might be able to tweak the CSS to fix that.
Unfortunately, I found no other event I could intercept on this except didFinishLoadForFrame. However, by then, the page has already downloaded and rendered at least once for a microsecond. It would be great to intercept some event before then, where I have the full HTML, and do the swap there before display. I didn't find such an event. However, if someone finds such an event -- that would probably make this a great templating solution.
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
DOMHTMLElement * htmlNode =
(DOMHTMLElement *) [[[frame DOMDocument] getElementsByTagName: #"html"] item: 0];
NSString *s = [htmlNode outerHTML];
if ([s containsString:#"<!-- processed -->"]) {
return;
}
NSURL *oBaseURL = [[[frame dataSource] request] URL];
s = [s stringByReplacingOccurrencesOfString:#"%EXAMPLE%" withString:#"ZZZ"];
s = [s stringByReplacingOccurrencesOfString:#"</head>" withString:#"<!-- processed -->\n</head>"];
[frame loadHTMLString:s baseURL:oBaseURL];
}
The above will look at HTML that contains %EXAMPLE% and replace it with ZZZ.
In the end, I realized that this is inefficient because of page flash, and, on long bits of text that need a lot of replacing, may have some quite noticeable delay. The better way is to create a compile time generator. This would be to make one HTML folder with %PARAMETERIZED_TAGS% inside instead of English text. Then, create a "Run Script" in your "Build Phase" that runs some program/script you create in whatever language you want that generates each HTML folder from all the available lang-XX.plist files you have in a directory, where XX is a language code like 'en', 'de', etc. It reads the HTML file, finds the parameterized tag match in the lang-XX.plist file, and replaces that text with the text for that language. That way, after compilation, you have several HTML folders for each language, already using your translated strings. This is efficient because then it allows you to have one single HTML folder where you handle your code, and don't have to do the extremely tedious process of creating each HTML folder in each language, nor have to maintain that mess. The compile time generator would do that for you. However -- you'll have to build that compile time generator.
First let me give you simple overview how it loads, then ill ask question regarding that.
Browser Fetch HTML => parse html => create nodes => parse nodes and start converting them to Dom elements => finds style node so start creating CSSOM => on finishing parsing if there was style tag it waits to let it construct CSSOM tree => once both are finished it merges both, DOM and CSSOM, and fires DOMContentLoaded Event.
So in summary as soon as CSSOM is ready browser starts rendering and Dom can incrementally be added.
This is all fine, but how does the flow go when browser starts rendering page when not the whole html is loaded..(for example in nodejs you can partial html then wait 2s and then send more)
What if there was another style tag at the bottom of the page. Not having received all html, and no css browser would start rendering, but from my understanding rendering only occurs after cssom has been completely built.
What happens to script tag, if css isn't done processing script tag isn't executed and thus also stops parsing. JS is ran after CSSOM is complete.
Things may block the DOMContentLoaded Event, but that does not prevent rendering of the incomplete page. That can be important for very long pages streamed from a slow server.
Browsers can and do interleave script execution, re-styling, rendering with the document parsing. This can be trivially shown by executing javascript in the <head> and querying the DOM, you will see that the document will not have all of its nodes (possibly not even a body element) before the DOMContentLoaded event has fired.
You have to think of document construction more as a stream than sequentially executed blocks that run to completion before the next block starts.
CSSOM stops parsing. Thus execution of subsequent script tags, and also delays rendering.
Script tags before style tags will execute before CSS is loaded into CSSOM from style tags afterwards.
Style tags that come after script tags will alter CSSOM. And if script accessed styles that are being altered then what it read is outdated. Order matters.
Parsing is stopped not just rendering.
JavaScript blocks parsing because it can modify the document. CSS
can’t modify the document, so it seems like there is no reason for it
to block parsing, right?
However, what if a script asks for style information that hasn’t been
parsed yet? The browser doesn’t know what the script is about to
execute—it may ask for something like the DOM node’s background-color
which depends on the style sheet, or it may expect to access the CSSOM
directly.
Because of this, CSS may block parsing depending on the order of
external style sheets and scripts in the document. If there are
external style sheets placed before scripts in the document, the
construction of DOM and CSSOM objects can interfere with each other.
When the parser gets to a script tag, DOM construction cannot proceed
until the JavaScript finishes executing, and the JavaScript cannot be
executed until the CSS is downloaded, parsed, and the CSSOM is
available
.
https://hacks.mozilla.org/2017/09/building-the-dom-faster-speculative-parsing-async-defer-and-preload/
A few important facts:
Event DOMContentLoaded is fired when the document has been fully parsed by the main parser AND the DOM has been completely built.
Any normal script (not async or deferred) effectively blocks DOM construction. The reason is that a script can potentially alter DOM.
Referencing a stylesheet is not parser-blocking nor a DOM-construction-blocker.
If you add a <script> (be it external or inline) after referencing a CSS, the execution (not fetching) of the script is delayed until fetching and parsing of the CSS has been finished even if the script's fetch finishes sooner. The reason is that the scripts may be dependent on the to-be-loaded CSS rules. So the browser has to wait.
Only in this case, a CSS blocks the document parser and DOM construction indirectly.
When the browser is blocked on a script, a second lightweight parser scans the rest of the markup looking for other resources e.g. stylesheets, scripts, images etc., that also need to be retrieved. It's called "Pre-loading".
In a ClojureScript/Om app, I have a DOM and a string of HTML.
How can I translate that string of HTML into elements I can insert into the DOM?
I've started down the path of parsing the HTML with hickory, planning to then process the hickory data to create DOM elements, but I think there must be a simpler way I'm overlooking.
(I don't need to validate the HTML, I can assume it's safe and valid enough.)
You don't need to parse the HTML string. It's unnecessary overhead. React/Om supports something like DOM's innerHTML property. Just set the prop this way:
(om.dom/div #js {:dangerouslySetInnerHTML #js {:__html "<b>Bold!</b>"}} nil)
If you work with plain DOM without Om, set the innerHTML property like:
(let [div (. js/document getElementById "elId")]
(set! (. div -innerHTML) "<b>Bold!</b>"))
Aleš Roubíček answer is much better. I'll leave this un case it helps somebody.
Hickory offers a as-hiccup function. Hiccup uses Clojure data structures to represent HTML. You could feed those data structures to Clojurescript libraries that follow the same conventions:
Hiccups to generate regular DOM elements.
Sablono to generate React DOM elements and use with Om.
You could also use Kioo/Enfocus and instead of passing a file path, pass the string directly. This would be more direct and instead of using two libraries (Hickory + Sablono) you would use only one. The caveat is that Kioo and Enfocus follow the Enlive style of templating (which is great but has a learning curve) and the docs are focused on file paths and not strings (even though it is possible to pass strings).