Notes from the course The Hard Parts of UI Development
by Will Sentance in Frontendmasters.
Goal 1 - Display content on the screen
-
Layout engine
works out page layout for specific browser/screen. -
Render engine
produces the composite ‘image’ for graphics card. -
html
>DOM
inC++
(webcore) > visual content.html
is a one time addition of elements into the DOM. -
CSS
>CSSOM
(mirrors theDOM
) inC++
> styling the content above - Render engines creates an image of the page, which gets sent to the graphics card and updated 60 times a second.
Goal 2 - Let users change the content / interact with it
-
html
is a one time addition of elements to theDOM
(in C++). Changing content on the screen changes the DOM. However, we cannot run code on the DOM. Javascript is needed. - When we load a url in the browser: 1) we open the html file,
html
is parsed by thehtml parser
and creates the DOM (in C++); 2) the html file has a reference to the JS file, which will start loading by the JS engine, with its own thread of execution, memory, execution context, etc.; 3) JS links to the C++ DOM runtime via thedocument
object and all the methods available to it:document.querySelector
, etc. -
Web IDL
(Interface Description Language
) - standardised way to know how to access other parts of the browser from JS.DOM API
is written in human language - its implementation in the browser will be described in theWeb IDL
. -
const jsDiv = document.querySelector('div')
uses thedocument
object, which will give us access to theDOM API
via a hidden link to the C++ DOM. It will allow us to grab a list of elements in C++.jsDiv
will be assigned to the evaluation ofdocument.querySelector('div')
. That will be an object with properties that allow us to access and modify the C++ DOM element i.e.textContent
. If we console log that element, instead of the C++ DOM element we get the html command that created the C++ DOM element! -
textContent
is the property ofjsDiv
in the document object that will update the DOM So it is agetter setter
property, otherwise it would be confined to this object and would be of little use to us.other
and others get added to the call stack. - The following code follows the steps described after:
// in app.html
<input />
<div></div>
<script src='app.js'></script>
// in app.js
let post = '';
const jsInput = document.querySelector('input');
const jsDiv = document.querySelector('div');
function handleInput() {
post = jsInput.value;
jsDiv.textContext = post;
}
jsInput.onInput = handleInput;
-
app.html
loads; theHTML parser
looks at each line and adds them to the C++ DOM from which we will produce the actual pixels of the webpage through thelayout and render engine
; theHTML parser
will tee up the JS engine to run; this will give us access to thedocument
object that has a reference to the C++ object, that we can run function on to search or add elements. - There is a division between "C++ land" and JS land (we are not able to write code directly into the C++ land. Once the HTML is parsed and the items added in the C++ object, the items appear in the screen.
- When JS starts executing, we get a call stack and a global execution context, which has a pre-loaded object:
document
. This object allow our JS to interact with the C++ object:querySelector
(this is one type ofaccessor object
), etc. They not only have action in JS but also in the C++ object. - The JS above: 1) define const post assigned to empty string 2)
document.querySelector('input')
will call thequerySelector
getter/setter in the document object, which will bring us to the C++ object 3) it finds it but because the C++ object cannot be brought back a corresponding object gets produced in JS with a link to the underlying C++ object; 4) that newly produced object is assigned tojsInput
; 5) that JS object has properties that will allow us to work with the C++ object: i.e.onInput
,value
, etc.; 6) same forjsDiv
; 7) assignhandleInput
to a function declaration; 8) in thejsInput
JS object representation of the C++ element we will use the setteronInput
to assign it to the function declarationhandleInput
. That will add a handler in the C++ element that will trigger the execution of that function when a user inputs something in theinput
C++ object element; 9) user action writes something in the input (ie. "Hi") and that triggers anevent
which issues a command to the C++ element to execute its handler; 10)value
in theinput
C++ element will be populated withHi
; 11) the Events API will trigger the function referenced in the C++ element handler and add the call tohandleInput
in the callback queue; 12) theevent loop
will grab that function execution from the callback queue when the call stack is empty and there i no more JS code to run in the global execution context; 13) the function call will be added it to thecall stack
; 14) anexecution context
will be created with its memory; 15) in memory the variablepost
will be assigned to the value of the propertyvalue
of thejsInput
object; 16) we look inside thejsDiv
object for the settertextContext
which is set to the value ofpost
; 17)textContent
updates the value of the underlying C++ element, which in turn updates the screen to show "Hi".
One-way data binding
- It is a paradigm implemented by the frameworks (React, Vue, Angular, etc.) that restrict the back and forth between C++ and JS to make our lives easier as UI engineers.
- Code:
// in app.html
<input />
<div></div>
<script src='app.js'></script>
// in app.js
let post = '';
const jsInput = document.querySelector('input');
const jsDiv = document.querySelector('div');
jsInput.value = 'What is on your mind?'; // view
function handleInput() {
post = jsInput.value;
jsDiv.textContext = post; // affect view!
}
function handleClick() {
jsInput.value = ''; // affect view!
}
jsInput.onInput = handleInput;
jsInput.onClick = handleClick;
-
jsInput.value
sets the C++ value to the value set in JS and that renders the string in the screen; user action click input: sends an event to the DOM to trigger the handler which adds the function to the JS callback queue; event loop grabs thehandleClick
and adds it to the call stack;handleClick
is called and an execution context is created; thevalue
setter ofjsInput
gets sets to an empty string, updates the value in the C++ element, which updates the string; next user action: input; they will write whatever, which will trigger the C++ element handler, add the function to the callback queue, event loop will add it to the call stack, new execution context, thevalue
property of thejsInput
object will be assigned topost
, and that string will be set to thetextContent
setter, which updates the C++ element, which updates the screen.