3
\$\begingroup\$

I am just getting into the world of javascript, coming from the world of classical inheritance. The following is a library I wrote to track when a webapp is idling and I wrote it like I write classical inheritance. But what I have heard about javascript is you have to completely forget how classical inheritance works.

I would love to see how a real javascript developer would write the following using prototypes and the likes.

var IdleListeners = new function(){
  var self = this;

  window.onmousemove = resetTimer;
  window.onmousedown = resetTimer;
  window.onclick = resetTimer;
  window.onscroll = resetTimer;
  window.onkeypress = resetTimer;

  function resetTimer() {
    for (var i = 0; i < self.listeners.length; i++) {
        self.listeners[i].reset();
    }
  }
}

IdleListeners.listeners = [];

IdleListeners.add = function(idleFunction, timeout){
  this.listeners.push(new IdleListener(idleFunction, timeout));
}

var IdleListener = function(idleFunction, timeout){
  this.idleFunciton = idleFunction;
  this.timeout = timeout;

  this.reset = function(){
    if (this.hasOwnProperty('interval')){
      clearInterval(this.interval);
    };
    this.interval = setInterval(this.idleFunciton, this.timeout);
  }
}

It is used by simply calling the following in your html/js file

IdleListeners.add(function(){
    console.log("Idle!")
}, 1000);

"Idle!" would be called once every 1000 miliseconds when the mouse hasn't moved or no keys have been pressed.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
window.onmousemove = resetTimer;
window.onmousedown = resetTimer;
window.onclick = resetTimer;
window.onscroll = resetTimer;
window.onkeypress = resetTimer;

First, I'd suggest not binding handlers this way. The problem is that some other code might override the property and completely replace the handler. Even if you take steps not to override an existing one, there's no guarantee that yours won't get overridden. Use addEventListener instead.

Also, document and window have different events. document related to the DOM elements (i.e.: click, mouseover) and window related to window events (i.e. resize, scroll etc.). See this answer for details.

var IdleListeners = new function(){

You're not benefiting from the perks of constructor-created objects. What your code essentially does is create an instance of an anonymous constructor which has no methods nor properties. You merely created a closure that wraps the initialization code and your timer reset.

var IdleListener = function(idleFunction, timeout){

For this one, you almost got it, except for the this.reset. The goal of prototypal inheritance is to share methods among instances. Defining reset in this instead of the prototype means that each instance gets its own copy of reset, not shared.

this.interval = setInterval(this.idleFunciton, this.timeout);

I believe you want setTimeout. You wouldn't want the handlers to fire periodically, especially when they're bound to an alert. It would be endless.

But what I have heard about javascript is you have to completely forget how classical inheritance works.

Not really. You just need to know how classical inheritance is different from prototypal inheritance. Also, you shouldn't force JS to do what it doesn't do natively. While it can emulate, it's not pretty.

Keep the code simple. As much as possible, work with just functions, objects (maps) and arrays (lists). Don't jump into OOP or inheritance directly. For instance, I can simplify your code with a singleton and a list of handlers.

;(function(ns){

  // An array for our "listeners"
  var listeners = [];

  // An add function which accepts a delay and handler
  ns.add = function add(handler, timeout){

    // All we're doing here is create an entry in the array containing our
    // settings, and preparing the timer as null. Reset will build it for us.
    listeners.push({
      handler: handler,
      timeout: timeout,
      timer: null
    });
  }

  // Now when you call reset (as a result of an action), it loops through all
  // the handler definitions, clears any existing timers, and rebuilds them.
  ns.reset= function reset(){
    listeners.forEach(function(listener){
      clearTimeout(listener.timer);
      listener.timer = setTimeout(listener.handler, listener.timeout);
    });
  }

  // Add handlers
  document.addEventListener('click', reset);

// Use an existing global or create one
}(this.IdleListener = this.IdleListener || {}));

// Usage
IdleListener.add(function(){
  console.log('idle!');
}, 1000);
\$\endgroup\$
2
  • \$\begingroup\$ Question about this though, when we call ns.add(...) the timer is never set. So that means if the page is loaded and the user doesn't click/move mouse idle will never be run, even though the page is idling. To fix this, instead of passing null into timer, I tried passing setTimeout(handler, timeout). Is there a better way of doing this? \$\endgroup\$
    – mattNit
    May 5, 2016 at 3:59
  • \$\begingroup\$ @mattNit Yes, that should work. But then, who doesn't move when the page loads anyways? Unless we're talking about a kiosk, but then this code wouldn't make sense on a kiosk. :P \$\endgroup\$
    – Joseph
    May 5, 2016 at 4:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.