Getting an html element without jquery / this.$()


#1

Not that it really matters that much, but I was curious if there was a way to access an HTML element in a component without using this.$()

Example:

export default Component.extend({
	tagName: 'img',
	attributeBindings: ['src'],

	didInsertElement() {
		let _this = this;
		this.$().on('error', function() {
			_this.set('src', missingImage);
		});
	},
	willDestroyElement() {
		this.$().off();
	}
});

Like, how could I access the element with plain javascript?


#2

If you’re in a component you have access to it’s element via this.element. Be aware that this returns a plain DOM element, so you’ll need to use addEventListener/removeEventListener rather than on/off (which are jQuery methods)

You can also get the elementId


#3

i’ve been doing document.querySelector() and document.querySelectorAll() and it works just fine.


#4

Interesting. So a no-jquery version of that code looks something like:

export default Component.extend({
	tagName: 'img',
	attributeBindings: ['src'],

	didInsertElement() {
		let _this = this;
		_this.get('element').addEventListener("error", function(e) {
			console.log("IMAGE ERROR,", e);
			_this.set('src', missingImage);
		});
	},
	willDestroyElement() {
                    console.log("destroying?");
		this.get('element').removeEventListener("error", function() {
			console.log("destroyed");
		});
	}
});

The _this.get('element').addEventListener("error", function(e) {}) part works as expected. But I can’t quite figure out why the this.get('element').removeEventListener("error", function() {}) part isn’t getting triggered.

I see “destroying?” in my console, but not “destroyed” when I transition out.

Mildly related, in situations where you’re dealing with the DOM in a component, is it recommended to wrap the dom-related code in a run loop? I’ve seen examples both with and without. It would see that if you’re doing it in didInsertElement you wouldn’t need it, but perhaps there is another reason it’s used.


#5

You are trying to remove an event listener that doesn’t exist. If you want to pass a function to removeEventListener, it has to be the exact function that you added. Not even one that has the same code, the exact same instance. That means you have to save it somewhere.

One note, you’re using _this = this a lot. You shouldn’t do that in ember, use arrow functions instead.

Generally, no you don’t.


#6

Cool, thanks for your help. Didn’t know that about arrow functions - that’s awesome!


#7

Follow up question, what if you want to select an html element without being inside a component?


#8

Do you mean like from a template, like application.hbs? If so…my guess is no because templates are backed by controllers which don’t have a lifecycle like a component does. But there may be a way I don’t know!


#9

Yeah that’s what I meant. That’s too bad. I think I figured out a way to work around my problem though so it’s all good.

I’m just a bit surprised because I figured being able to select and change elements is crucial to having a dynamic website.


#10

A major benefit of using a framework such as Ember is to handle a large amount of the DOM interfacing detail so you can focus on the parts which make your application different. I’ve built a number of large Ember applications and the few times I need to access the DOM directly are with integrating 3rd party libs (that don’t already have wrappers), changing classes outside the application container, and awkward animation scenarios (sticky menus which stack on other dynamic menus)

Try to model everything as a component and use the framework facilities. There is a learning curve but it’ll be worth it and there’s a helpful community to ease the journey. Ember already listens for a bunch of events by default in components for you

Any element access outside of a component I isolate to a service, that way if you want to utilise FastBoot you have one place to prevent errant access to the DOM and provide defaults