[WIP] The Hard Parts of UI development

Pere Sola - Jan 14 - - Dev Community

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 in C++ (webcore) > visual content. html is a one time addition of elements into the DOM.
  • CSS > CSSOM (mirrors the DOM) in C++ > 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 the DOM (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 the html 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 the document 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 the Web IDL.
  • const jsDiv = document.querySelector('div') uses the document object, which will give us access to the DOM 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 of document.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 of jsDiv in the document object that will update the DOM So it is a getter 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;
Enter fullscreen mode Exit fullscreen mode
  • app.html loads; the HTML parser looks at each line and adds them to the C++ DOM from which we will produce the actual pixels of the webpage through the layout and render engine; the HTML parser will tee up the JS engine to run; this will give us access to the document 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 of accessor 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 the querySelector 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 to jsInput; 5) that JS object has properties that will allow us to work with the C++ object: i.e. onInput, value, etc.; 6) same for jsDiv; 7) assign handleInput to a function declaration; 8) in the jsInput JS object representation of the C++ element we will use the setter onInput to assign it to the function declaration handleInput. That will add a handler in the C++ element that will trigger the execution of that function when a user inputs something in the input C++ object element; 9) user action writes something in the input (ie. "Hi") and that triggers an event which issues a command to the C++ element to execute its handler; 10) value in the input C++ element will be populated with Hi; 11) the Events API will trigger the function referenced in the C++ element handler and add the call to handleInput in the callback queue; 12) the event 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 the call stack; 14) an execution context will be created with its memory; 15) in memory the variable post will be assigned to the value of the property value of the jsInput object; 16) we look inside the jsDiv object for the setter textContext which is set to the value of post; 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;
Enter fullscreen mode Exit fullscreen mode
  • 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 the handleClick and adds it to the call stack; handleClick is called and an execution context is created; the value setter of jsInput 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, the value property of the jsInput object will be assigned to post, and that string will be set to the textContent setter, which updates the C++ element, which updates the screen.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .