0

I searched a lot for a solution to this certainly-not-unique problem, but I have not found anything that will work in my context of an HTML page.

I have an input text that contains some kind of source-code that generates something, and I can show a preview of that something on the same HTML page (by updating the background image, for example). Note that the source could be a LaTeX file, an email, a Java program, a ray-trace code, etc. The "action" to generate the preview has a certain cost to it, so I don't want to generate this preview at each modification to the source. But I'd like the preview to auto-update (the action to fire) without the user having to explicitly request it.

Another way to phrase the problem is to keep a source and sink synchronized with a certain reasonable frequency.

Here's my solution that's too greedy (updates at every change):

$('#source-text').keyup(function(){
    updatePreview(); // update on a change
  });

I tried throttling this by using a timestamp:

$('#source-text').keyup(function(){
    if (nextTime "before" Now) { // pseudocode
       updatePreview(); // update on a change
    } else {
       nextTime = Now + some delay // pseudocode
    }
  });

It's better, but it can miss the last updates once a user stops typing in the source-text field.

I thought of a "polling loop" for updates that runs at some reasonable interval and looks for changes or a flag meaning an update is needed. But I wasn't sure if that's a good model for an HTML page (or even how to do it in javascript).

zoran404
  • 1,682
  • 2
  • 20
  • 37
Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111

3 Answers3

2

Use setTimeout, but store the reference so you can prevent it from executing if further editing has occurred. Basically, only update the preview once 5 seconds past the last keystroke has passed (at least in the below example).

// maintain out of the scope of the event
var to;

$('#source-text').on('keyup',function(){
  // if it exists, clear it and prevent it from occuring
  if (to) clearTimeout(to);

  // reassign it a new timeout that will expire (assuming user doesn't
  // type before it's timed out)
  to = setTimeout(function(){
    updatePreview();
  }, 5e3 /* 5 seconds or whatever */);
});

References:

And not to self-bump, but here's another [related] answer: How to trigger an event in input text after I stop typing/writing?

Community
  • 1
  • 1
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • I thought your answer worked, but it doesn't update until I stop typing. I want updates to occur while typing, but at a frequency less than every keystroke. See http://jsfiddle.net/p4u2mhb9/2/ – Fuhrmanator Aug 19 '14 at 22:43
  • @Fuhrmanator: How about every 5 seconds while [still] typing? http://jsfiddle.net/p4u2mhb9/4/ – Brad Christie Aug 20 '14 at 12:27
0

I tweaked @bradchristie's answer, which wasn't quite the behavior I wanted (only one update occurs after the user stops typing - I want them to occur during typing, but at a throttled rate).

Here's the solution (demo at http://jsfiddle.net/p4u2mhb9/3/):

// maintain out of the scope of the event
var to;
var updateCount = 0;
var timerInProgress = false;

$('#source-text').on('keyup', function () {
    // reassign a new timeout that will expire
    if (!timerInProgress) {
        timerInProgress = true;
        to = setTimeout(function () {
            timerInProgress = false;
            updatePreview();
        }, 1e3 /* 1 second */ );
    }
});
Community
  • 1
  • 1
Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
0

I made @Brad Christie's answer more generic, see at http://jsfiddle.net/NickU/gLxucfjv/38/

var lazyRun = function (funcCall, delay, key) 
{
  if (typeof funcCall !== "function") return;
  if (typeof key === "undefined") key = "timeoutId";
  funcCall.to = funcCall.to || {};
  if (funcCall.to[key]) clearTimeout(funcCall.to[key]);
  var args = arguments.length > 3 ? Array.prototype.slice.call(arguments, 3) : [];
  funcCall.to[key] = setTimeout(funcCall, delay, ...args);
};



  $(document).ready(function () 
  {
      $('input').on('keyup', function () {
          lazyRun(updatePreview,1e3, this.id, '#update-count-'+ this.id, 
          this.updateCount ? ++this.updateCount : (this.updateCount = 1));
      });
      $('label').text("not updated yet");
  });

  function updatePreview(id, updateCount) {
      $(id).text("->" + updateCount);
  }

HTML:

 <form>
    <input type="text" id="source-text" />
    <label id="update-count-source-text">not updated</label><hr>
     <input type="text" id="source-text-2" />
    <label id="update-count-source-text-2">not updated</label>
 </form>

The simplest form of lazyRun is:

var lazyRun = function (funcCall, delay) {
  if (funcCall.to) clearTimeout(funcCall.to);
  funcCall.to = setTimeout(funcCall, delay);
};