Jeremy Troy Suchanski

Web Frontend Development II

Gary James

April 25, 2022

L04 Readings Notes

Ch10: Testing and Debugging (Notes)

System error: there’s a problem with the system or external devices with which the program is interacting.

Programmer error: the program contains incorrect syntax or faulty logic; it could even be as simple as a typo.

User error: the user has entered data incorrectly, which the program is unable to handle. It might even be argued that user errors are in fact also programmer errors, because the program should be designed in a way that prevents the user from making the error.

strict mode on a per-function basis: add “use strict” inside a function and it will then only be applied to code inside that function. This is the recommended way to use strict mode because if the whole file is in strict mode and you’re using anybody else’s code, there’s no guarantee they’ve coded in strict mode.

modules:  are self-contained pieces of code that are in strict mode by default, so the 'use strict' declaration is not required. (Introduced by ES6)

sloppy mode: Not using strict mode because it’s forgiving of sloppy programming practices.

browser sniffing: is the “old-school” way of checking for browser support by using the string returned by window.navigator.userAgent property to identify the user’s browser. The relevant methods can then be used for that browser. This approach is not recommended because the user agent string cannot be relied upon to be accurate and this would extremely difficult to implement and maintain with the constant state of change.

alert debugging breakpoint: The most basic form of debugging is to use the alert() method to show a dialog at certain points in the code. Because alert() stops a program from running until OK is clicked, it allows us to effectively put breakpoints in the code that let us check the value of variables at that point to see if they’re what we expect them to be. Using alerts for debugging was the only option in the past, but JavaScript development has progressed since then and their use is discouraged for debugging purposes today.

console debugging: a console object provides a number of methods for logging information and debugging.

console.log(): can be used to log the position & value of a variable in a function.

console.trace(): will log an interactive stack trace in the console that will show the functions that were called in the lead up to an exception occurring while the code is running.

console.count(): displays the number of times the line was called. It can be passed an optional name parameter to identify the counter. It can be used in #1 Chrome’s Developer Tools, #2 Firebug and #3 the IE11 F12 tools.

console.dir(node): displays an interactive list of all object properties — like you would normally see in the DOM panel. It can be used in #1 Chrome’s Developer Tools, #2 Firebug, #3Firefox’s Web Developer console and #4 the IE11 F12 tools. Code Example:

console.dir( document.getElementById("test") );

console.dir

console.dirxml(node): displays the node and all descendants — like a section of the HTML panel. It can be used in #1 Chrome’s Developer Tools, #2 Firebug and #3 the IE11 F12 tools. Code Example:

console.dirxml( document.getElementById("test") );

console.dirxml

console.table(): outputs an object or array in a tabular format which can be inspected and re-ordered by clicking the table headings. You can also specify an array as a second parameter to define different column headings. It can be used in #1 Chrome’s Developer Tools and #2 Firebug. Code Example: 

var browsers = [

         { name: "Internet Explorer", vendor: "Microsoft", version: "11" },

         { name: "Chrome", vendor: "Google", version: "31" },

         { name: "Firefox", vendor: "Mozilla", version: "26" },

         { name: "Safari", vendor: "Apple", version: "7" },

         { name: "Opera", vendor: "Opera", version: "18" }

];

 

console.table( browsers );

console.table

#3 Chrome #4 Safari

debugger: command keywork & one of the most useful commands that will create a breakpoint in your code that will pause the execution of the code and allow you to see where the program is currently up to. You can also hover over any variables to see what value they hold at that point. Clicking the “play” button restarts the program.

Remove Debugging Code Prior to Shipping: Remember to remove any references to the debugger command before shipping any code, otherwise the program will appear to freeze when people try to use it!

7 error objects used for specific errors: These error objects can also be used as constructors to create custom error objects. Code Example: const error = new TypeError('You need to use numbers in this function');)

  1. EvalError: is not used in the current ECMAScript specification and is only retained for backwards compatibility. It was used to identify errors when using the global eval() function.
  2. RangeError: is thrown when a number is outside an allowable range of values.
  3. ReferenceError: is thrown when a reference is made to an item that doesn’t exist. For example, calling a function that hasn't been defined.
  4. SyntaxError: is thrown when there’s an error in the code’s syntax.
  5. TypeError: is thrown when there’s an error in the type of value used; for example, a string is used when a number is expected.
  6. URIError: is thrown when there’s a problem encoding or decoding the URI.
  7. InternalError: is a non-standard error that is thrown when an error occurs in the JavaScript engine. A common cause of this too much recursion.

error object properties: are often used inconsistently across browsers. The only properties that are generally safe to use are:

  1. name property: returns the name of the error constructor function used as a string, such as 'Error' or 'ReferenceError'.
  2. message property: returns a description of the error and should be provided as an argument to the Error constructor function.
  3. stack property: will return a stack trace for that error. This is a non-standard property and it’s recommended that it is not safe to use in production sites.

Throwing Exceptions: It’s possible to throw your own exceptions using the throw statement, causing the execution of the program to stop.

error object: It is best practice to throw these. This can then be caught in a catch block. (example: throw new Error('Something has gone badly wrong!');)

try block: used if we suspect a piece of code will result in an exception. It will run the code inside the block as normal, but if an exception occurs it will pass the error object that is thrown onto a catch block.

catch block: The code inside the catch block only runs if an exception is thrown inside a try block. The error object is automatically passed as a parameter to the catch block. Allowing programmers to query the error name, message and stack properties, and deal with it appropriately.

finally block: can be added after a catch block. It will always be executed after the try or catch block, regardless of whether an exception occurred or not. It is useful if you want some code to run in both cases.

Test-driven development workflow: This is often referred to as the “red-green-refactor” cycle of TDD, as failing tests usually show up as red, and tests that pass show as green. #1 Write tests (that initially fail) #2 Write code to pass the tests #3 Refactor the code #4 Test refactored code #5 Write more tests for new features

Refactoring: the process of restructuring computer code without changing or adding to its external behavior and functionality.

 

How Single-Page Applications Work (Notes)

 

Internal state SPA: limited because there is only one “entry”. A single entry means that you always start at the root when you enter the application. When you navigate within an internal-state app, there is no external representation.

Location-based SPA: you can share a link and be confident that anyone opening that link will see the same thing as you because the location is always updating as you navigate (assuming they have the same authorization to view the content).

Location Primer: SPAs use window.location. This allows you to interact with the different parts of the URL without having to parse it yourself. Example: 

imageOnly three of the location object’s properties are important for an SPA: pathname, hash, and search (commonly called a query string).

Pathname: typically the most important of the three properties because it is the one used for determining what content to render.

Search & Hash: useful for providing additional data.

Route Matching: comparing the current location (usually only its pathname) against the router’s routes to find one that matches. Single-page application generally rely on a router. After matching a route, the router will trigger a re-render of the application.

Routers: are made up of routes, which describe the location that they should match. These can be static (/about) or dynamic (/album/:id, where the value of :id can be any number of possibilities) paths.

In-App Navigation: the browser has native behavior attached to the event to trigger navigation. However, you can also attach your own click handler and override the native behavior (using event.preventDefault() in modern browsers).

How Browsers Handle Locations: Each browser tab has a “browsing context.”

browsing context: maintains a “session history.” It also keeps track of which entry is currently active.

session history: an array of location entries. An entry stores information about a location: its URL, the associated Document, serialized state, and a few more properties.

entry’s index:  specifies its position in the session history array.

window.document: a browser tab’s active Document. When a browser navigates, a request is sent to a server and the browser uses the response to create a Document object. The Document describes the page (the DOM) and provides methods for interacting with it.

Session History: Each navigation makes a request to a server and creates a new entry (including a new Document). You can use the back & forward buttons to navigate backwards and forwards. When you click a link and there are entries after the current entry, they will be dropped unless you navigate to the exact same location as the current location.

History API’s core functions: accessed via window.history.

  1. pushState() & replaceState(): have the same function signature:

#1 The first argument is state. If you do not want to pass any state, pass null. It may be tempting to keep application state here, but there are some caveats that will be discussed later.

#2 The second is a title string, but no browsers actually use this yet.

#3 The third argument is the path that you want to navigate to. This can be a full URL, an absolute path, or a relative path, but it must be within the same application (same protocol and hostname). If it is not, a DOMException error will be thrown.

  1. pushState(): adds an entry to the session history after the current entry. If there are entries after the current entry, those are lost when you push a new location.
  2. replaceState(): replaces the current entry in the session history. Any entries after the current entry are unaffected.
  3. go(): the programmatic way of performing the same task as the browser’s forward and back buttons. It takes a single argument, a number, which is the number of entries away from the current one you want to navigate. Positive numbers go forward, negative numbers go backwards, and zero (or undefined) reloads the page.
  4. history.back(): is the same as history.go(-1)
  5. history.forward(): the same as history.go(1)

history.state: accesses the current entry’s state.

window.addEventListener('popstate', event => {

   // let the router know navigation happened!

}, false);

The session history will already be updated by the time the event listener is called, so we just need to let the router know that the location has changed.

The Problem with Single Page Apps (Notes)

Steps to serve all code from a single index.html file

#1 Configure the server to point all paths on a domain back to the root index.html file.

#2 Suppress the default behavior when someone clicks a link that points to another page in the app.

#3 Use more JavaScript—history.pushState()—to update the URL without triggering a page reload.

#4 Match the URL against a map of routes, and serve the right content based on it.

#5 If your URL has variable information in it (like a todolist ID, for example), parse that data out of the URL.

#6 Detect when someone clicks the browser’s back button/forward button, and update the URL and UI.

#7 Update the title element on the page.

#8 Use even more JavaScript to dynamically focus the content area when the content changes (for screen-reader users).

// Get the app container

var app = document.querySelector('[data-app]');

// Determine the view/UI

var page = app.getAttribute('data-app');

// Render the correct UI

if (page === 'lists') {

            // Render the homepage...

}

if (page === 'settings') {

            // Render the settings page...

}

#1 Support for the browser’s forward and backward buttons are baked in. You don’t need to do anything to make that work.

#2 You don’t need to intercept clicks and determine if the clicked link points to an internal link or an external one. You just let them all resolve.

#3 You don’t need to use complex regex patterns or another library to parse the URL and determine which view or UI to render. It’s baked into the markup already.

#4 It’s simpler and easier to implement.

Solution to argument: use front end performance best practices and load static HTML files, page loads using this approach feel nearly instantaneous.

 

HTML5 Template Element (Notes)

 

Anything inside of a <template> tag gets parsed just like regular HTML, except

#1 It doesn't get rendered. #2 <script> tags inside of it don't get run. #3 <style> tags inside of it don't get evaluated. #4 It doesn't load any external resources (so you won't see any requests for the contents of <img> or <embed> tags). #5 It can be accessed as a DocumentFragment instance via the special content property of the <template> element.

content property: #1 DocumentFragment instances provide an API for manipulating their contents that's largely the same as the global document object, so you can manipulate them like their own separate document. #2 inserting a DocumentFragment instance into the DOM is really fast compared to manipulating an element's innerHTML property (or using insertAdjacentHTML()), because the DOM structure already exists in memory, so it just needs to get linked into the DOM tree.

cloneNode method: when called with true as an argument, creates a completely new DocumentFragment instance that's an exact copy of the original.

querySelector method on DocumentFragment class: get a real Element or NodeList back that you can then manipulate just like if you had requested elements in the page itself.

ID attributes: it's safest to just avoid them in the template itself and populate them from your code as you're using the template.

Injecting HTML5 templates into other HTML5 templates: this can be done because DocumentFragment works almost the same as the DOM. This means you can create complex layouts composed of multiple HTML5 templates with little effort.

HTML5 template element advantages over JavaScript templating libraries:

#1 the templates (including the associated JavaScript) tend to be smaller than the equivalent pre-compiled template code.

#2 they render faster when inserted into the DOM

#3 you need no special tools to deal with building or checking them, because they're just plain HTML and plain JavaScript.

HTML5 <template> element Support: Every major browser released in the last 3 years fully supports it, and most released in the past 5 years do too. (About 95% market share for availability.) [Internet Explorer lacks support (and Opera Mini and the BlackBerry browser, but neither of those has enough market share to matter in many cases).]