I have a function that records clicks on a link with a data tag. The code hides a telephone number until the link has been clicked, then it reveals the number. The problem I'm having is I am unable to get that revealed number to fire without user interaction once it has loaded.
I had to prevent the default event for when the button is clicked for the first time as the url does not yet contain the real data and also I don't want the page to jump.
Once clicked the new link's html will change to, example: "☎ 0123456789"
and the href will change to, example: "tel:0123456789"
This data is obtained from JSON sent from AJAX PHP file.
After this I want to programatically click the URL but I can't seem to get it to fire and keep getting a console error message saying "Uncaught TypeError: event is undefined" ~ presumably to do with the event prevent default?
Info: there will be multiple phone numbers loaded via PHP so I need to access each one individually.
HTML for the link;
<a data-num='$number' class='telephone' href='#'>☎ Click to call</a>
JS code (jQuery 3.4.1);
$(document).on('click','.telephone',function(event) {
event.preventDefault(); // Prevent page jump
var num = $(this).data("num");
$.ajax({
url: "myserverfile.php",
method: "POST",
data: {num: num},
dataType: "json",
success: function(a) {
var b = (a[0]['status']); // True or false
var c = (a[0]['tel']); // Number or error
// If result found change URL and click it to call number
if (b == true) {
$('.telephone[data-num="' + num + '"]').html("☎ " + c); // Change link text
$('.telephone[data-num="' + num + '"]').attr("href", "tel:" + c); // Change link href
$('.telephone[data-num="' + num +'"]').click(); // Update: This causes a loop because it runs the code again. I need to hide the original a tag and create a new one then trigger it.
}
}
});
});
Any suggestions?
Calling the .click() method doesn't create an event. The event is created when the user actually clicks something. If you console.log(event) it'll be undefined when called by .click(), which is where you're getting the error from. To get around that you could
if (event) { event.preventDefault(); }
But then you're going to run into recursion since you're basically
$(document).on('click', '.telephone', function() { $('.telephone').click(); });
To get around that you could add something like a loaded class
$(document).on('click', '.telephone:not(.loaded)', function() {
$('.telephone[data-num="' + num +'"]')
.addClass('loaded')
.attr(...)
.html(...)
.click();
});
Also, you'll thank yourself later for using a little more descriptive variable names. And for those unaware, jquery is chainable.
Figured it out. I was creating a loop so the click wouldn't work so I hid the first link after the click and loaded a new hidden one. I used a different column value from the table for the data tag on this new link to avoid any possible conflicts.
Apart from the loop issue the major part I was missing was [0] before assigning .click.
Thanks for the suggestions along the way.
Working code below;
$(document).on('click', '.telephone', function(event) {
event.preventDefault();
var num = $(this).data("num");
$.ajax({
url: "myserverfile.php",
method: "POST",
data: {num: num},
dataType: "json",
success: function(a) {
var b = (a[0]['status']);
var c = (a[0]['id']);
var d = (a[0]['tel']);
if (b == true) {
$('.telephone[data-num="' + num + '"]').hide(200);
$(document).ready(function() {
$(".telephone-reveal");
$('.telephone-reveal[data-id="' + c + '"]').show(200);
$('.telephone-reveal[data-id="' + c + '"]').html("☎ " + d);
$('.telephone-reveal[data-id="' + c + '"]').attr("href", "tel:" + d);
$('.telephone-reveal[data-id="' + c + '"]')[0].click();
});
}
}
});
});
Related
long time reader, first time submitter
It looks like i have the ability to insert javascript or HTML in this custom code box, but If it can be done using hTML that would be preferred.
I am trying to get the last string 'Variablex1x' which is dynamic based on the page being viewed. It is a unique identifier that corresponds to records on a different site. I would like to 'grab' that identifier and post it on the end of the target URL. When the user clicks the 'targetdomain.com' url, they are taken to the page of the targetdomain.com/Variablex1x
https://currentdomain.com/portal/x/mycase/Variablex1x
https://Targetdomain.com/Variablex1x
You can try something like this:
$( "#target" ).click(function() {
var Variablex1x;
var newUrl;
Variablex1x = getQueryVariable(nameofvariable)
if(Variablex1x != false){
window.location.href = newurl + "/" + Variablex1x; + "/" + Variablex1x;
}
else{
window.location.href = newurl;
}
});
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
getQueryVariable comes from
https://css-tricks.com/snippets/javascript/get-url-variables/ and will work as long as you know what variable you're looking for.
The idea is when you click on the link instead of actually navigating you'll fire the click function, so you'll need to update the target id. The click function will figure out if you have parameters or not, if you do it will append them to the URL and navigate, if not it will just navigate.
This is not a perfect solution but it should get you started.
IF you don't know what parameters you're looking for here is an answer of how to get those parameters: How can I get query string values in JavaScript?
I'm creating an extension that works with this structure of .js files:
Page A (content script) -> B (background script) -> Page C (content script)
On page A, there are links that, when clicked, send a message to B. The idea of B is to open up a new tab and load page C; when C is fully loaded, it'll send a message back to B, which will send some data to C to fill in a form.
This all works, but after putting in some logging I realized that my listeners in B are actually firing twice if more than one link is clicked from page A. Here's what the code looks like:
Page A:
$(document).ready(function () {
$('.click-test').click(function() {
chrome.runtime.sendMessage({formData: here}, function(response) {
});
});
});
B:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "formStatus");
port.onMessage.addListener(function(msg) {
if (msg.status == "formReady") {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {formData: here}, function(response) {
// The below is being called twice incorrectly if two links from page A are clicked
console.log("sent message for tab ID " + tabs[0].id);
});
});
}
});
});
chrome.tabs.create({url: requestFormUrl});
});
Page C:
$(document).ready(function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// Fill in form with request data
});
var port = chrome.runtime.connect({name: "formStatus"});
port.postMessage({status: "formReady"});
});
When the first link on page A is clicked, things work fine. When the second link is clicked, the onConnect listener in B fires twice: first with the same data as the first click, then with the correct data corresponding to the second click.
Is there some way to disable B's listener as soon as it fires once? Or, have I just made this code way too complicated?
Rob W. is right in the comments - removing the listener as he did within the addListener part worked. Thanks!
I want to build a url where parts of the url string are dynamic build with a select box.
The watch is only called at startup:
$scope.$watch('url', function () {
$scope.buildUrl();
}, true);
Here is the fiddle:
http://jsfiddle.net/mkeuschn/wJGFm/
best regards
The $watch will only fire if the value being watched changes. In this case, it is better to watch dimension, since this is the selection that is changing. Then, you can re-assign the dimension part of the url and re-build.
Here is an updated fiddle.
JS:
$scope.buildUrl = function () {
$scope.url.dimension = $scope.dimension.value;
$scope.completeUrl = $scope.url.base + "dateFrom=" + $scope.url.from + "&dateTo=" + $scope.url.to + "&dimension=" + $scope.url.dimension;
};
$scope.$watch('dimension', function () {
$scope.buildUrl();
}, true);
I'm trying to teach myself how to write Chrome extensions and ran into a snag when I realized that my jQuery was breaking because it was getting information from the extension page itself and not the tab's current page like I had expected.
Quick summary, my sample extension will refresh the page every x seconds, look at the contents/DOM, and then do some stuff with it. The first and last parts are fine, but getting the DOM from the page that I'm on has proven very difficult, and the documentation hasn't been terribly helpful for me.
You can see the code that I have so far at these links:
Current manifest
Current js script
Current popup.html
If I want to have the ability to grab the DOM on each cycle of my setInterval call, what more needs to be done? I know that, for example, I'll need to have a content script. But do I also need to specify a background page in my manifest? Where do I need to call the content script within my extension? What's the easiest/best way to have it communicate with my current js file on each reload? Will my content script also be expecting me to use jQuery?
I know that these questions are basic and will seem trivial to me in retrospect, but they've really been a headache trying to explore completely on my own. Thanks in advance.
In order to access the web-pages DOM you'll need to programmatically inject some code into it (using chrome.tabs.executeScript()).
That said, although it is possible to grab the DOM as a string, pass it back to your popup, load it into a new element and look for what ever you want, this is a really bad approach (for various reasons).
The best option (in terms of efficiency and accuracy) is to do the processing in web-page itself and then pass just the results back to the popup. Note that in order to be able to inject code into a web-page, you have to include the corresponding host match pattern in your permissions property in manifest.
What I describe above can be achieved like this:
editorMarket.js
var refresherID = 0;
var currentID = 0;
$(document).ready(function(){
$('.start-button').click(function(){
oldGroupedHTML = null;
oldIndividualHTML = null;
chrome.tabs.query({ active: true }, function(tabs) {
if (tabs.length === 0) {
return;
}
currentID = tabs[0].id;
refresherID = setInterval(function() {
chrome.tabs.reload(currentID, { bypassCache: true }, function() {
chrome.tabs.executeScript(currentID, {
file: 'content.js',
runAt: 'document_idle',
allFrames: false
}, function(results) {
if (chrome.runtime.lastError) {
alert('ERROR:\n' + chrome.runtime.lastError.message);
return;
} else if (results.length === 0) {
alert('ERROR: No results !');
return;
}
var nIndyJobs = results[0].nIndyJobs;
var nGroupJobs = results[0].nGroupJobs;
$('.lt').text('Indy: ' + nIndyJobs + '; '
+ 'Grouped: ' + nGroupJobs);
});
});
}, 5000);
});
});
$('.stop-button').click(function(){
clearInterval(refresherID);
});
});
content.js:
(function() {
function getNumberOfIndividualJobs() {...}
function getNumberOfGroupedJobs() {...}
function comparator(grouped, individual) {
var IndyJobs = getNumberOfIndividualJobs();
var GroupJobs = getNumberOfGroupedJobs();
nIndyJobs = IndyJobs[1];
nGroupJobs = GroupJobs[1];
console.log(GroupJobs);
return {
nIndyJobs: nIndyJobs,
nGroupJobs: nGroupJobs
};
}
var currentGroupedHTML = $(".grouped_jobs").html();
var currentIndividualHTML = $(".individual_jobs").html();
var result = comparator(currentGroupedHTML, currentIndividualHTML);
return result;
})();
First of all, I'm not sure if my title describes the problem correctly... I did search but didn't find anything that helped me out...
The project I'm working on has an #orderList. All orders have a delete option. After an order gets deleted the list is updated.
Sounds simple... I ran into a problem though.
/**
* Data returned at the end of selecting some options
*/
$.post(myUrl, $('#myForm').serialize(), function(data) {
// I build the orderlist
// The data returned is a JSON object holding session data (including orders)
buildOrderList(data);
...
// Do some other work
});
/*
* function to build the html list
*/
function buildOrderList(data) {
// Empty list
$('#orderList').empty();
// The click handler for the delete button is in here because it needs the data object
$(document).on('click', '[id^=delete_]', function() {
// Get the orderId from the delete button
var orderId = $(this).attr('id').split('_');
orderId = orderId['1'];
// I call the delete function
deleteOrder(orderId, data);
});
var html = '';
// Loop the data object
$.each(data, function(key,val){
...
// Put html code needed in var html
...
});
$('#orderList').append(html);
}
/*
* function to delete an order
*/
function deleteOrder(orderId, data) {
// Because of it depends on other 'products' in the list if the user can
// simply delete it, I use a jQuery dialog to give him some options.
// These options I send to a php script so it knows what should be deleted.
// This fires when a user clicks on the 'delete' button from a dialog.
// The dialog uses data to show options but does not change the value of data.
switch(data.type) {
case 'A':
delMsg += '<p>Some message for case A</p>';
delMsg += '<select>with some options for case A</select>';
$('#wizard_dialog').append(delMsg);
$('#wizard_dialog').dialog('option', 'buttons', [
{ text: "Delete", click: function() {
$.post(myUrl, $('#myDeleteOptions').serialize(), function(newData) {
// Now the returned data is the updated session data
// So I build the orderList again...
buildOrderList(newData);
...
// Do some other work
});
$( this ).dialog( "close" );
$(this).html(''); }},
{ text: "Cancel", click: function() { $( this ).dialog("close"); $(this).html(''); }}
] );
break;
case 'B':
// Do the same thing but different text and <select> elements
break;
}
}
The orderList updates correctly, however if I try to delete another order, the jQuery dialog gives me the option for the current (correct product) AND the option for the product that previously owned the id of the current. (Hope I didn't loose anyone in my attempt to explain the problem)
The main question is how to 'refresh' the data send to buildOrderList.
Since I call the function in a new $.post with fresh data object returned it should work, shouldn't it?
/**
* Enable the JQuery dialog
* (#wizard_dialog)
* this is the init (note that I only open the dialog in deleteOrder() and set text and buttons according to the data send to deleteOrder() )
*/
$('#wizard_dialog').dialog({
autoOpen: false,
resizable: false,
modal: true,
dialogClass: "no-close",
open: function() {
$('.ui-dialog-buttonpane').find('button:contains("Annuleren")').addClass('cancelButtonClass');
$('.ui-dialog-buttonpane').find('button:contains("Verwijderen")').addClass('deleteButtonClass');
$('.ui-dialog :button').blur(); // Because it is dangerous to put focus on 'OK' button
$('.ui-widget-overlay').css('position', 'fixed'); // Fixing overlay (else in wrong position?)
if ($(document).height() > $(window).height()) {
var scrollTop = ($('html').scrollTop()) ? $('html').scrollTop() : $('body').scrollTop(); // Works for Chrome, Firefox, IE...
$('html').addClass('noscroll').css('top',-scrollTop); // Prevent scroll without hiding the bar (thus preventing page to shift)
}
},
close: function() {
$('.ui-widget-overlay').css('position', 'absolute'); // Brake overlay again
var scrollTop = parseInt($('html').css('top'));
$('html').removeClass('noscroll'); // Allow scrolling again
$('html,body').scrollTop(-scrollTop);
$('#wizard_dialog').html('');
}
});
EDIT:
Because the problem could be in the dialog I added some code.
In the first code block I changed deleteOrder();
ANSWER
The solution was rather simple. I forgot to turn the click handler off before I added the new one. This returned the previous event and the new event.
$(document).off('click', '[id^=delete_]').on('click', '[id^=delete_]', function() {
// Get the orderId from the delete button
var orderId = $(this).attr('id').split('_');
orderId = orderId['1'];
// I call the delete function
deleteOrder(orderId, data);
});