Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is Object Oriented Javascript used for DOM manipulation

I understand the main principle of OOP and I somewhat know how to implement it into JS.

function Person(name) {
    this.name = name;
    this.speak = function(msg) {
        console.log('Person says:' + msg);
    }
}

var dad = new Person('David');
dad.speak('I am your dad!');

The script above does nothing more than printing out a message in the console. I don't understand how we approach the DOM with this technique. Maybe something like this?:

function Person(target, name) {
    this.target = target;
    this.name = name;
    this.speak = function(msg) {
        this.target.find('.speech-bubble').html(msg);
    }
}

var dad = new Person($('#dad'), 'David');
dad.speak('I am your dad!');

Although this doesn't seem like a good approach.

How do we manipulate the DOM with objects, methods, constructors etc. through OO Javascript?

like image 231
Stefan van Lingen Avatar asked Jan 27 '17 12:01

Stefan van Lingen


2 Answers

In relation to OO, if you are going to adopt for your DOM facing code, you are not too far of.

I'd say that a class should represent a component/element on the DOM. With it's methods being the state management part. But there is no correct answer here to be honest. This is but one way of designing OO with the DOM facing part.

Example:

const basicClassName = 'component';
const basicTemplate = '<h1>This is my basic component</h1>';

class MyComponent {
  constructor(template = basicTemplate, className = basicClassName) {
    this.template = template;
    this.className = className;
    
    this.element = document.createElement('div');
    this.element.className = className;
    this.element.innerHTML = template;
    this.element.onclick = this.onClick.bind(this);
    this.element.style.cursor = 'pointer';
  }
  
  onClick() {
    this.element.classList.toggle('clicked');
  }
}

const component = new MyComponent();

const container = document.querySelector('.container');

container.appendChild(component.element);
body {
  font-size: 14px;
}

.component {
  display: block;
  padding: 1.3em;
  box-shadow: 1px 1px 4px lightgray;
}


.clicked {
  background-color: papayawhip;
}
<div class="container"></div>
like image 151
Vinicius Dallacqua Avatar answered Sep 28 '22 09:09

Vinicius Dallacqua


What you need to understand is the concept of the Prototype.

When you create an instance using new, you are constructing an object based upon a prototype.

Consider the following:

function Person(name) {
    this.name = name;
    this.speak = function (msg) {
        console.log('Person says:' + msg);
    };
}
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', mom.speak === dad.speak);

Each time you construct a new instance of Person, a new speak prototype now floats around somewhere in your logic. This is very inefficient.

To fix this, we need to modify the prototype of our function:

function Person(name) {
    this.name = name;
}
Person.prototype.speak = function (msg) {
    console.log('Person says:' + msg);
};
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', dad.speak === dad.speak);

This way, we only have the function created once, on the prototype which is inherited to all instances. This is easier to maintain and a lot more efficient.

Now we can extend DOM object via their prototype, but it isn't recommended because you start to mess with the web standards, making troubleshooting much more difficult.

Array.prototype.isLengthGreaterThanFive = function(thisArg) {
  return this.length > 5;
};
console.log([1, 2, 3, 4].isLengthGreaterThanFive(), [1, 2, 3, 4, 5, 6].isLengthGreaterThanFive());

A better way of handling this is to create extended objects or to simply use functions:

//Using functions
function isLengthGreaterThanFive(array) {
  return array.length > 5;
}
console.log(isLengthGreaterThanFive([1, 2, 3, 4]), isLengthGreaterThanFive([1, 2, 3, 4, 5, 6]));

//Using a wrapper class
var MyArray = (function() {
  function MyArray(array) {
    if (array === void 0) {
      array = [];
    }
    this.array = array;
  }
  MyArray.prototype.isLengthGreaterThanFive = function() {
    return this.array.length > 5;
  };
  return MyArray;
}());
console.log(new MyArray([1, 2, 3, 4]).isLengthGreaterThanFive(), new MyArray([1, 2, 3, 4, 5, 6]).isLengthGreaterThanFive());

The benefits of using a class is that we can extend upon our idea of the object:

//Base class
function Person(firstname, lastname, says) {
    if (firstname === void 0) {
        firstname = "Leonado";
    }
    this.firstname = firstname;
    if (lastname === void 0) {
        lastname = "Da Vinci";
    }
    this.lastname = lastname;
    if (says === void 0) {
        says = "hello";
    }
    this.says = says;
}
//Base methods
Person.prototype.iAm = function () {
    return this.firstname + " " + this.lastname;
};
Person.prototype.Speak = function () {
    return this.says + " my name is " + this.iAm();
};
//Extended class
function Warrior(firstname, lastname, says) {
    //Call in constructor
    Person.call(this, firstname, lastname, says);
}
//Inheriting
Warrior.prototype = Object.create(Person.prototype);
Warrior.prototype.constructor = Warrior;
//Overruling "Speak"
Warrior.prototype.Speak = function () {
    return "My name is " + this.iAm() + ", " + this.says;
};
console.log([new Warrior("Peter", "Allan", "Ahoyhoy").Speak(), new Person("Peter", "Allan", "Ahoyhoy").Speak()]);

In the example above we extend the prototype of Person for Warrior so that we retain the functionality of Person, and then simply modify what's different about a Warrior. This way we get to reuse the prototype method iAm, and we can focus on only changing what needs to change in the Speak method.

EDIT 1

I noticed too late that the question had changed a little.

You can treat DOM elements like any other class in JavaScript. The following setup has all Persons sharing a single DIV to speakUp:

var Person = (function () {
    function Person(age, firstname, lastname) {
        if (age === void 0) { age = 50; }
        if (firstname === void 0) { firstname = "Peter"; }
        if (lastname === void 0) { lastname = "Venkman"; }
        this.age = age;
        this.firstname = firstname;
        this.lastname = lastname;
    }
    Person.prototype.speakUp = function () {
        Person.bubble.innerHTML = this.firstname + " " + this.lastname + " is " + this.age + " years old";
    };
    return Person;
}());
Person.bubble = document.createElement("div");
document.body.appendChild(Person.bubble);
setInterval(function () {
    var p = new Person(Math.floor(Math.random() * 100));
    p.speakUp();
}, 3000);

This could easily become a DIV per Person, or a refereced DOM object (document.getElementById) shared among all Persons.

EDIT 2

In response to your comment:

In JavaScript everything is in essence and object. You create a function it registers an object with the functions name and returns and instance of that object. Everything like Arrays, Strings, DOM elements and custom functions has some master object hidden behind the scenes. Every time a new Array or DOM element or whatever is created, it has a reference to its master object (called the prototype). This is called the prototype chain.

If you look on my second example above, when dad.speak is called JavaScript first searches the instance for a speak property, but it won't find one because we haven't assigned it the way we did in example one were it was instance specific.

JavaScript will then try one level up the prototype chain and here it will find a matching property and use this instead. This way we can alter the default behavior of custom OR existing elements in JavaScript.

The idea being, that if you have some property that all instances of a prototype should have, then we simply modify the prototype once and they will all inherit this property.

Think of it this way. If you were to describe all living things on earth in JavaScript you would want some form of groupings. For instance the first level would be something like an Exists object that carries a property for a name and an id. From here you you could create Plant and Animal and have them both inherit the prototype of Exists. Now we could create a Flower class that inherits Plant and a Rose class that inherits Flower and so on.

The idea is to apply you properties in a way that makes sense to human beings via inheritance (an owl can fly because it is a bird / a shark can swim because it is a fish). Binding them at the level that makes sense, inheriting in a logical pattern and using your time efficiently.

If you are still confused, try looking up prototype tutorials.

Here is a good Youtube video to explain it:

https://www.youtube.com/watch?v=PMfcsYzj-9M

like image 32
Emil S. Jørgensen Avatar answered Sep 28 '22 09:09

Emil S. Jørgensen