1

I'm a server-side dev learning the ropes of pure JS these days. This question is about understanding client-side caching via pure JS.

Imagine a web page listing various tweets from users.Each tweet has a "respond to this" button. Pressing it renders a text box.

And if this text box is already rendered, pressing the "respond to this" button merely toggles it off. The JS function responsible for creating this effect is called toggle_reply.

Upon each page load, I assign the toggle functionality to each "respond to this" button like so:

Array.from(document.querySelectorAll('button.reply')).forEach(btn => btn.onclick = toggle_reply)

This is called at every page refresh. And the page refreshes every time a new tweet comes in - adding the new tweet to the list, and removing old tweets if the list is bigger than 40.

My question is about improving this approach. It feels overkill to me that I'm running this for loop every time there's a fresh. Would there be a way to cache it for buttons that exist on the page and have already been processed by the for loop? An illustrative example of how to do this would be great.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205
  • 1
    You should look into AJAX, where one don't refresh the whole page, just passing messages back and forth. – Asons Mar 18 '18 at 11:35
  • @LGSon: Yes I'm looking at xhr objects as well. – Hassan Baig Mar 18 '18 at 11:37
  • 1
    Side note: On modern browsers, the NodeList returned by `querySelectorAll` has its own `forEach` (no need for `Array.from`), and it's easy to polyfill for older browsers; [my answer here](https://stackoverflow.com/questions/46057719/object-doesnt-support-this-property-or-method-ie10-11/46057817#46057817) shows how. – T.J. Crowder Mar 18 '18 at 11:43
  • 1
    I assumed you were using XHR to refresh the tweets, so the answer below will continue to work when you start doing that. :-) – T.J. Crowder Mar 18 '18 at 11:49
  • @T.J.Crowder: yea I can see that's the beauty of it. This is going to be a great exercise to understand event delegation. – Hassan Baig Mar 18 '18 at 11:56

1 Answers1

2

This is a good example of when event delegation is useful. Don't hook each button; instead, just hook click on the container they're in, and then when the click bubbles to the container, check to see if it passed through one of those buttons as it bubbled:

document.getElementById("container").addEventListener("click", function(e) {
    // See if the click passed through `button.reply`
    var btn = e.target.closest("button.reply");
    if (btn) {
        // It's a reply button
        console.log("Reply to tweet");
    }
});

// Example of it working even when we add a new tweet
document.getElementById("container").insertAdjacentHTML("beforeend",
  `<div class="tweet">
    Four
    <button type="button" class="reply">Reply</button>
  </div>`
);
<div id="container">
  <div class="tweet">
    One
    <button type="button" class="reply">Reply</button>
  </div>
  <div class="tweet">
    Two
    <button type="button" class="reply">Reply</button>
  </div>
  <div class="tweet">
    Three
    <button type="button" class="reply">Reply</button>
  </div>
</div>

See documentation for Element#closest here; it's relatively new, but can be polyfilled.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Just wondering, instead of using `element#closest` which I'd have to polyfill, any way to cheaply check `e.target's` class list for `reply` class? (although I'm assuming you went for `element#closest` because that's easier?). – Hassan Baig Mar 18 '18 at 12:13
  • 1
    @HassanBaig: Polyfilling `closest` is your best option; see the MDN link above for a polyfill you can use. Note that it's not that you want to just check `e.target`'s class list; you want to check `e.target` for the class, and if it doesn't have it, check its parent, and then *its* parent, up to the element on which you hooked the event, since buttons can contain other elements (``). – T.J. Crowder Mar 18 '18 at 12:20