Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Only bind timeago plugin to an element once #26

Open
wants to merge 2 commits into from

3 participants

@stevenharman

Currently, every time an element is passed into the $.fn.timeago() method that element is included in the setInterval, regardless of whether or not it has already been included in a previous interval.

We have found this problematic as we use a event to trigger binding of the timeago plugin, like so (within a Sammy.js app):
app.bind('widgetsRendered', function(e) {
$('.timestamp').timeago();
});

It would be ideal if only elements that were not already bound to timeago were included in the setInterval.

stevenharman added some commits
@stevenharman stevenharman Issue 25: only bind timeago plugin once-per-element
This change will filter out any elements that have already been bound to
the timeago plugin so they are not included in multiple setInterval
closures.

NOTE: I also had to bump the version of jQuery used as 1.4.2 has a
bug where it wouldn't call the .each(callback). Perhaps this is due to
changes to the .filter method? Moving to 1.4.1 or 1.4.4
is also acceptable.
199d6c8
@stevenharman stevenharman Issue 20: don't bind nor setInterval for empty collections d488c95
@stevenharman

I didn't realize the new Pull Request system auto-creates a new issue. I've closed the original Issue #25 and copied it's text here.

Sorry for the confusion

@philfreo

+1 for fixing this. Right now the naive way to use this plugin with dynamic content is to just call $('.timeago').timeago() after every time you add more content which is problematic because it causes the existing elements to get multiple setIntervals on them. Needs a fix like this.

#30 is related but the plugin should work better without using a jquery plugin like livequery

@porjo

Wouldn't the solution be to use a global array that stores the IDs resulting from setInterval. Every call to setInterval does a clearInterval on the array of IDs (except for the last one) before appending the array with the new interval ID. This way, additional intervals can be set but will be prevented from accumulating

e.g.

if(id_array.length > 1) {
  for(var i=0; i < id_array.length-1; i++) {
    clearInterval(id_array[i]);
    id_array.splice(i,1);
  }
}
id_array.push(setInterval([...]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 18, 2010
  1. @stevenharman

    Issue 25: only bind timeago plugin once-per-element

    stevenharman authored
    This change will filter out any elements that have already been bound to
    the timeago plugin so they are not included in multiple setInterval
    closures.
    
    NOTE: I also had to bump the version of jQuery used as 1.4.2 has a
    bug where it wouldn't call the .each(callback). Perhaps this is due to
    changes to the .filter method? Moving to 1.4.1 or 1.4.4
    is also acceptable.
  2. @stevenharman
This page is out of date. Refresh to see the latest.
Showing with 88 additions and 45 deletions.
  1. +9 −2 jquery.timeago.js
  2. +1 −1  test/index.html
  3. +78 −42 test/qunit.js
View
11 jquery.timeago.js
@@ -1,6 +1,6 @@
/*
* timeago: a jQuery plugin, version: 0.9.2 (2010-09-14)
- * @requires jQuery v1.2.3 or later
+ * @requires jQuery v1.4.3 or later
*
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
@@ -99,12 +99,15 @@
});
$.fn.timeago = function() {
+ if (this.length === 0) return;
+
var self = this;
+ var newTimestamps = self.filter(unattachedTimestamps);
self.each(refresh);
var $s = $t.settings;
if ($s.refreshMillis > 0) {
- setInterval(function() { self.each(refresh); }, $s.refreshMillis);
+ setInterval(function() { newTimestamps.each(refresh); }, $s.refreshMillis);
}
return self;
};
@@ -127,6 +130,10 @@
return element.data("timeago");
}
+ function unattachedTimestamps($elements) {
+ return $(this).data("timeago") === undefined;
+ }
+
function inWords(date) {
return $t.inWords(distance(date));
}
View
2  test/index.html
@@ -5,7 +5,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- use http://code.jquery.com/jquery-latest.js for the latest jQuery -->
- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js" type="text/javascript"></script>
<!-- http://dev.jquery.com/view/trunk/qunit/testrunner.js -->
<script src="qunit.js" type="text/javascript"></script>
View
120 test/qunit.js
@@ -17,10 +17,11 @@ var QUnit = {
config.currentModule = name;
synchronize(function() {
- if ( config.currentModule ) {
+ if ( config.previousModule ) {
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
}
+ config.previousModule = config.currentModule;
config.currentModule = name;
config.moduleTestEnvironment = testEnvironment;
config.moduleStats = { all: 0, bad: 0 };
@@ -84,7 +85,7 @@ var QUnit = {
var li = document.createElement("li");
li.appendChild( b );
li.id = "current-test-output";
- tests.appendChild( li )
+ tests.appendChild( li );
}
try {
@@ -107,7 +108,7 @@ var QUnit = {
callback.call(testEnvironment);
} catch(e) {
fail("Test " + name + " died, exception and test follows", e, callback);
- QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
+ QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
// else next test will carry the responsibility
saveGlobal();
@@ -128,16 +129,10 @@ var QUnit = {
});
synchronize(function() {
- try {
- QUnit.reset();
- } catch(e) {
- fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset);
- }
-
if ( config.expected && config.expected != config.assertions.length ) {
QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
}
-
+
var good = 0, bad = 0,
tests = id("qunit-tests");
@@ -152,7 +147,7 @@ var QUnit = {
var li = document.createElement("li");
li.className = assertion.result ? "pass" : "fail";
- li.innerHTML = assertion.message || "(no message)";
+ li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
ol.appendChild( li );
if ( assertion.result ) {
@@ -188,6 +183,7 @@ var QUnit = {
var li = id("current-test-output");
li.id = "";
li.className = bad ? "fail" : "pass";
+ li.style.display = resultDisplayStyle(!bad);
li.removeChild( li.firstChild );
li.appendChild( b );
li.appendChild( ol );
@@ -211,6 +207,12 @@ var QUnit = {
}
}
+ try {
+ QUnit.reset();
+ } catch(e) {
+ fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset);
+ }
+
QUnit.testDone( testName, bad, config.assertions.length );
if ( !window.setTimeout && !config.queue.length ) {
@@ -233,11 +235,15 @@ var QUnit = {
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function(a, msg) {
+ a = !!a;
+ var details = {
+ result: a,
+ message: msg
+ };
msg = escapeHtml(msg);
- QUnit.log(a, msg);
-
+ QUnit.log(a, msg, details);
config.assertions.push({
- result: !!a,
+ result: a,
message: msg
});
},
@@ -255,27 +261,27 @@ var QUnit = {
* @param String message (optional)
*/
equal: function(actual, expected, message) {
- push(expected == actual, actual, expected, message);
+ QUnit.push(expected == actual, actual, expected, message);
},
notEqual: function(actual, expected, message) {
- push(expected != actual, actual, expected, message);
+ QUnit.push(expected != actual, actual, expected, message);
},
deepEqual: function(actual, expected, message) {
- push(QUnit.equiv(actual, expected), actual, expected, message);
+ QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
},
notDeepEqual: function(actual, expected, message) {
- push(!QUnit.equiv(actual, expected), actual, expected, message);
+ QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
},
strictEqual: function(actual, expected, message) {
- push(expected === actual, actual, expected, message);
+ QUnit.push(expected === actual, actual, expected, message);
},
notStrictEqual: function(actual, expected, message) {
- push(expected !== actual, actual, expected, message);
+ QUnit.push(expected !== actual, actual, expected, message);
},
raises: function(fn, message) {
@@ -403,10 +409,17 @@ extend(QUnit, {
/**
* Resets the test setup. Useful for tests that modify the DOM.
+ *
+ * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
*/
reset: function() {
if ( window.jQuery ) {
- jQuery("#main, #qunit-fixture").html( config.fixture );
+ jQuery( "#main, #qunit-fixture" ).html( config.fixture );
+ } else {
+ var main = id( 'main' ) || id( 'qunit-fixture' );
+ if ( main ) {
+ main.innerHTML = config.fixture;
+ }
}
},
@@ -469,6 +482,31 @@ extend(QUnit, {
return undefined;
},
+ push: function(result, actual, expected, message) {
+ var details = {
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeHtml(message) || (result ? "okay" : "failed");
+ message = '<span class="test-message">' + message + "</span>";
+ expected = escapeHtml(QUnit.jsDump.parse(expected));
+ actual = escapeHtml(QUnit.jsDump.parse(actual));
+ var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
+ if (actual != expected) {
+ output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
+ }
+
+ QUnit.log(result, message, details);
+
+ config.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
// Logging callbacks
begin: function() {},
done: function(failures, total) {},
@@ -499,7 +537,16 @@ addEvent(window, "load", function() {
}
var banner = id("qunit-header");
if ( banner ) {
- banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>';
+ var paramsIndex = location.href.lastIndexOf(location.search);
+ if ( paramsIndex > -1 ) {
+ var mainPageLocation = location.href.slice(0, paramsIndex);
+ if ( mainPageLocation == location.href ) {
+ banner.innerHTML = '<a href=""> ' + banner.innerHTML + '</a> ';
+ } else {
+ var testName = decodeURIComponent(location.search.slice(1));
+ banner.innerHTML = '<a href="' + mainPageLocation + '">' + banner.innerHTML + '</a> &#8250; <a href="">' + testName + '</a>';
+ }
+ }
}
var toolbar = id("qunit-testrunner-toolbar");
@@ -634,8 +681,15 @@ function validTest( name ) {
return run;
}
+function resultDisplayStyle(passed) {
+ return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : '';
+}
+
function escapeHtml(s) {
- s = s === null ? "" : s + "";
+ if (!s) {
+ return "";
+ }
+ s = s + "";
return s.replace(/[\&"<>\\]/g, function(s) {
switch(s) {
case "&": return "&amp;";
@@ -648,24 +702,6 @@ function escapeHtml(s) {
});
}
-function push(result, actual, expected, message) {
- message = escapeHtml(message) || (result ? "okay" : "failed");
- message = '<span class="test-message">' + message + "</span>";
- expected = escapeHtml(QUnit.jsDump.parse(expected));
- actual = escapeHtml(QUnit.jsDump.parse(actual));
- var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
- if (actual != expected) {
- output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
- }
-
- // can't use ok, as that would double-escape messages
- QUnit.log(result, output);
- config.assertions.push({
- result: !!result,
- message: output
- });
-}
-
function synchronize( callback ) {
config.queue.push( callback );
@@ -1255,7 +1291,7 @@ QUnit.diff = (function() {
}
return str;
- }
+ };
})();
})(this);
Something went wrong with that request. Please try again.