pajser92
pajser92

Reputation: 23

JavaScript: Instead of passing "this" as an argument, how can I make function which will be called on it as an object?


I'm JavaScript beginner and am still grasping some concepts around it, so sorry if the question is dumb or it's already answered, but I didn't know what to search for, as the language terminology is still kinda foreign to me (both JavaScript and English). Currently I'm trying to master "this" keyword and trying to minimize JS code inside HTML file and move as much as possible to external files.


This is my question:
Let's say I want to change paragraph's value from Hello World! to foo bar by clicking on paragraph itself, just by using "this" keyword and some JavaScript.

I can do it in external file like:

<!--index.html-->

<p onclick="setParagraphText(this, 'foo bar')">Hello World!</p>


----------------------------------------------
//script.js

function setParagraphText(paragraph, value) {
    return paragraph.innerHTML = value;
}

Or inline, inside tag:

<p onclick="this.innerHTML='foo bar'">Hello World!</p>

My question is: is it possible to do a combination of these 2 ways, so that the p value is not passed as an argument, but instead the function is invoked on it as an object (as a similar to 2nd example), but still keep the method of doing it in external file (like in 1st example)?

Something along the lines of this.function(value) instead of function(this, value)

<!--index.html-->

<p onclick="this.setParagraphText('foo bar')">Hello World!</p>

----------------------------------------------
//script.js

function setParagraphText(value) {
    //something with innerHTML = value; or whatever will work
}

Thanks in advance!

Upvotes: 2

Views: 252

Answers (5)

jperl
jperl

Reputation: 5112

First of all, if you want to be able to call your function on this, you have to know what the keyword this here represents. One simple way to do it is to console.log it.

enter image description here

So, this is the DOM element you have your inline javascript on ! Confirmed here: this in an inline event handler. Okay, if you want to have more information, console.log paragraph.constructor.

enter image description here

So, it's a HTMLParagraphElement. That's what you would get if you call this:

document.createElement("p");

So, if you want to be able to call this.setParagraphText, it comes down to calling setParagraphText on an HTMLParagraphElement object. But in order to do that, HTMLParagraphElement has to implement it and one way to do this, as subarachnid suggested, is to add the function to its prototype so that it is shared by all instances of it. If you think this would be useful, take a look at Web Components.

Here is a link: Extending native HTML elements.

Basically, you do it like this (and the cool thing here is the functionality, that is changing its content when clicking on it, will be encapsulated within the class):

<!DOCTYPE html>
<html>
<body>

<script>

	// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class CoolParagraph extends HTMLParagraphElement {

  constructor() {
    super(); 
    this.addEventListener('click', e => this.setParagraphText('new value'));
  }

  setParagraphText(v) {
    this.innerHTML = v;
  }
}

customElements.define('cool-paragraph', CoolParagraph, {extends: 'p'});
		
</script>

<!-- This <p> is a cool paragraph. -->
<p is="cool-paragraph">Cool paragraph! Click on me and the content will change!</p>

</body>
</html>

So you don't even have to write inline Javascript anymore!

But if you want to have it your way and add your inline javascript, it's fine.

<!-- Note the this.setParagraphText(), now it works! -->
<p is="cool-paragraph" onclick="this.setParagraphText('foo bar')">Cool paragraph! Click on me and the content will change!</p>

<!DOCTYPE html>
<html>
<body>

<script>

	// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class CoolParagraph extends HTMLParagraphElement {

  constructor() {
    super(); 
    //this.addEventListener('click', e => this.setParagraphText('new value'));
  }

  setParagraphText(v) {
    this.innerHTML = v;
  }
}

customElements.define('cool-paragraph', CoolParagraph, {extends: 'p'});
		
</script>

<!-- This <p> is a cool paragraph. -->
<p is="cool-paragraph" onclick="this.setParagraphText('foo bar')">Cool paragraph! Click on me and the content will change!</p>

</body>
</html>

I don't know if that answers your question, but that should hopefully points you in the right direction.

Upvotes: 2

Rickard Elim&#228;&#228;
Rickard Elim&#228;&#228;

Reputation: 7591

Dunno if this is what you're after, but this in the function is referring to the function itself, but you can send in a scope with .call(this, arguments) which means that referring to this in the function is actually the scope of the HTML element.

I'm showing two ways of how you can handle this, with either a data attribute or sending in a new value as a parameter.

function setParagraphText(newValue) {
  this.innerText = this.dataset.text + " + " +  newValue;
}
<p data-text="foo bar" onclick="setParagraphText.call(this, 'foo yah')">Hello World!</p>

Also read: Javascript call() & apply() vs bind()?

Upvotes: 0

Thomas
Thomas

Reputation: 181805

Currently I'm trying to [...] minimize JS code inside HTML file and move as much as possible to external files.

Then how about having no JS code inside your HTML? This lets you kill two birds with one stone:

document.getElementById("clickme").addEventListener("click", function() {
  this.innerHTML = "foo bar"
})
<p id="clickme">Hello world!</p>

The listener you add with addEventListener will be invoked with this being the element that the listener was added on.

Upvotes: 0

Đinh Carabus
Đinh Carabus

Reputation: 3496

For your code to work you would have to enhance the HTMLElement prototype with your setParagraphText method (which is basically just a wrapper for this.innerHTML = value):

HTMLElement.prototype.setParagraphText = function(value) { 
  this.innerHTML = value; 
};

Now something like this should work:

<p onclick="this.setParagraphText('foo bar')">Hello World!</p>

But I would strongly advise against modifiying native prototypes (older browsers like IE 9 don't even allow such a thing afaik).

Upvotes: 0

Nick Parsons
Nick Parsons

Reputation: 50759

You can add a data- attribute to your p tag which would store the data you want your text to change to (ie: "foo bar"), and then use addEventListener to add a click event-listener to paragraph tags which have this particular data- attribute. By doing this, you're handing over the javascript logic to the javascript file, and thus limiting the JS written within your HTML file.

See example below:

const clickableElements = document.querySelectorAll('[data-click]');
clickableElements.forEach(elem => {
  elem.addEventListener('click', function() {
    const value = this.dataset.click;
    this.innerHTML = value;
  });
});
<p data-click="foo bar">Hello World!</p>
<p data-click="baz">Hello Moon!</p>

Upvotes: 2

Related Questions