post-css-position

Important Facts about JavaScript and Browser that Make you an Expert in 2019

Because JavaScript runs in a browser, Web developers naturally should pay special concern for browsers.

This is the third article in the JavaScript Definitive Guide. Previous articles are:

Popular Web Browsers

Currently the mainstream browsers are divided into several categories:

  • IE 6~11, Edge: The most widely used browsers that always have poorly support of the W3C standard. From IE10, the IE series supports the ES6 standard.
  • Chrome: Google’s Webkit-based browser, built upon a very powerful JavaScript engine – V8. Since Chrome is always self-upgrading as soon as it is installed, the version of Chrome should not become a matter. The latest version always supports ES6.
  • Safari: Apple’s Mac system comes with a Webkit-based browser, called Safari. It supports ES6 from the 6.1 version of Mac OS X 10.7 Lion. The latest OS X 10.11 El Capitan comes with a Safari of version 9.x, which has good implementation of the ES6 standard.
  • Firefox: Mozilla’s own Gecko kernel and JavaScript engine OdinMonkey. The early Firefox was released by version, and finally it was smart to learn Chrome’s practices for self-upgrade and always up to date.
  • Browsers in Mobile Devices: At present, the two camps of iOS and Android on mobile devices mainly use Apple’s Safari and Google’s Chrome. Since both browsers adopt the Webkit kernel, HTML5 is widely used on mobile phones. The support for the ES6 standard support is also very good. All the latest versions support ES6.

Other browsers such as Opera are automatically ignored because their market share is too small.

Different browsers vary in the support of JavaScript. Mainly, some APIs have different interfaces, such as AJAX and File interfaces. For the ES6 standard, different browsers have implemented different features.

When writing JavaScript, you should take into account the differences between Web browsers. Try to make sure the same JavaScript code will run in different browsers.

Objects that Connect JavaScript and Browser

JavaScript can get a lot of objects provided by the browser, and then operate the objects.

Window

The window object not only acts as the global scope, but also represents the browser window.

It has properties innerWidth and innerHeight that get the internal width and height of the browser window. The internal width and height is the width and height of a web page after removing the placeholder elements such as the menu bar, toolbar, and border.

Compatibility: IE<=8 do not support this feature.

'use strict'; // You can adjust the browser window size to try:
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);

Correspondingly, there are outerWidth and outerHeight property, which can used to acquire the width and the height of the browser window.

Navigator

The navigator object contains information about the browser. The most common properties include:

  • navigator.appName: browser name;
  • navigator.appVersion: browser version;
  • navigator.language: the language set by the browser;
  • navigator.platform: the type of the operating system;
  • navigator.userAgent: A User-Agent string set by the browser .
'use strict';
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);

Please note that the navigator information can be easily modified by the user, so the value read by JavaScript is not necessarily correct. Many beginners want to write different versions of code for different browsers, and like to use the if statement to check browser version, for example:

var width;
if (getIEVersion(navigator.userAgent) < 9) {
    width = document.body.clientWidth;
} else {
    width = window.innerWidth;
}

But this is inaccurate. Meanwhile, it is difficult to maintain the code. The correct way is to take full advantage of JavaScript’s ability of returning undefined for properties that do not exist.

Use the short-circuit operator || directly:

var width = window.innerWidth || document.body.clientWidth;

Screen

The screen object represents the information of the screen. Commonly used attributes are:

  • screen.width: the width of the screen, in pixels;
  • screen.height: the height of the screen, in pixels;
  • screen.colorDepth: the number of color bits, such as 8, 16, 24.
'use strict';
console.log('Screen size = ' + screen.width + ' x ' + screen.height);

Location

The location object represents the URL information of the current Web page. For example, a full URL:

"http://www.example.com:8080/path/index.html?a=1&b=2#TOP"

We can use location.href to obtain the full URL. To get the value of each part of the URL, you can write:

location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'

To load a new page, you can call location.assign(). Besides, location.reload() is a very convenient function if you want to reload the current page.

'use strict';
if (confirm('Reload current page ' + location.href + '?')) {
    location.reload();
} else {
    location.assign('/'); // Set a new URL
}

Document

The document object represents the current page. In the browser, HTML is organized in the form of a DOM tree structure. The document object is the root node of the entire DOM tree.

The title property of document is the text inside <title>xxx</title> in the HTML document. It can be changed dynamically:

'use strict';
console.log(document.title)
document.title = 'Enjoy this tutorial!';

Please observe the change of the title in the browser window.

To find a node in the DOM tree, you need to start looking from the object document. The most common way is based on ID and Tag Name.

Let’s prepare the HTML data first:

<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
    <dt>Mocha</dt>
    <dd>Hot Mocha Coffee</dd>
    <dt>Juice</dt>
    <dd>Freshly squeezed apple juice</dd>
</dl>

With the methods getElementById() and getElementsByTagName(), we can obtain a DOM node by ID or a group of nodes by Tag name:

'use strict';
var menu = document.getElementById('drink-menu');
var drinks = menu.getElementsByTagName('dt');
var i, s;
s = 'The provided drinks are:';
for (i=0; i<drinks.length; i++) {
    s = s + drinks[i].innerHTML + ',';
}
console.log(s);

The output will be:

Mocha, Juice

The document object also has a cookie attribute that gets the current page’s cookie.

A cookie is a key-value identifier sent by the server. Because the HTTP protocol is stateless, the server has to distinguish between users using the cookies. For instance, when a user successfully logins in, the server sends a cookie to the browser, e.g., user=ABC23(an encrypted string). When the browser accesses the website, the cookie is attached to the request header, and the server can distinguish the user according to the cookie.

Cookies can also store settings for a website, such as the language of the page, and so on.

JavaScript can read the cookie of current page with document.cookie:

document.cookie;

Since JavaScript can read the cookie of a Web page, and the user’s login information usually exists in the cookie, this poses a huge security risk. Because the importation of third-party JavaScript code in the HTML page is allowed:

<!-- Current page is on www.example.com -->
<html>
    <head>
        <script src="http://www.foo.com/jquery.js"></script>
    </head>
    ...
</html>

If there is malicious code in the imported third-party JavaScript files, the www.foo.com website will directly obtain the user login information of the website www.example.com.

In order to solve this problem, the server can use the httpOnly option when setting a cookie. And that cookie will not be read by JavaScript. This behavior is implemented by the browser, and all mainstream browsers support the httpOnly option. IE supports it from IE6 SP1.

IMPORTANT: To ensure security, the server should always stick to it when setting up cookies httpOnly.

History

The history object holds the history of the browser, and JavaScript can call the methods back() or forward(), which is equivalent to the behaviour of clicking the “back” or “forward” button in the browser.

This object belongs to historical objects. For modern Web pages, due to the extensive use of AJAX and page interaction, simple and rude calls to history.back() may make users feel very angry.

When a novice starts designing a Web page, he/she likes to call history.back() when a user is successfully registered, trying to return to the page before login. This is a wrong way. Try to use the AJAX. We will cover that topic in later articles.

WARNING: Never use the history object.

Manipulating DOM

Since the HTML document is parsed to a DOM tree by the browser. To change the structure of the HTML, you need to manipulate the DOM through JavaScript.

Always remember that the DOM is a tree structure.

Operating a DOM node is actually just a few things:

  • Update: updating the content of a DOM node is equivalent to updating the content of the HTML tag represented by the DOM node.
  • Traversing: Traversing the child nodes under the DOM node for further operations.
  • Add: Add a child node under the DOM node, which is equivalent to dynamically adding an HTML node.
  • Delete: Deleting the node from the HTML is equivalent to deleting the content of the DOM node and all the child nodes it contains.

Before handling a DOM node, we need to get the DOM node first. This can be done in various ways. The most common methods are 

  • document.getElementById(), ID selector
  • document.getElementsByTagName(), Tag selector
  • document.getElementsByClassName(), as well as CSS class selectors.

Since the ID is unique in the HTML document, the document.getElementById() method directly locate a unique DOM node if there is a HTML tag with the specfied ID.

document.getElementsByTagName() and document.getElementsByClassName() always returns a set of DOM nodes.

To accurately select the DOM, you can first locate the parent node and then select from the parent node to narrow the scope.

E.g:

// Return the node with the ID 'test':
var test = document.getElementById('test');

// Locate the node with the ID 'test-table' first, then return all the internal tr nodes:
var trs = document.getElementById('test-table').getElementsByTagName('tr');

// Locate the node with the ID 'test-div' first, and then return all the nodes that with a red class:
var reds = document.getElementById('test-div').getElementsByClassName('red');

// Get all the direct children under the node test:
var cs = test.children;

// Get the first and last child nodes under node test:
var first = test.firstElementChild;
var last = test.lastElementChild;

Another way is to use querySelector() and querySelectorAll(). You need to understand the selector syntax, and then use the condition to get the node, which is more convenient. We will explain the syntax in detail later.

// Get the node with the ID q1 through the querySelector:
var q1 = document.querySelector('#q1');

// Get all the qualified nodes in the q1 node through querySelectorAll:
var ps = q1.querySelectorAll('div.highlighted > p');

Note: The lower version of IE<8 does not support querySelector and querySelectorAll. IE8 has limited support for this feature.

Strictly speaking, we refer here to the DOM node Element, but DOM node is actually Node. In HTML, Node includes ElementCommentCDATA_SECTION, the root node Document and others. But most of the time we only care about Element, since it controls the page structure. Other types of Node can be ignored. The root node Document is automatically bounded as a global variable document.

Exercise 1

For the following HTML structure:

<!-- HTML Structure -->
<div id="test-div">
  <div class="c-red">
    <p id="test-p">JavaScript</p>
    <p>Java</p>
  </div>
  <div class="c-red c-green">
    <p>Python</p>
    <p>Ruby</p>
    <p>Swift</p>
  </div>
  <div class="c-green">
    <p>Scheme</p>
    <p>Haskell</p>
  </div>
</div>

Please select the node(s) with the specified criteria:

'use strict';
// Select <p id="test-p">JavaScript</p>:
var js = ???;

// Select <p>Python</p>, <p>Ruby</p>, <p>Swift</p>:
var arr = ???;

// Select <p>Haskell</p>:
var haskell = ???;

// Test:
if (!js || js.innerText !== 'JavaScript') {
    alert('Select JavaScript failed!');
} else if (!arr || arr.length !== 3 || !arr[0] || !arr[1] || !arr[2]
           || arr[0].innerText !== 'Python' || arr[1].innerText !== 'Ruby'
           || arr[2].innerText !== 'Swift') {
    console.log('Select Python,Ruby,Swift failed!');
} else if (!haskell || haskell.innerText !== 'Haskell') {
    console.log('Select Haskell failed!');
} else {
    console.log('Pass!');
}

Updating DOM

After getting a DOM node, we can update it.

You can directly modify the text of a node in two ways:

One is to modify the innerHTML property. This method is very powerful. It can not only modify the text content of a DOM node, but also modify the subtree inside the DOM node directly through the HTML fragment:

// Get <p id="p-id">...</p>
var p = document.getElementById('p-id');
// Set the text to abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// Set the HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// The internal structure of <p>...</p> has been modified

Use innerHTML should pay attention to whether to write HTML directly. If the string being written is users’ input, be careful to encode the characters to avoid XSS attacks.

The second way is a modification of the innerText or the textContent attribute, which automatically encodes the string in HTML, ensuring that no HTML tags can be set:

// Get <p id="p-id">...</p>
var p = document.getElementById('p-id');
// Set the text:
p.innerText = '<script>alert("Hi")</script>';
// HTML is automatically encoded and cannot be set to a <script> node.

The difference between the two is that when the innerText property is read, the text of a hidden element is not returned, and textContent returns all text. Also note that IE<9 does not support textContent.

Modifying CSS is also a common operation. The style attribute of a DOM node corresponds to all CSS and can be directly obtained or set. CSS allows names such as font-size, but it is not a valid property name for JavaScript. It needs to be rewritten as a camel-style naming in JavaScript, e.g., fontSize:

// Get <p id="p-id">...</p>
var p = document.getElementById('p-id');
// Set CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';

Exercise 2

For the following HTML structure, try to get the specified node and modify it.

<div id="test-div">
    <p id="test-js">javascript</p>
    <p>Java</p>
</div>
'use strict';
// Get the <p id="test-js">javascript</p> node:
var js = ???;

// TODO: Modify the text to JavaScript:

// TODO: Modify the CSS to: color: #ff0000, font-weight: bold

// Test:
if (js && js.parentNode && js.parentNode.id === 'test-div' && js.id === 'test-js') {
    if (js.innerText === 'JavaScript') {
        if (js.style && js.style.fontWeight === 'bold'
            && (js.style.color === 'red' || js.style.color === '#ff0000'
            || js.style.color === '#f00' || js.style.color === 'rgb(255, 0, 0)')) {
            console.log('Pass!');
        } else {
            console.log('Failed!');
        }
    } else {
        console.log('Failed!');
    }
} else {
    console.log('Failed!');
}

Inserting DOM

What should I do if we have a DOM node and want to insert a new DOM into this DOM node?

If the DOM node is empty, for example, <div></div> then, the content of the DOM node can be modified directly use innerHTML = '<span>child</span>', which is equivalent to “inserting” the new DOM node.

If the DOM node is not empty, then you can’t do this because innerHTML will replace all the original children.

appendChild

In such cases, there are two ways to insert a new node. One is to use appendChild, adding a child node to the last child of the parent node. E.g:

<!-- HTML -->
<p id="js">JavaScript</p>
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

To add <p id="js">JavaScript</p> to the end of <div id="list">:

var js = document.getElementById('js'),
    list = document.getElementById('list');
list.appendChild(js);

Now the HTML structure becomes:

<!-- HTML Structure -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
    <p id="js">JavaScript</p>
</div>

Since the js node we inserted already exists in the current document tree, this node will first be deleted from the original location and inserted into the new location.

More often we will create a new node from zero and insert it into the specified location:

var list = document.getElementById('list'),
    haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);

So we dynamically added a new node:

<!-- HTML -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
    <p id="haskell">Haskell</p>
</div>

It is useful to dynamically create a node and then add it to the DOM tree. For example, the following code dynamically creates a <style>node and adds it to the end of the node <head>, thus dynamically adding a new CSS definition to the document:

var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);

You can execute the above code in Chrome’s console to observe changes in page style.

insertBefore

What if we want to insert a child node before the specified location? You can use parentElement.insertBefore(newElement, referenceElement);, where the new element will be inserted before the referenced element.

Still take the above HTML as an example, assuming we want to insert Haskell before Python:

<!-- HTML -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

Can write like this:

var list = document.getElementById('list'),
    ref = document.getElementById('python'),
    haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);

The new HTML structure is as follows:

<!-- HTML -->
<div id="list">
    <p id="java">Java</p>
    <p id="haskell">Haskell</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

The key point of insertBefore is to get the “reference child node”. Many times, you need to loop through all the children of a parent node, which can be implemented by iterating the children property:

var i, c,
    list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
    c = list.children[i]; // Get the i-th child
}

Exercise 3

For an existing HTML structure, reorder DOM nodes according to their content in alphabetical order.

<!-- HTML -->
<ol id="test-list">
    <li class="lang">Scheme</li>
    <li class="lang">JavaScript</li>
    <li class="lang">Python</li>
    <li class="lang">Ruby</li>
    <li class="lang">Haskell</li>
</ol>

Tips: Get all child nodes and sort them with the sort method.

'use strict';

// TODO: Add your codes here.

// Test:
;(function () {
    var arr, i,
        t = document.getElementById('test-list');
    if (t && t.children && t.children.length === 5) {
        arr = [];
        for (i=0; i<t.children.length; i++) {
            arr.push(t.children[i].innerText);
        }
        if (arr.toString() === ['Haskell', 'JavaScript', 'Python', 'Ruby', 'Scheme'].toString()) {
            console.log('test passed!');
        }
        else {
            console.log('Test failed: ' + arr.toString());
        }
    }
    else {
        console.log('Test failed!');
    }
})();

Deleting DOM

Deleting a DOM node is much easier than inserting it.

To delete a node, first obtain the node itself and its parent node, then call removeChild on the parent node to delete it:

// Get the node:
var self = document.getElementById('to-be-removed');
// Get its parent node:
var parent = self.parentElement;
// Delete
var removed = parent.removeChild(self);
removed === self; // true

Note that the deleted node is not in the document tree, but it is still in memory and can be added to another location at any time.

When you iterate over the child nodes of a parent node and delete them, be aware that the children property is a read-only property and it is updated in real time as the child nodes change.

For example, for the following HTML structure:

<div id="parent">
    <p>First</p>
    <p>Second</p>
</div>

When we delete the child nodes with the following code:

var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // <-- ERROR

Browser error: parent.children[1]not a valid node. The reason is that when the <p>First</p> node is deleted, the number of child nodes has changed from 2 to 1, and the index [1]no longer exists.

Therefore, when deleting multiple nodes, be aware that the children attributes are changing all the time. You should delete them from the last one.

Exercise 4

For the following HTML structure, delete nodes that are not related to web development technology.

<!-- HTML -->
<ul id="test-list">
    <li>JavaScript</li>
    <li>Swift</li>
    <li>HTML</li>
    <li>ANSI C</li>
    <li>CSS</li>
    <li>DirectX</li>
</ul>
'use strict';

// TODO: Add your code here.

// Test:
;(function () {
    var arr, i,
        t = document.getElementById('test-list');
    If (t && t.children && t.children.length === 3) {
        arr = [];
        for (i = 0; i < t.children.length; i ++) {
            arr.push(t.children[i].innerText);
        }
        if (arr.toString() === ['JavaScript', 'HTML', 'CSS'].toString()) {
            console.log('test passed!');
        }
        else {
            console.log('Test failed: ' + arr.toString());
        }
    }
    else {
        console.log('Test failed!');
    }
})();

Closing Words

Thanks for reading this article.

We have learned so much in previous articles. You should be proud of yourself!

If you encounter any problem, please feel free to leave a message.

Looking forward to seeing you in the next post.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *