The purpose of these challenges is to gain an under-the-hood understanding of building user interfaces in the web browser. Each challenge will go hand-in-hand with a section of The Hard Parts of UI Development Course by Will Sentance from Frontend Masters.
Throughout these challenges, we will cover topics including HTML, JavaScript, & the DOM API in the web browser; one-way data binding and "state-driven" views; the virtual DOM and "composable" UI; and performance optimizations.
In UI Engineering we have 2 simple goals:
Enable our users to interact with the content they see, and then change it.
First, let's take a look at the index.html file. Open it in the browser.
open index.html. This should automatically open the file in your default web browser.What do you see? Your tab or window should show the text you see inside the <title> in index.html, but the page itself should be totally blank. You can also inspect the page and view your html code in Chrome DevTools. For more info about how to use Chrome DevTools, check out their docs.
andinindex.htmland refresh the page in the browser. (Usecommand + /orcontrol + /to comment/uncomment code) When our HTML loads now, it will populate the [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) and we can see our input box on the page! Feel free to add some text to your
` and refresh so that you can see it render to the page as well.
Next, type something into your input box. The pixels on the page show what you have typed - Remember that the underlying DOM data for the input node has also updated to reflect your change!
Uncomment the line containing the stylesheet in index.html to add some pizazz to our page - remember, when this loads, it will populate the CSSOM, similar to how our HTML populates the DOM with elements to render.
Now what? Even if we can change the data on the DOM, we can't do anything with that data or access it directly. We would still need some kind of logic to make something happen in the event that a user types in our input or a button is clicked. Our html is only being loaded once and we can't run any code in the DOM. Therefore, we need JavaScript to create, save, and update data.
<script> tag in index.html. This script acts as a link between the html and the code in the linked JavaScript file. When our script loads with our HTML in the browser, the JavaScript engine will start running and allow us to run our JS code in the browser directly (and access everything else we get with the JS runtime, including memory to store data).Extension Challenge: Document Object
- In our JavaScript runtime, we have access to some very useful APIs, including document. In part1.js, if you console.log(document), you will see an object in the browser dev tools console. This document object also has a hidden property that acts as a link to the DOM.
part1.js, declare a variable 'post' and initialize it to a string that is your name. Congrats - we have data! Now, how can we use it to update the DOM and what we see in our view?If you're unfamiliar with DOM manipulation, take a look at the docs on MDN, and particularly the querySelector method (a hidden property on the document object), which allows us to query the DOM to select a specific DOM element and create an object in our JavaScript memory with a hidden "link" to access it. That object will have "property-methods" to get or set the data on that DOM element.
Query the DOM to select the input node on the DOM and assign the resulting JS object to a variable called 'jsInput'. Now do the same for the the div node on the DOM and assign the resulting JS object to a variable called 'jsDiv'.
Let's use our JS data to update the DOM data. Our jsDiv object will have some "getter/setter" property-methods, including textContent. Set the jsDiv.textContent to be the value of our post variable. Refresh the page in the browser, and you should see that string as the text in the div.
Now let's update our JavaScript data based on our user interactions. First, edit your variable declaration for post at the top of the file to initialize it to an empty string instead.
Declare a function 'handleInput' that will "handle" what we want to do with what the user types in the input. Use the value getter/setter method available on our jsInput object. When the user types into the input, reassign post to hold that text.
Set the textContent on the div to to be the value of post here in handleInput instead.
event handler that will run our handleInput function in the event that a user types in input. There are several ways to do this, but one is the oninput property on the jsInput object. Us this to set handleInput on our input DOM element as a callback.We should now have a full User Interface (UI) which addresses our two main goals:
Now that we have a simple application our users can interact with, how can we make our UI more sophisticated? One thing we can do is implement one-way data binding, a popular paradigm for tackling the essential engineering challenge of keeping the 'view' consistent with (and dependent on) our underlying data.
In index.html, edit the script tag to use part2.js as the src file.
What if we wanted to add some placeholder text to the input box? In your JS file, set the value of the input box to be the string 'What's on your mind?' and refresh the browser.
Click into the input and type something. Notice that the user needs to manually delete the placeholder text that is in the input box every time they want to type in a new input.
'handleClick' for the input box which sets the value inside the input box to an empty string when the user clicks in it to type. HINT: Make sure to add handleClick as a callback to the input element.Now we're changing our 'view' based on several different possible user interactions. How can we make these changes more predictable? Let's restrict every change to view to be via:
a run of a single dataToView convertor function.
To do this, let's create a function 'dataToView'. It should:
Update the value in our input box on the DOM to be whatever post currently is. HINT: post should now start off as undefined, and if it is undefined when dataToView is invoked, we should populate the value in the input box to be the string containing 'What's on your mind?'. This also means dataToView needs to be invoked when the page is first loaded.
Update the content of the div on the DOM to be our current data.
Now that the dataToView function uses our JS data to update the DOM content, we need to make sure it is invoked after our data changes. What adjustments can we make to our event handlers so that they only make changes to the underlying data, and what should happen as soon as a change is made?
While it may not be efficient, we can have our dataToView run so often that any change to data will instantly propagate using a setInterval function at a rate that is close to the browser refresh rate.
Extension Challenge: Submit Button
Add a submit button that will create and save a new post. How can you implement this kind of functionality in your application? In addition, set up some logic for creating divs that hold previous 'posts' so that the user is able to see all their previous posts.
In our first two UI Hard Parts challenges, we displayed content and let our users interact with it to change the underlying data, and implemented one-way data binding to ensure our view is always dependent on and consistent with that data.
In part3.js, we are describing key parts of the UI in dataToView: the contents (data) and how to display it. What if we described the elements as well? This would make our funnction a complete description of the data and view.
index.html, edit the script tag to use part3.js as the src file. Then comment out or delete the input and div elements.Instead, let's create our DOM elements with JavaScript. A UI Component is a function that fully creates elements and relates their data to the view.
In part3.js, uncomment the function 'component' and build it out. You will see it has already been started for you.
The first and last lines of code in this function are there to make sure that once your user has clicked in the input, they will stay "clicked" and able to type as component is repeatedly called (hint!) to auto-update the DOM.
This function will combine all of our previous functionality into one:
jsInput and jsDiv variable declarations at the top of the file, but unitialized - you will reassign the values to be the new objects you create.component, we should no longer need our dataToView function.In order to achieve this last step, we need to attach or append them to the body of the DOM. There a a couple of ways to do this, but to make sure we are replacing our nodes with an updated version when our data changes (instead of accidentally attaching multiple copies of our input or div), use replaceChildren.
Now, our view should look the same as it did before, but our code should consist of 3 parts - our declared variables at the top of the file, the component function (where all of our functionality will be now), and setInterval.
However, our code is still fairly imperative - just glancing at it, it's hard to tell what our view will look like. The more "visual" our code is (like HTML), the easier it is for us to work with as developers.
For example - imagine we wanted the text content of a div element to be the string "I live in (user's location)!". We can use the concat method to build out our string piece by piece:
let userLocation = 'LA';
let textToDisplay = 'I live in ';
textToDisplay = textToDisplay.concat(userLocation);
textToDisplay = textToDisplay.concat('!');
However, this is not very visual or declarative. So instead, we can use a template literal with string interpolation:
textToDisplay = `I live in ${userLocation}!`;
As we can see, this mirrors what our visual/graphic output will be. Could we do something similar with our main code creating visual elements?
Let's start with a "unit of code" representing each piece of our view.
const divInfo = ['div', `Hi, ${myName}!`];
In this example, our divInfo is an array with the details of a DOM element - just by looking at our code, we can tell what it will look like (the type (div) and the text that will display inside).
part3.js, write a function 'convert' that will take in a node (an array of details like our 'divInfo' example above). This function should use that array to create a new DOM element and set its content, and return its linked JavaScript object.With this function, we can now produce DOM elements from any list of info that visually mirrors what we will see in our view.
Next, let's create our virtual DOM - blocks of code (or a list) representing each piece of our view.
Declare a global variable 'vDOM' but do not initialize it.
Write a function 'createVDOM' that returns a list (array) containing the following two sub-arrays:
[
'input',
myName,
function handle() {
myName = jsInput.value;
},
][('div', `Hello, ${myName}!`)];
Notice that in each sub-array, index [0] is the type of DOM element we want to create, index [1] has details of what we want that element to contain or display, and index [2] is an event handler callback function.
Edit the convert function to make sure it will properly convert each sub-array in our vDOM into a DOM element, accounting for the different properties on different element types, as well as setting any event handlers.
Declare a function 'updateDOM'. This function will:
vDOM variable to be the returned result of invoking createDOM().convert function with our new vDOM details to update jsInput and jsDiv with a new DOM element (linked through a JS object).$ claude mcp add UIHP-Challenges-April23 \
-- python -m otcore.mcp_server <graph>