I am working on a chrome extension with a popup which shows up when you click on the extension icon. On popup, I have a button which once clicked shows loading box on the currently open tab page.
Screenshot:
The loading box is removed after some time using setTimeout. However this works only when popup itself is VISIBLE. If I click on button on popup and then go to some other tab and come back or click elsewhere on tab page, the popup hides BUT loading box remains visible.
Does any one know how to hide the loading box even if popup goes invisible/out of focus ? I thought it would go away since there is setTimeout function which removes it but it doesn't work when popup loses focus.
Instead of pasting all relevant code here, here is the download link for the extension so that you could see exactly what I mean.
In actual extension, I have ajax request though instead of setTimeout:
$.ajax({
url : 'localhost url here....',
data : data, // this is searialized form data
dataType : 'json',
method : 'post',
success : function (r) {
if (r.success) {
window.close();
var notification = webkitNotifications.createNotification(
'img/48.png',
'Done!',
'The page has been saved successfully :)'
);
notification.show();
setTimeout(function () {
notification.cancel();
}, 5000);
}
else {
if (r.error) {
$ediv.text(r.error).fadeIn('fast');
}
}
},
error : function (r) {
$ediv.text('Unknown error, please try again later.').fadeIn('fast');
},
complete : function (r) {
chrome.tabs.executeScript(
null, {code : "document.body.removeChild(document.getElementById('__wnoverlay__'))"}
);
}
});
Thanks for your help
Steps
Move this AJAX Request to Background Page.
On Click on Button(Where your dialog box is injected to page) pass message to background Scripts to Store tab.id(Check next point).
Using tab.id received from browser action execute your removal dialog box code(Tab id is needed because user can switch his active tab\window any time).
References
Message Passing
Fetching details of active tab
EDIT 1
Add following in manifest file ensure you register background and jquery with background Page.
"background":{
"scripts":["js/jquery.js","background.js"]
},
Add following code in background.js
This code migrates AJAX Call to background Page and executes removal of dialog box after 5 seconds threshold.
function invokeAJAX(tabid) {
$.ajax({
url: 'localhost url here....',
data: data, // this is searialized form data
dataType: 'json',
method: 'post',
success: function (r) {
if (r.success) {
window.close();
var notification = webkitNotifications.createNotification(
'img/48.png',
'Done!',
'The page has been saved successfully :)');
notification.show();
setTimeout(function () {
notification.cancel();
}, 5000);
} else {
if (r.error) {
$ediv.text(r.error).fadeIn('fast');
}
}
},
error: function (r) {
$ediv.text('Unknown error, please try again later.').fadeIn('fast');
},
complete: function (r) {
chrome.tabs.executeScript(
tabid, {
code: "document.body.removeChild(document.getElementById('__wnoverlay__'))"
});
}
});
}
Your popup.js looks like this where you invoke functions of background Page directly
document.addEventListener("DOMContentLoaded", function () {
$('#btn').click(function () {
// show loading message
// chrome.extension.sendRequest({}, function(response) {});
chrome.tabs.executeScript(null, {
"code": 'var __a=document.createElement("DIV");__a.id="__wnoverlay__";__a.style.width="300px";__a.style.height="80px";__a.style.position="fixed";__a.style.top="50%";__a.style.left="50%";__a.style.color="#fff";__a.style.zIndex=9999999;__a.style.opacity=0.8;__a.style.textAlign="center";__a.style.padding="10px";__a.style.border="12px solid #cccccc";__a.style.marginLeft="-150px";__a.style.marginTop="-40px";__a.style.fontWeight="bold";__a.style.fontSize="17px";__a.style.borderRadius="10px";__a.innerHTML="Working, please wait...";document.body.appendChild(__a);'
});
chrome.tabs.query({}, function (tab) {//Get current tab
chrome.extension.getBackgroundPage().invokeAJAX(tab.id);//DO Ajax call and delete div added after 5 sec to current tab only
});
});
});
EDIT 2
popup.js
Changes made to popup.js
Made tabs.query to fetch only current active browsing normal window
Call back returns tab array so used tab[0] index.
After these changes it sends correct message.
document.addEventListener("DOMContentLoaded", function () {
$('#btn').click(function () {
var $this = $(this);
chrome.tabs.executeScript(
null, {
"code": 'var __a=document.createElement("DIV");__a.id="__wnoverlay__";__a.style.width="300px";__a.style.height="80px";__a.style.position="fixed";__a.style.top="50%";__a.style.left="50%";__a.style.color="#fff";__a.style.background="url(http://groot.com/WebNote_HTML/ChromeExtension/img/spinner.gif) center no-repeat #999999";__a.style.zIndex=9999999;__a.style.opacity=0.8;__a.style.textAlign="center";__a.style.padding="10px";__a.style.border="12px solid #cccccc";__a.style.marginLeft="-150px";__a.style.marginTop="-40px";__a.style.fontWeight="bold";__a.style.fontSize="17px";__a.style.borderRadius="10px";__a.innerHTML="Working, please wait...";document.body.appendChild(__a);'
});
//Proper Query Formation
chrome.tabs.query({
"active": true,
"status": "complete",
"currentWindow": true,
"windowType": "normal"
}, function (tab) { //Get current tab
//DO Ajax call
//tab is an array so we need to access its first index
chrome.extension.getBackgroundPage().invokeAJAX(tab[0].id, $this.closest('form').serialize());
});
});
});
background.js
Changes made to background.js
Eliminated $ediv.text code references as it is undefined in background page.
After these changes this is final code.
function invokeAJAX(tabid, data) {
data = data || '';
$.ajax({
url: 'http://groot.com/WebNote_HTML/ChromeExtension/savePage.php',
data: data,
dataType: 'json',
method: 'post',
success: function (r) {
if (r.success) {
// window.close();
var notification = webkitNotifications.createNotification(
'img/48.png',
'Done!',
'The page has been saved successfully :)');
notification.show();
setTimeout(function () {
notification.cancel();
}, 5000);
} else {
if (r.error) {
//$ediv.text(r.error).fadeIn('fast');
console.log("Error .." + r);
}
}
},
error: function (r) {
//$ediv.text('Unknown error, please try again later.').fadeIn('fast');
console.log("Error .." + r);
},
complete: function (r) {
chrome.tabs.executeScript(
tabid, {
code: "document.body.removeChild(document.getElementById('__wnoverlay__'))"
});
}
});
}
EDIT 3
$('#btn').click(function () {
var $this = $(this);
//Proper Query Formation
chrome.tabs.query({
"active": true,
"status": "complete",
"currentWindow": true,
"windowType": "normal"
}, function (tab) { //Get current tab
//DO Ajax call
//tab is an array so we need to access its first index
chrome.tabs.executeScript(
tab[0].id, {
"code": 'var __a=document.createElement("DIV");__a.id="__wnoverlay__";__a.style.width="300px";__a.style.height="80px";__a.style.position="fixed";__a.style.top="50%";__a.style.left="50%";__a.style.color="#fff";__a.style.background="url(http://groot.com/WebNote_HTML/ChromeExtension/img/spinner.gif) center no-repeat #999999";__a.style.zIndex=9999999;__a.style.opacity=0.8;__a.style.textAlign="center";__a.style.padding="10px";__a.style.border="12px solid #cccccc";__a.style.marginLeft="-150px";__a.style.marginTop="-40px";__a.style.fontWeight="bold";__a.style.fontSize="17px";__a.style.borderRadius="10px";__a.innerHTML="Working, please wait...";document.body.appendChild(__a);'
});
$('#url').val(tab[0].url);
$('#title').val(tab[0].title);
$loader.hide();
chrome.extension.getBackgroundPage().invokeAJAX(tab[0].id, $this.closest('form').serialize());
});
});
The popup code stops executing when the is not shown. However, the injected code is always executed. So you should set the timeout in the injected code, like this:
chrome.tabs.executeScript(null, {"code": 'setTimeout(function(){ document.body.removeChild(document.getElementById("__wnoverlay__")); }, 5000)'});
Replace the code from line 13-15 with the above code and it should work.
Related
I've got an ASP-NET/MVC/Bootstrap app in which I've implemented a progress bar that displays in a modal window during a long running search process.
The sequence of events is this:
User clicks button to initiate process. This button submits the form. There is an on-click event on the button that calls a javascript function (showProcessingModal()) to show the modal window and then initiate the recursive ajax callback that gets the status of the process and displays it in the modal:
var searchCancel;
function showProcessingModal() {
var sid = performance.now();
$("#SearchId").val(sid);
$("#PleaseWaitModal").modal("show");
getSearchProgress(sid);
}
function getSearchProgress(sid) {
$.ajax({
url: '#Url.Action("SearchProgress")',
method: "POST",
data: { SearchId: sid },
success: function (result) {
$('.progress-bar').css('width', result.pct + '%').attr('aria-valuenow', result.pct);
if (result.msg != "") {
$('#ProgressMessage').html(result.msg);
}
if (result.pct == 100) {
$('.progress-bar').removeClass('active')
}
if (result.pct <= 100) {
searchCancel = setTimeout(function () {
getSearchProgress(sid);
}, 500);
}
},
error: function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
}
});
}
This works beautifully in IE and Chrome. In FireFox, the modal shows but the ajax call fails because it seems FireFox has closed the socket on account of the page being submitted. I'm assuming Safari is doing the same although with Safari, the modal doesn't even show. I have yet debugged why.
I'm suspecting that I'm going to have to go a different route to make this work for all browsers. I'm thinking that the form post is what's messing things up.
Thoughts anyone?
I am developing a chrome extension in which one can select a color scheme from list given in popup and apply it to the open (highlighted) tab. From one of code snippet I comes to know that using code : "document.body.style.backgroundColor='red'" in chrome.tabs.executeScript change the background color. but there is only one line in code.
What my steps are
select the color scheme from popup
get the class name of the selected li
apply that class to the DOM document
Please see the code below
popup.js
document.addEventListener('DOMContentLoaded', function () {
var li = document.querySelectorAll('li');
for (var i = 0; i < li.length; i++) {
li[i].addEventListener('click', click);
}
});
function click(e) {
// console.log(e.target.className); // gives correct value
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
console.log('clicked class');
console.info(param1); // gives nothing
document.body.setAttribute('class', e.target.className);
});
window.close();
}
How to get e.target.className inside function(e) ?
again If I use jquery. it changed the that popup background color only, see the code
$(function(){
console.log('jQuery added');
$(document).on ('click', 'li', function(){
var cl = this.className;
$('body').removeClass().addClass(cl);
});
});
Please tell me
What is the proper way to accomplish this in both javascript and jQuery
How to get e.target.className inside function(e) ?
Let's look at the following sample code:
var a = 1;
function f(a) {
alert(a);
}
f(2);
This is a simplified version of your problem. There is a variable a in the global scope, but by naming your function parameter a you're essentially making a local variable of the same name.
In your code:
function click(e) {
// e is now from click(e)
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
// e is now from function(e)
});
}
The solution is simple: you're not using the parameter of the callback of executeScript, so just use function() { /* ... */ } as a callback.
If I use jQuery, it changes the popup background color only
Your code operates in the context of your popup; $('body') refers to popup's body. Same with document.body - the callback of executeScript executes in the popup.
To change the active tab, this needs to be done from the content script in that tab.
What is the proper way to accomplish this
While you could just inject code, it's better to make a content script that waits for a command.
// content.js
if(!injected) { // Make sure it's only executed once
injected = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "bodyClass") {
document.body.setAttribute('class', message.class);
}
});
}
Then from the popup, you inject this script then message it:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
// requires only activeTab permission
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
// This code executes in the popup after the content script code executes
// so it is ready for the message
chrome.tabs.sendMessage(tabs[0].id, {action: "bodyClass", class: "example"});
});
});
If you need jQuery, you need to inject it first:
chrome.tabs.executeScript(tabs[0].id, {file: "jquery.js"}, function() {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
/* content script ready */
}
}
Alternatively, you can define the script in the manifest and not inject it every time, but this potentially drains memory as it is injected in tabs where it is not needed.
There is bug in chromium and in chrome I need to use JSON.stringify(e.target.className) the before sending via code
code : "var scriptOptions = { selectedClass: " + JSON.stringify(cl) + " }"
from chorme.sendMessage documentation
Sending a request from the extension to a content script looks very
similar, except that you need to specify which tab to send it to.
function click(e) {
var cl = e.target.className; // both gives the same result that is OK.
chrome.tabs.query({ active: true, highlighted: true, currentWindow: true }, function(htab) {
// console.log(JSON.stringify(htab, ['active', 'id', 'index', 'windowId', 'title', 'url'], 4));
chrome.tabs.executeScript(htab[0].id, {
code : "var scriptOptions = { selectedClass:" + JSON.stringify(cl) + " }" }, function() {
chrome.tabs.executeScript(htab[0].id, { file: "js/script.js" }, function(){
console.log('Inside script file');
chrome.tabs.sendMessage(htab[0].id, { action: "bodyColor" }, function(resp) {
console.log('response aaya');
});
});
});
});
}
My javascript skills are limited and I'm having a problem with the structure of a series of functions which I think need callbacks. I've been reading a number of posts and tutorials but it's not sticking...yet..
On my page I have a pop up modal which contains an image. If the user clicks the edit button it's to be edited in aviary. Once that's completed the image properties get saved into a database and then the images within the modal box - and the underlying form - should get updated with the edited image.
My series of events starts with the modal opening:
$('#editImageLink2').click(function(event) {
aviaryOnClick('image2', $(this).data('mode'), function(image) {
#do final bits here
});
});
Modal pops up then if the user clicks the edit button this next function starts the editor:
function aviaryOnClick(source, mode) {
editedImage = doAviary(source);
if (editedImage) {
return true;
} else {
return false;
}
}
So - aviary pops up as expected. Then when the user saves the edited image I'm starting to have trouble:
The doAviary function looks like this:
function doAviary(source) {
console.log("hit doAviary", source);
var featherEditor = new Aviary.Feather({
apiKey: 'XXXXXXXX',
apiVersion: 3,
theme: 'dark',
tools: 'all',
displayImageSize: true,
maxSize: 1200,
onSave: function(imageID, newURL) {
//replace image in modal preview
var img = document.getElementById(imageID);
img.src = newURL;
if (newURL != undefined) {
storeImage(newURL, updateFormImage(imageData));
featherEditor.close();
return true;
}
},
onError: function(errorObj) {
alert(errorObj.message);
return false;
}
});
return featherEditor.launch({
image: source,
url: $('#' + source).attr('src')
});
}
So I'm trying to run storeImage in the onSave event, which should then run a callback to the update images with the image data.
My storeImage function:
function storeImage(newURL, imageData) {
var options = new Object();
options.aviaryURL = newURL;
options.mode = mode;
options.dbID = ($('#dbID').val()) ? $('#dbID').val() : null;
//console.log("store image options object:", options);
jQuery.ajax({
url: '/filemanager/aviary',
type: 'POST',
dataType: 'json',
data: options,
complete: function(xhr, textStatus) {
//called when complete
},
success: function(data, textStatus, xhr) {
//called when successful
console.log("finished store image", data);
$.cookie('asset_filename', data.image.filename);
$.cookie('asset_id', data.image.id);
imageData(data);
},
error: function(xhr, textStatus, errorThrown) {
//called when there is an error
imageData(false);
}
});
so IF the image is saved the data should be passed back to the callback. If it fails it's false
Then in the update image function
function updateFormImage(data) {
if (data.result == 'success') {
image = data.image;
#simple updates of elements in page
}
}
My current problem is that on save I'm getting an error imageData is not defined - I'm not sure why this is - if it's waiting for ajax to complete before passing back the data to the callback it should exist.
Why does this error happen?
What better ways are there to refactor this code and use callbacks correctly.
I originally had a callback on the first function but got an error callback function not defined
Confused.
Thanks
imageData is not defined into doAviary.
Also, updateFormImage should return something (imageData).
I have a modal dialog on my page using jQuery that a user enters a password into.
It's a standard jQuery dialog and it works fine. I have linkbuttons in a datagrid that open the dialog using this code:
$('.needsVal').click(function () {
$("#login1").dialog('open');
id = $(this).attr('id');
return false;
});
The problem is later on in the page I make an Ajax call, and based on the value returned, I want to selectively fire a postback for the page. The problem is the postback never fires. My postback code is as follows:
if (returnValue == "true") {
WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(id, "", false, "", "", false, true));
return true;
}
else {
alert("Authentication failure!\nPlease check your password and try again.");
return false;
For some reason I can't get the postback to work with the modal dialog, and it's driving me nuts. I had this working with a regular Javascript prompt, but I had to change it because there's no way to mask the password in a prompt.
Any thoughts on how to get the postback to work?
id is a global variable that has the unique ID of the clicked button. I've confirmed that's being passed properly.
I managed to get this to work.
What I found was there are two ASP.NET controls on the page - two gridviews, one with regular linkbuttons and another with a linkColumn.
Because of the way the linkbuttons work in the two types of controls (linkbutton vs. commandbutton) I had to vary how I open the form, and how I interact with the prompt. I had to create two different events. (Maybe there's a way to do it with one, but I couldn't figure it out)
What I finally wound up with jQuery wise:
//Click event for gridview 1 (standard linkbutton)
$('.needsVal').click(function () {
$("#login1").dialog('open');
id = $(this).attr('id');
return false;
});
//Click event for second gridview (command button)
$('.needsValSideMenu').click(function () {
var h = $(this).html();
script = h.substring(h.indexOf("\"") + 1, h.indexOf("\">"));
$("#login2").dialog('open');
return false;
});
//AJAX call for standard linkbutton grid
function checkPassword() {
var returnValue;
$.ajax({
async: false,
type: "POST",
url: "myservice.asmx/Authenticate",
data: { "password": pw.value },
dataType: "xml",
error: function () { alert("Unexpected Error!"); },
success: function (msg) {
returnValue = $(msg).find('boolean').text()
}
});
if (returnValue == "true") {
//alert(id.replace("_", "$"));
id = id.split("_").join("$");
WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(id.replace("_","$"), "", true, "", "", false, true));
}
else {
alert("Authentication failure!\nPlease check your password and try again.");
}
}
//Call for second grid (command button) - need to get and execute the script associated with this button
function checkPasswordSideMenu() {
var returnValue;
$.ajax({
async: false,
type: "POST",
url: "myservice.asmx/Authenticate",
data: { "password": pw2.value },
dataType: "xml",
error: function () { alert("Unexpected Error!"); },
success: function (msg) {
returnValue = $(msg).find('boolean').text()
}
});
if (returnValue == "true") {
eval(script);
}
else {
alert("Authentication failure!\nPlease check your password and try again.");
}
}
I couldn't think of a way to do this in one method since I need to call a different checkPassword routine depending on which type of button was clicked.
I am using Magnific Popup version 0.8.9.
I am loading content into it via Ajax, and I use a callback for ajaxContentAdded. This callback sets up an event handler for submitting a form that was loaded into the popup, like so:
$('.add-item-btn').magnificPopup({
type: 'ajax',
closeOnContentClick: false,
callbacks: {
ajaxContentAdded: HandleItemFormSubmit
}
});
This works fine, the form submit is handled correctly. The event handler function posts it to the server, which (in case of errors) returns the entire form including error messages.
For this purpose I let it replace the popup's content with the returned form, and setup the submit handler again.
function HandleItemFormSubmit()
{
var popup = this;
// Submit form using ajax
$('form.item-form').submit(function()
{
var data = $(this).serialize();
var url = $(this).attr('action');
$.post(url, data, function(resp)
{
if (resp == 'OK')
{
// All good, close up
popup.close();
}
else
{
// Show HTML from response (with errors)
popup.closeOnContentClick = false;
popup.content.replaceWith(resp);
popup.updateItemHTML();
HandleItemFormSubmit();
}
});
return false;
});
}
However, despite setting closeOnContentClick to false at two different points, the popup immediately closes when content is clicked after the content was replaced (it does work the first time).
The content in the popup has a single root element by the way.
I hope the author or someone else can help out here, I have no idea what is wrong here.
Thank you very much!
I've found another solution:
$('html').on('submit', '#UR_FORM', function(e) {
e.preventDefault();
$.ajax({
data: $(this).serialize(),
type: $(this).attr('method'),
url: $(this).attr('action'),
success: function(response) {
var magnificPopup = $.magnificPopup.instance;
magnificPopup.items[0].type = "inline";
magnificPopup.items[0].src = response;
magnificPopup.updateItemHTML();
}
});
});
You need to call the HandleItemFormSubmit for the popup object:
HandleItemFormSubmit.call(popup);
Otherwise when you call it the way you do, HandleItemFormSubmit();, the this will be set to window and this will not work as expected.
Update
Use this in the else clause:
if (resp == 'OK')
{
popup.close();
}
else
{
// Show HTML from response (with errors)
popup.closeOnContentClick = false;
popup.content.replaceWith(resp);
popup.updateItemHTML();
HandleItemFormSubmit.call(popup);
}