countdown disappears after its parent div is loaded via .load jquery method - javascript

I have a countdown which is activated after the page is loaded:
$(document).ready(function() {
var rem_hour = $("#user-card-second-remaining").attr("rem_hour");
var rem_min = $("#user-card-second-remaining").attr("rem_min");
var rem_sec = $("#user-card-second-remaining").attr("rem_sec");
var currentDate = new Date();
var targetDate = new Date(currentDate.getTime() + rem_hour * 60 * 60000 + rem_min*60000 + rem_sec * 1000);
$("#user-card-second-remaining").countdown(targetDate, function(event) {
$(this).html(event.strftime('%H:%M:%S'));
});
});
Every minute, I load a div partially of this page, and #user-card-second-remaining is inside this loaded div.
function fn60sec() {
// runs every 60 sec and runs on init.
var pathname = window.location.pathname;
var card = pathname + " #user-card";
$( "#user-card-wrapper" ).load(card);
}
The problem is that when I loaded new #user-card-second-remaining div, countdown event disappears.
It seems, I think, I should refresh dom (not page) such that .countdown method could be bind into fresh #user-card-second-remaining div.
How can I do that?

When the DOM is updated, all jquery stuff attached to DOM elements are removed.
Well actually, the DOM element with the jquery styff is removed, and replaced with another event, brand new, without jquery stuff.
What you need to do, is add the jquery stuff again after you update the DOM, as you suggested.
function fn60sec() {
// runs every 60 sec and runs on init.
var pathname = window.location.pathname;
var card = pathname + " #user-card";
$( "#user-card-wrapper" ).load(card, function() {
var rem_hour = $("#user-card-second-remaining").attr("rem_hour");
var rem_min = $("#user-card-second-remaining").attr("rem_min");
var rem_sec = $("#user-card-second-remaining").attr("rem_sec");
var currentDate = new Date();
var targetDate = new Date(currentDate.getTime() + rem_hour * 60 * 60000 + rem_min*60000 + rem_sec * 1000);
$("#user-card-second-remaining").countdown(targetDate, function(event) {
$(this).html(event.strftime('%H:%M:%S'));
});
});
});
should do the trick.

What library/plugin is .countdown( from? You need to look at their documentation for a delegated event overload, or an alternative using an event name so you can use jquery .on('eventname',
Usually in this situation you use a pattern
$('#staticSelector').on('countdown', '#user-card-second-remaining', function() ...
and this listens on #staticSelector for events bubbling up from '#user-card-second-remaining'. So #staticSelector needs to be an element that always exists, but the '#user-card-second-remaining' can come and go as needed being refreshed inside your div that is being refreshed.
The section here on Direct and delegated events explains this:
http://api.jquery.com/on/

Related

Infinite self invoking function

I am working on a piece of code that is supposed to randomly display a view count on my site. It all works fine, except that it only runs once on page load. Its supposed to change the view count every 10 seconds after the initial first run. E.g. run this code snippet every 10 seconds after page load. I have tried multiple ways with setInterval but found that it is supposedly bad practice to use anyways after so many failed attempts?
Could you guys point me in the right direction. I have been reading posts on Stack overflow for hours and none had an answer to my issue. I have a feeling, that I am overlooking something obvious here.
document.addEventListener('page:loaded', function() { //// Page has loaded and theme assets are ready
(function() {
var f = function() {
document.addEventListener('DOMContentLoaded', function() {
// Minimum view count
var minViews = 2;
// Maximum view count
var maxViews = 20;
// Text to show after the view count number
var text = 'people are viewing this product right now.';
// Create the new element to display on the page
$(".view-count").get().forEach(function(entry, index, array) {
var $viewCountElement = Math.floor(Math.random() * (maxViews - minViews) + minViews) + ' ' + text;
$(entry).html($viewCountElement);
});
});
};
window.setInterval(f, 10000); //10 sec interval
f();
})();
});
You may not need nested event listener. You can just call the setInterval in the call back function of DOMContentLoaded.
function elementDisplay() {
// Minimum view count
var minViews = 2;
// Maximum view count
var maxViews = 20;
// Text to show after the view count number
var text = 'people are viewing this product right now.';
// Create the new element to display on the page
$(".view-count").get().forEach(function(entry, index, array) {
var $viewCountElement = Math.floor(Math.random() * (maxViews - minViews) + minViews) + ' ' + text;
$(entry).html($viewCountElement);
});
}
document.addEventListener('DOMContentLoaded', function() {
window.setInterval(elementDisplay, 10000); //10 sec interval
});
setInterval is a browser based API, zo it should be cleaned up from the browser properly.
This is majorly for your proper implementation of setInterval. You can add your listeners, too.
FYI: jQuery.ready/DOMContentLoaded occurs when all of the HTML is ready to interact with, but often before its been rendered to the screen.
var interval;
$(document).ready(function(){
console.log("I am initialized")
var f = function() {
console.log("I am called")
// Minimum view count
var minViews = 2;
// Maximum view count
var maxViews = 20;
// Text to show after the view count number
var text = 'people are viewing this product right now.';
// Create the new element to display on the page
$(".view-count").get().forEach(function(entry, index, array) {
var $viewCountElement = Math.floor(Math.random() * (maxViews - minViews) + minViews) + ' ' + text;
$(entry).html($viewCountElement);
});
};
interval = setInterval(f, 10000);
});
$(window).on("beforeunload", function() {
// clear the interval when window closes
return clearInterval(interval);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Note: Check the console for output

function not working properly when run on document ready

I have a form with 2 fields: date field #datefromtoday and number of days #daysfromtoday. I use a javascript function to:
1) Automatically listen to the datefromtoday and (if there is a date) display the number of days from today when the page is loaded
2) adjust the date from today when entering/modify the number of days.
Here is the code:
$(document).ready(function (){
function modifyDays(){ //definy function to modify days
var endDateToDays = $( "#datefromtoday" ).val();
var endDateToDays_obj = new Date(endDateToDays); // convert in object
var endDateToDays_ms = endDateToDays_obj.getTime(); // convert in ms
var todayDate = new Date(); //
var todayDate_ms = todayDate.getTime(); //
var daysFromToday = parseInt(Math.ceil( (endDateToDays_ms - todayDate_ms) / 1000 / 60 / 60 / 24 ) ) || ''; //if not number display nothing
document.getElementById("daysfromtoday").value = daysFromToday; //outuput
}
modifyDays(); //here is the problem. If I delete this line of code, everything works perfectly
$("#datefromtoday").on('change', function(){ //run function when modify delay date
modifyDays();
});
});
PROBLEM
the modifyDays function works like a charm on the on.change event, but
when is loaded on document ready, it interferes with datatables www.datatables.net and also with other scripts, and they don't work anymore...
I'm probably using the wrong code to call the function on page load.... any ideas? Thanks for your help!!!
If you think it is because of calling the main function, in the body element of the html page, add an onload attribute:
<body onload="loaded()">
and declare loaded as that main function:
var loaded = function (){
function modifyDays(){ //definy function to modify days
var endDateToDays = $( "#datefromtoday" ).val();
var endDateToDays_obj = new Date(endDateToDays); // convert in object
var endDateToDays_ms = endDateToDays_obj.getTime(); // convert in ms
var todayDate = new Date(); //
var todayDate_ms = todayDate.getTime(); //
var daysFromToday = parseInt(Math.ceil( (endDateToDays_ms - todayDate_ms) / 1000 / 60 / 60 / 24 ) ) || ''; //if not number display nothing
document.getElementById("daysfromtoday").value = daysFromToday; //outuput
}
modifyDays(); //here is the problem. If I delete this line of code, everything works perfectly
$("#datefromtoday").on('change', function(){ //run function when modify delay date
modifyDays();
});
};
Then it should work if the problem is how the function is called.
I don't think the problem is because of you are calling the function on page load. The error might be coming from inside the function modifyDays. Only dependency I see is #datefromtoday and #daysfromtoday. Check whether those nodes are there when the function execute on dom ready event.
If the timing of the function call is the problem, you can put the modifyDays(); call in a window.setTimeout(modifyDays, 5000); or something of the sort to delay it until the other scripts finish loading, so this snippet doesn't interrupt or interfere with them. You may want to put a placeholder in the html for the seconds when it isn't loaded yet if this works.

Updating Momentjs FromNow() in a div element

I'm having this div element that shows the time past since it got created. However it doesn't get updated and always remains on few seconds ago. It looks like this
var newMsg= "<div id="chat-time">'+ moment().fromNow()+'</div>";
$("#chat-list").html( newMsg);
How can I update this text. I know I can do it with sentInterval but I can't figure out how to do it properly.It just prints out seconds! I'm using this for a chatroom. So each message will have a timestamp in the formatof momentjs.fromNow().
Does setting timer for all these message create a problem? I'd appreciate a hint.
EDIT:I'm using this code as mentioned in below but it's not showing anything:
<div id="chat-time"></div>
var messageTimeStamp = new Date();
setInterval(function(){
var time = moment(messageTimeStamp).fromNow();
$("#chat-time").html(time);
}, 1000);
To make this work you need the element in the dom and the setInterval running without being included in any string concatenation
HTML
<div id="chat-time"></div>
JS
var $chatTime = $('#chat-time').text(moment().fromNow());
setInterval(function(){
var time = moment().fromNow();
$chatTime.txt( time );
}, 1000);
UPDATE 2
Given that you're using socket.io, you'd do something like this (demo: http://plnkr.co/edit/QuaMV6x1vNB0kYPaU6i1?p=preview):
// The messages the user can currently see.
var messages = [];
// You have something like this in your code, presumably.
socket.on('new message', function(data) {
addChatMessage(data);
});
function addChatMessage(data) {
// First add the message to the dome, with a unique id for the timestamp text.
var messageElementId = 'chat-message-' + data.messageId;
$("#chat-list").prepend($("<div>" + data.message + "<i> (sent: <span id='" + messageElementId + "'>just now</span>)</i></div>"));
// When you no longer display that message in the DOM it from clear this array. I'd render the DOM based on this list if I were you.
messages.push({
messageElementId: messageElementId,
timestamp: data.timestamp
});
}
// By updating all the messages at once you don't have memory leaks.
setInterval(function() {
messages.forEach(function(message) {
var time = moment(message.timestamp).fromNow();
$("#" + message.messageElementId).text(time);
});
}, 1000);
UPDATE 1
Given this is your code:
var newMsg= "<div id="chat-time">'+ moment().fromNow()+'</div>";
$("#chat-list").html(newMsg);
You would do this, instead:
var messageTimeStamp = new Date(); // You need to grab this from somewhere.
setInterval(function(){
var time = moment(messageTimeStamp).fromNow();
$("#chat-list").html(time);
}, 1000);
You need to use moment(TIMESTAMP_OF_MESSAGE) not moment() and do something like this:
$(function(){
$("body").append($('<div id="chat-time"></div>'));
var messageTimeStamp = new Date();
var i = 0;
setInterval(function(){
var time = moment(messageTimeStamp).fromNow();
$("#chat-time").html('moment().from(messageTimeStamp): ' + time + '; setInterval calls made ' + i++);
}, 1000);
});
Here's a demo.
http://plnkr.co/edit/QuaMV6x1vNB0kYPaU6i1?p=preview
I dont see any problem using setInterval (). AngularJS wrapper setInterval on $interval service module . Check out these urls: interval Angular and Wrapper SetInterval

How to make my userscript work on specific pages of a site that uses the history api?

(This is in continuation of this discussion)
I've been trying to make a script (for instagram) that shows how many images out of total are currently visible when viewing a profile page (example profile). It shows the counter in the upper right corner of the screen and supports infinite scrolling.
Here it is:
// ==UserScript==
// #name Instagram - visible images counter
// #name Instagram - visible images counter
// #include https://instagram.com/*
// #grant none
// ==/UserScript==
var p = document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root');
var m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count');
var ww = m[0].innerHTML.replace(/(\d+),(?=\d{3}(\D|$))/g, "$1"); // Regex to strip the thousand comma seperator from the posts number
var z = (p.length/ww)*100;
var counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%';
var div = document.createElement("div");
div.style.top = "1px";
div.style.right = "1px";
div.style.position = "fixed";
document.body.appendChild(div);
div.id="mycounter";
mycounter = document.getElementById('mycounter');
mycounter.innerHTML = counter;
mycounter.style.top = "1px";
mycounter.style.right = "1px";
mycounter.style.position = "fixed";
/// ---------------------------------
/// mutation observer -monitors the Posts grid for infinite scrolling event-
/// ---------------------------------
var target1 = document.querySelector('.-cx-PRIVATE-PostsGrid__root');
var observer1 = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
p=document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root');
m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count');
ww = m[0].innerHTML.replace(/(\d+),(?=\d{3}(\D|$))/g, "$1");
z = (p.length/ww)*100;
counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%';
mycounter.innerHTML = counter;
});
})
var config = { attributes: true, childList: true, characterData: true }
observer1.observe(target1, config);
My script works ok, but I have this issue:
Instagram, after it's recent redesign -I think-, seems to use single-page application workflow,
i.e fetches the clicked link content and replaces the current page with it, and then changes the browser's current URL, all without actually reloading the page.
So, my script only works when I open a profile URL in a new tab.
It doesn't work when opening a profile URL in the same tab while in my timeline (https://instagram.com)).
In other words, it doesn't work (after I open https://instagram.com and login)
if I click to view a profile URL of a user I follow, eg. https://instagram.com/instagram/
How can I fix this?
Someone has kindly suggested these 3 ways:
Try an event handler for some event fired by the Instagram site like
pjax:end on github, for example.
Use a mutation observer that waits a profile-specific html element.
Or use setInterval to check location.href periodically.*
So, I've been trying various approaches (including the suggestions #2 and #3) but no success.
(about suggestion #1: I can't find any element similar to pjax:end).
So, what I've tried so far:
(based on suggestion #2) adding another mutation observer to check whether the element that shows the 'posts count' element exists, and if yes, then run my code.
var target0 = document.querySelector('.-cx-PRIVATE-PostsStatistic__count');
var observer0 = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
myCode();
});
})
var config0 = { attributes: true, childList: true, characterData: true }
observer0.observe(target0, config0);
(based on suggestion #3) checking location.href every 1 second whether the current location is a profile (i.e. not the timeline (https://instagram.com/). If true then clear the periodic function and run my previous code.
var interval = setInterval(testHref() , 1000);
function testHref(){
if (location.href != "https://instagram.com/")
clearInterval(interval);
myCode();
}
simply adding a 1 sec delay on top of my code (and changing the #require rule to apply only on profile URLs // #include https://instagram.com/*/), but no success:
setTimeout(function() { }, 1000);
I've even tried using the waitForKeyElements utility function, which detects and handles AJAXed content.
I thought it's much easier to implement this way, working as a simple "wait until an element exists" (I just used the main profile pic as the selector to wait for, because I couldn't find any relevant AJAX selector. Also I didn't use jNode inside my code).
So I just enclosed my whole code in a single function visibleCounter, and added a waitForKeyElements line (see below), but unfortunately it also doesn't work:
waitForKeyElements (".-cx-PRIVATE-ProfilePage__avatar", visibleCounter);
function visibleCounter(jNode){
myCode()
}
I solved this using the arrive.js library. It provides events to watch for DOM elements creation and removal. It makes use of Mutation Observers internally. The library does not depend on jQuery, you can replace jQuery elements in the examples below with pure javascript elements and it would work fine.
I quote this comment by its author:
I've always found MutationObserver api a bit complex so I've built a
library, arrive.js, to provide a simpler api to listen for elements
creation/removal.
as well as just two of its uses:
Watch for elements creation:
Use arrive event to watch for elements creation:
// watch for creation of an element which satisfies the selector ".test-elem"
$(document).arrive(".test-elem", function() {
// 'this' refers to the newly created element
var $newElem = $(this);
});
and
Watch for elements removal
Use leave event to watch for elements removal. The first arugument to
leave must not be a descendent or child selector i.e. you cannot pass
.page .test-elem, instead, pass .test-elem. It's because of a
limitation in MutationObserver's api.
// watch for removal of an element which satisfies the selector ".test-elem"
$(".container-1").leave(".test-elem", function() {
var $removedElem = $(this);
}); [1]: https://github.com/uzairfarooq/arrive
And, this is the complete script:
// ==UserScript==
// #name Instagram - visible images counter
// #include https://www.instagram.com/*
// #grant none
// #require https://code.jquery.com/jquery-3.0.0.min.js
// #require https://greasyfork.org/scripts/21927-arrive-js/code/arrivejs.js?version=139586
// ==/UserScript==
function showCounter() {
var visibleCount = $( "a[href*='taken-by']" ).length; // Count of visible images
var totalString = $("span:contains('posts')").eq(1).children().eq(0).html(); // The 'total' value (it's a string)
var total = totalString.replace(/(\d+),(?=\d{3}(\D|$))/g, '$1'); // Apply regex to 'total' string to strip the thousand comma seperator
if (visibleCount > total){
visibleCount = total;
}
var visiblePercent = ((visibleCount / total) * 100).toFixed(1); // Visible images as percentage
var counter = visibleCount + ' / ' + totalString + ' that is ' + visiblePercent + '%';
return counter;
}
function createDiv(){
// Creation of the counter element
document.body.appendChild(div);
div.innerHTML = showCounter(); // Initial display of the counter
div.style.top = '1px';
div.style.right = '1px';
div.style.position = 'fixed';
}
function observer(){
/// ---------------------------------
/// mutation observer -monitors the Posts grid for infinite scrolling event-.
/// ---------------------------------
observer1 = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
div.innerHTML = showCounter(); // In each infinite scrolling event, re-calculate counter
});
}).observe($('article').children().eq(1).children()[0], // target of the observer
{
// attributes: true,
childList: true,
// characterData: true,
}); // config of the observer
}
var div = document.createElement('div'); // global variable
var observer1; // global variable
if (document.URL !== 'https://www.instagram.com/' &&
document.URL.indexOf('https://www.instagram.com/p/') === -1 ){
createDiv();
observer();
}
$(document).arrive('article ._5axto', function() { // 'article .5axto'
createDiv();
observer();
});
$(document).leave('article ._5axto', function() {
div.remove();
observer1.disconnect();
});
Copied from my answer here:
For a similar use case, I used MutationObserver and checked node.baseURI of each node in each mutation.addedNodes.
(async function() {
'use strict';
const selector = '.quiz--answered';
const observer = new MutationObserver(mutations => {
if (!mutations.some(mutation => Array.from(mutation.addedNodes).some(node => node.baseURI.includes('/quiz/')))) {
return;
}
const elm = document.querySelector(selector);
if (elm) {
// work with elm
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();

turn.js and Google Analytics

I'm trying to gather either an event or custom variable tracking on the turn.js plugin through Google Analytics. Right now it's set up for a custom variable. I'm trying to track time on page turns. In the plugin there is an event handler called 'turned'. This is an event that runs when the page has been turned. I have my analytics set up here, but they are collecting to google incorrectly. Any help with this is appreciated. I have the JS event below:
when: {
turned: function(event, page, view) {
_gaq.push(['_setCustomVar', 1, 'Page Spread Scope 1', 'page spread ' + view, 1 ]);
_gaq.push(['_trackPageview']);
}
}
Not sure what exact data you want but I think it is something like this.
Just wrote it right now, so haven't fully tested it. I did debug it and it seems to work fine.
It should record the page+pagenumber and it should record the time on that page with the new _trackTiming method
For your page turn this snippet:
<script>
$('#magazine').turn().bind("click", function(event){
pagenumber = $('#magazine').turn('page');
myTimer();
});
</script>
And then this in your head somewhere.
<script>
var startTime = new Date().getTime();
function myTimer(event) {
var endTime = new Date().getTime();
var timeSpent = endTime - startTime;
var hourInMillis = 1000 * 60 * 60;
if (0 < timeSpent && timeSpent < hourInMillis) {
_gaq.push(['_trackTiming', 'page-timer', 'time-on-page', timeSpent, pagenumber, 1]);
_gaq.push(['_trackPageview', 'turnedpage-' +pagenumber]);
}
return this;
}
//wiremedia.nl
</script>

Categories

Resources