DOM AND EVENTS

DOM TRAVERSING

Old common ways to traverse the DOM:

getElementById always run as: document.getElementById
getelementsByTagName could be on any node…document as well.
getElementsByClassName

New way: Selectors API

querySelector() and querySelectorAll

var mainDiv = document.querySelector("#main"); //
var pTags = document.querySelectorAll("p"); // all p tags
var mainPtags = mainDiv.querySelectorAll("p"); // all p tags inside the main div
var tags = document.querySelectorAll("p.tag"); //all p tags with the class "tag"

USING ARRAY METHODS ON A NODELIST

The nodelist you get when using querySelectorAll() is not of the same prototype as array and lacks many of its methods(functions).

Because you often want many of the built in functions that exists for arrays to manipulate a nodelist there are a few ways

to copy over the methods from the Array prototype to the Nodelist prototype.

Extending the Nodelist(Anti-pattern)

NodeList.prototype.forEach = Array.prototype.forEach;

This should not be used because of many reasons:

You might run into trouble if the script is running a different environment with other scripts,

future versions of EcmaScript might have a built in forEach method on the prototype that may work differently or make it obsolete.

A better way todo it is to borrowing methods from the Array prototype and send in our nodelist as a argument.

Array.prototype.forEach.call(nodes,function(node){});

Another way if you are doing a lot of array manipulation with the nodelist you simply turn it into an array:

nodes = Array.prototype.slice.call(nodes);

MANIPULATION OF THE DOM

Adding nodes

node.appendChild(newNode); //adds or moves
node.insertBefore(newNode, beforeNode);

Replacing nodes

node.replaceChild(newNode, oldNode);

Removing nodes

node.removeChild(oldNode);

Making clones

node.cloneNode(bool); //true makes a deep copy which means all the child elements will also be included (text,links etc);

CREATING NODES

document.createElement("");//create a new ELEMENT_NODE
document.createTextNode("");// Create a new TEXT_NODE
var newTag = document.createElement("p");
var newText = document.createTextNode("Cool text!");

newTag.appendChild(newText);
document.querySelector("#main").appendChild(newTag);

SHORTCUTS

innerHTML(ANTI-PATTERN)

  • Parses the string
  • Creates elements and text nodes
  • Removes all children from element
  • Less secure.
element.innerHTML = "<p>Some text</p>";

A better way:

textContent

  • Creates one new TEXT_NODE
  • Removes all children from element
  • element.textContent = “Text…”;

ATTRIBUTES

getAttribute("attribute name");
setAttribute("attribute name", value);

removeAttribute("attribute name");
var newTag = document.createElement("img");
newTag.setAttribute("src", "image/picture.svg");

//The browser fetches and caches the "picture.svg"
document.querySelector("#main").appendChild.(newTag);

STYLES (anti-pattern)

var node = document.querySelector("#discovery");
node.style.color = "#000";

Result:

<p id="discovery" style="color: #000">Hi</p>

CSS property: Identifier:

  • font-size fontSize
  • margin-left marginLeft
  • float cssFloat

Instead of mixing JS and CSS use classes instead:

  • classList.add,
  • classList.remove,
  • classList.toggle,
  • classList.contains // used to check if the class is set or not

TEMPLATES

Templates are used to avoid mixing html in the JS code.
FOR IE support:  Mustache, UNderscore Templates, handlebarsJS, Jade(Big in NODEJS)
The native HTML-tag is quite new but have good support in all browsers except IE. Edge does support it well though.

<template id="post-template">
	<p class="post">
		<ul>
			<li class="active">
				<a href="#"> The first link</a>
			</li>
			<li>
				<a href="#"> The first link</a>
			</li>
		</ul>
	</p>
</template>
var template = document.querySelector('#post-template');
var clone = document.importNode(template.content, true);//take whats in the template and get a reference to it but without rendering
// make sure it set to true to have a deep copy which is the most common way.
document.querySelector("body").appendChild(clone); // import the clone from the template and put it in the body, this will be rendered.

EVENT

  • user intialized
  • Mouse,scrolling,keyboard
  • Browser intialized
  • Page loaded, Dom changed
  • Network
  • Content loaded

ADD EVENT LISTENERS

“when this happens do that”

var a = document.querySelector("#theatag");
a.addEventListener("click", buttonClicked);

function buttonClicked(event) {
	console.log("you clicked the link!");
};

Another way with a anonymous function:

var a = document.querySelector("#theatag");
a.addEventListener("click", function(event){
	console.log("you clicked the link!");
})

REMOVE EVENT LISTENERS

Great way to avoid users to make multiple submits etc,

var a = document.querySelector("#theatag");
a.addEventListener("click", function buttonClicked(event){
	console.log("you can only click me once!");
	a.removeEventListener("click", buttonClicked);//removing the event listener after its been clicked.
});

PROPAGATION

var a = document.querySelector("#atag");
var p = document.querySelector("#ptag");

a.addEventListener("click", function(event) {

	console.log("you clicked the link!");
	event.stopPropagation();
});
p.addEventListener("click", function(event) {
	console.log("I wont be called!");
});

p.addEventListener("click", function(event){
	console.log("captured it!");
}, true); // by adding true it will be called..

CHECK WHAT TRIGGERED THE EVENT

var a = document.querySelector("#atag");

a.addEventListener("click", function(event){
	a === this; //look out for this this
	a === event.target; // true
	this === event.target; //true
	//event.target is useful to use
});

A bad way todo it for prestanda reasons as it will create multiple event listeners:



<div id="links">
	<a href="#">first link</a>
	<a href="#">second link</a>
</div>


var aTags = document.querySelectorAll("#links a");
Array.prototype.forEach.call(aTags, function(a){

	a.addEventListener("click, function(event){
	console.log(a.textContent); //correct
	console.log(this.textContent); //correct
	console.log(event.target.textContent);//correct
	});

});

instead use:

EVENT DELEGATION



<div id="links">
	<a href="#">first link</a>
	<a href="#">second link</a>
</div>


var div = document.querySelectorAll("#links");

div.addEventListener("click", function(event) {
	console.log(this.textContent); // "the first link"
	console.log(event.target.textContent); // "the first link"
	this === div; //true;
	this === event.currentTarget ; //true
});

.BIND()

//negative is that you can’t remove the event listener because bind creates a new function
//bind works similiar to call and is way to tell what this will refer to,

var Something = function(element) {
	this.name = "Something good";
	this.onclick1 = function(event) {
		console.log(this.name); // undefined, as this is the element
	};
	this.onclick2 = function(event) {
		console.log(this.name); // "something good", as this is the binded Something object
	};

	element.addEventListener("click", this.onclick1, false); // this wont work because this is not this this...
	element.addEventListener("click", this.onclick2.bind(this), false);//by expresionally binding this, this will refer to correct this
}

STOP DEFAULT ACTION

Typical when the link isn’t really a link…
It used to be common to return false which do the same thing

var a = document.querySelector("#atag");
a.addEventListener("click", function(event){
	console.log("I got this, don't activate the link!");
	event.preventDefault();
});

TIMERS

TIMEOUT:

var timer = setTimeout(function() {
	console.log(“at least 3 seconds passed..”);
}, 3000);
clearTimeout(timer); //stops the timer


var timer = setInterval(function() {
	console.log(“at least 3 seconds passed..”);
}, 3000);
clearInterval(timer); //stops the interval

Add your comment