0

I'm working on a website, with jQuery but I'm trying to not use it anymore. In jQuery you can add an even listener on a element that wasn't on the website or wasn't created yet and no problem. I have elements that are only on the DOM when you're logged in, and I only have one JS file for the whole website.

Problem is, for example, when you're logged in you can't see the "log in" button, it's not even in the DOM, but it still have the event listener in the code, no error on the console, script runs well.

$("#logInButton").on("click", somefunction);

But, using document.querySelector("#logInButton").onclick = somefunction and being logged in already, it throws an error because document.querySelector("#logInButton") is null.

I can do like:

let logInButton = document.querySelector("#logInButton");

logInButton ? logInButton.onclick = somefunction : "";

And it works well, but I know it's not a good practice. Any workaround or improvement to that, not using jQuery?

JSFiddle if what happens. (See console)

Syden
  • 7,856
  • 5
  • 22
  • 39
velour
  • 18
  • 1
  • 5
  • 1
    *"And it works well, but I know it's not a good practice."* If having `#logInButton` on the page is optional, that's perfectly good practice -- other than using `onclick` rather than `addEventListener`. – T.J. Crowder Sep 17 '17 at 17:57
  • If `#logInButton` is not yet in the DOM then how could `$("#logInButton").on("click", somefunction);` be working without any delegation? – ibrahim mahrir Sep 17 '17 at 17:59
  • I'm trying not to use jQuery so the goal is to achieve the same thing, of course it equals to it. But using jQuery no error when the element isn't in the DOM, without like the line below, error because the querySelector returns null. – velour Sep 17 '17 at 17:59
  • @velour: Your title doesn't really match your question. In your title, you're talking about adding elements dynamically to the page. In your question, you're talking about an element that may or may not be present when you hook a handler to it **directly** (not delegated). – T.J. Crowder Sep 17 '17 at 18:00
  • @T.J.Crowder changed the title. Indeed, I have elements on the site that only are on DOM when you're logged in, and others when not. But on the script of course I need to add the event listeners on them, when they are in DOM or not, and with JQuery I didn't had any problem when the elements were't on the DOM. – velour Sep 17 '17 at 18:06
  • @velour Does the log in/out require the reloading of the page. – ibrahim mahrir Sep 17 '17 at 18:11
  • @ibrahimmahrir yes, for now. [An example of what happens.](https://jsfiddle.net/p2qbLs2m/) – velour Sep 17 '17 at 18:15
  • @velour The jQuery code doesn't trigger an error, but the listener won't work after you've added the button to the page. – Teemu Sep 17 '17 at 18:18
  • @velour Was your jQuery code wrapped in `$(document).ready( ... )` or `$(function() { ... })`? – ibrahim mahrir Sep 17 '17 at 18:20

2 Answers2

3

And it works well, but I know it's not a good practice.

If having #logInButton on the page is optional, that's perfectly good practice — other than using onclick rather than addEventListener (but that's probably a matter of style). Naturally, you'd have this code in a script linked at the end of the document, just prior to the </body> tag (or trigger it via a DOMContentLoaded callback).


But if you want the equivalent of the jQuery, you need to think in jQuery's "set-based" mindset and use querySelectorAll:

// Not very efficient
document.querySelectorAll("#logInButton").forEach(function() {
    // Set up the handler here using `this`
});

Except that jQuery optimizes queries using #id format to a getElementById call (which is dramatically faster) and then uses an if (like yours) to build the set with either one element or zero.

Perhaps in your quest to not use jQuery, you might give yourself a couple of helper functions to take its place, as the DOM API is quite verbose. If you like jQuery's set-based nature, you might even make them set-based:

function MyQuery(selector) {
    if (!selector) {
        this.data = [];
    } else if (typeof selector === "string") {
        // (jQuery takes it further than this, search in an unminified version for `rquickExpr`)
        var id = /#([\w-]+)/.match(selector);
        if (id) {
            var e = document.getElementById(id[0]);
            this.data = e ? [e] : [];
        } else {
            this.data = Array.from(document.querySelector(selector));
        }
    } else {
        /* ...handle other things, such as DOM elements or arrays of them...? */
        this.data = /*...*/;
    }
}
MyQuery.prototype = {
    constructor: MyQuery,
    on: function(eventName, handler) {
        this.data.forEach(function(element) {
            element.addEventListener(eventName, handler);
        });
        return this;
    }
    // ...etc...
};
function qset(selector) {
    return new MyQuery(selector);
}

Then

qset("#logInButton").on("click", /*...*/);

Of course, you might find yourself basically recreating jQuery. But if you keep it lean...


Side note: Using forEach on the return value of querySelectorAll requires an up-to-date browser, or that you polyfill it:

if (typeof NodeList !== "undefined" &&
    NodeList.prototype &&
    !NodeList.prototype.forEach) {
    Object.defineProperty(NodeList.prototype, "forEach", {
        value: Array.prototype.forEach
    });
}

For truly obsolete browsers (like IE8), you'd have to polyfill Array.prototype.forEach first.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • @ibrahimmahrir: I read the question as the element is optional (sometimes it's on the page, other times it isn't) and he/she is relying on the fact that jQuery turns that into a no-op when the element isn't there. This based on several aspects of the question, not least that they claim their version at the end with the conditional operator works. Note my comment on the question, though, as he/she has been fairly unclear about it. – T.J. Crowder Sep 17 '17 at 18:07
  • @ibrahimmahrir: Ah, and he/she has [now confirmed that's correct](https://stackoverflow.com/questions/46267161/decument-queryselector-on-dynamically-created-elements-or-elements-not-in-dom/46267210#comment79497002_46267161). – T.J. Crowder Sep 17 '17 at 18:08
  • @velour: Yes, I was pleased to see I'd understood the question correctly. Did you have a question about the answer? – T.J. Crowder Sep 17 '17 at 18:18
  • no, using the forEach and querySelectorAll won't give any error even if the element isn't in the DOM. But as you say it's not efficient... there should be another way! In the worst case I'd use that and make a helper function to use that the best way possible. – velour Sep 17 '17 at 18:28
  • @velour: What more could you want than either use a branch (efficient) or use a set (jQuery-esque). (See also the bit where I expanded on the bit about giving yourself some utility functions, which could even be set-based...) – T.J. Crowder Sep 17 '17 at 18:31
  • 1
    I came up with [this](https://jsfiddle.net/p2qbLs2m/1/) thanks to your answer and it seems to work well for my needs :) – velour Sep 17 '17 at 18:38
0

You can do it the same way jQuery does it, using event bubbling.

document.addEventListener('click', function (ev) { 
  if (ev.target.id === 'someIdHere') { 
    console.log('click'); 
  }
});
  • Er, that's not how jQuery does what the OP asked about. That's somewhat similar to how jQuery does event delegation (except you can root it anywhere with jQuery's `on` rather than just on the document), which is completely different. `$("#logInButton").on("click", somefunction)` (quoted in the OP's question) is **not** doing event delegation. – T.J. Crowder Sep 17 '17 at 20:47
  • event delegation is exactly what author needs – Marcin Malinowski Sep 18 '17 at 06:25
  • It's certainly one way to solve the problem. It is not, again, what the jQuery code quoted by the OP does. – T.J. Crowder Sep 18 '17 at 06:57
  • I never written it is what jQuery quoted code do, I written it is what jQuery do (in general, when using event delegation) – Marcin Malinowski Sep 18 '17 at 09:53
  • Well, I suggest fixing the wording of the answer then. The OP shows jQuery code, and you say to do it "the way jQuery does it." That quite clearly suggests, at least to me, that you're saying the jQuery code they showed does it that way. jQuery has no monopoly on event delegation, so "the way jQuery does it" really makes no sense unless you're referring to the OP's jQuery code. – T.J. Crowder Sep 18 '17 at 09:58
  • It does not show a jQuery code, it uses one of jQuery functions. I suggest you work on your wording first. – Marcin Malinowski Sep 18 '17 at 17:08