Callback for a popup window in JavaScript - javascript

I hope I did my homework well, searching the Internets for the last couple of hours and trying everything before posting here, but I'm really close to call it impossible, so this is my last resort.
I want a simple thing (but seems like hard in JavaScript):
Click button -> Open Window (using window.open)
Perform an action in the popup window and return the value to parent (opener)
But I want to achieve it in a systematic way, having a callback defined for this popup; something like:
var wnd = window.open(...)
wnd.callback = function(value) {
console.log(value);
};
I've tried defining the callback property in popup window JS code:
var callback = null;
Unfortunately, that does not work, as...
$('#action').click(function() {
console.log(callback);
});
... returns just that "null" I set initially.
I've also tried setting the callback in a parent window after window load (both thru window.onload=... and $(window).ready()), none worked.
I've also tried defining some method in child window source code to register callback internally:
function registerCallback(_callback)
{
callback = _callback; // also window.callback = _callback;
}
But with the same result.
And I don't have any more ideas. Sure, it would be simple setting the value using window.opener, but I'll loose much of a flexibility I need for this child window (actually an asset selector for DAM system).
If you have some ideas, please share them.
Thank you a million!

HTML5's postMessage comes to mind. It's designed to do exactly what you're trying to accomplish: post messages from one window and process it in another.
https://developer.mozilla.org/en/DOM/window.postMessage
The caveat is that it's a relatively new standard, so older browsers may not support this functionality.
http://caniuse.com/#feat=x-doc-messaging
It's pretty simple to use:
To send a message from the source window:
window.postMessage("message", "*");
//'*' is the target origin, and should be specified for security
To listen for messages in a target window:
window.addEventListener
("message", function(e) {
console.log(e.data); //e.data is the string message that was sent.
}, true);

After few more hours of experiments, I think, I've found a viable solution for my problem.
The point is to reference jQuery from parent window and trigger a jQuery event on this window (I'm a Mac user but I suppose, jQuery has events working cross-platform, so IE compatibility is not an issue here).
This is my code for click handler on anchor...
$(this).find('a[x-special="select-asset"]').click(function() {
var evt = jQuery.Event('assetSelect', {
url: 'this is url',
closePopup: true,
});
var _parent = window.opener;
_parent.jQuery(_parent.document).trigger(evt);
});
... and this is the code of event handler:
$(document).bind('assetSelect', function (evt) {
console.log(evt);
});
This solution is fine, if you don't need to distinguish between multiple instances of the asset selection windows (only one window will dispatch "assetSelect" event). I have not found a way to pass a kind of tag parameter to window and then pass it back in event.
Because of this, I've chosen to go along with (at the end, better and visually more pleasant) solution, Fancybox. Unfortunately, there is no way - by default - to distinguish between instances either. Therefore, I've extended Fancybox as I've described in my blog post. I'm not including the full text of blog post here, because is not the topic of this question.
URL of the blog post: http://82517.tumblr.com/post/23798369533/using-fancybox-with-iframe-as-modal-dialog-on-a-web

Related

How can I console.log to parent window?

I've got a function that essentially refreshes a table, which works ok, but some of the JS functions don't run. To debug I'm trying to pass data between a popup and it's parent window. Currently I have this function:
$.fn.runFncs = function(isParent)
{
if (isParent == 1) {
window.opener.$.fn.compareDates();
window.opener.$.fn.addStatusIcon();
window.opener.$.fn.iconTooltips(1);
window.opener.$.fn.iconTooltips(2);
window.opener.console.log('test');
} else {
$.fn.compareDates();
$.fn.addStatusIcon();
$.fn.iconTooltips(1);
$.fn.iconTooltips(2);
}
};
and this gets run on an ajax success.
When I hit the button for the ajax, I get my success message etc. but no console.log in my parent window. I've been able to access the parent window before using window.opener and it seems to run ok, just not this time for some reason.
I tried research but either my query was too specific or it was simple "what is console.log" questions so a little stuck here.
Is there an alternative way I can console.log to the parent window? Maybe a document function I'm unaware of?
Thanks! :)
function log(message){
console.log(message);
}
Put that function in your parent window and call it like so. You basically need to provide a wrapper function that you can access
window.opener.log("Hi");
You cannot access directly from one window/tab to anothers's console object, but you can send messages from one window to another. The parent window would get that messages and then it would write it on the console. See this Q&A for more details:
I tried out in recent (2018 aug) Firefox, Chrome and Opera and IE11 too and window.opener.console.log works perfectly in all of them. So I think the problem were somewhere else in your code.
You could even do child.console = console in the parent window and keep console.log, but most of the browsers clear that variable between page loads, and there is no way to set it again right after the document was created, but before the scripts are executed. You can add window.console = window.opener.console to the head of the child page if you can edit that. That should do the trick too.

How to extract target hostname during onbeforeunload event

I'd like to warn the user when he/she tries to navigate away from my webapp when his/her project is unsaved. Currently I don't care about edit boxes which are currently under edit. I know the project's dirty state from the session.
I quickly realized that if I set a function for the window.onbeforeunload, it'll disturb the user even if he/sh navigates to another page within my webapp. In such case no disturbance needed, the session will be still alive.
I tried a solution derived from Fire onbeforeunload only when window is closed (includes working example)
window.onbeforeunload = function (e) {
var targetHost = new URL(e.target.URL).hostname;
if (targetHost != window.location.host)
return "Project is unsaved!!!";
else
return null;
};
The new URL(e.target.URL).hostname results in (at least on IE11):
JavaScript runtime error: Object doesn't support this action
And mysteriously I can't find anything about no new URL(e.target.URL).hostname by any search.
But at the same time I hope this is not impossible, I cannot believe others didn't experience this.
There are two problems in your script:
window.onbeforeunload = function (e) {
var targetHost = new URL(e.target.URL).hostname;
if (targetHost != window.location.host) {
return "Project is unsaved!!!";
else
return null;
};
The first is that you did not close your if. The second is that e.target.URL is apparently not supported by IE. You need to have an A and a B plan.
A plan: Applies to the case when e.target.URL exists. In this case, you handle the case as you initially implemented, but with the syntax fix mentioned above.
B plan: Applies to the case when e.target.URL does not exist. For this case, you should assume that the user leaves the site. You can improve the approach by adding knowledge when the user uses your control to navigate, for instance, you know that a given anchor will stay on the site. In this case, you can define a click event and set a boolean value to true (and the default value of that boolean should be false...), so that later your onbeforeunload event will know that your user stayed on the site even if he or she uses IE.
So you need a logic like this:
var targetHost = !!e.target.URL ? (new URL(e.target.URL).hostname) : ((myFlag) ? (window.location.hostname) : ("elsewhere"));

Is it possible to bind to events on a child window from a parent?

Is it possible to bind functions to events on child windows?
document.getElementById('foo').onclick = function() {
var newWindow= window.open('other.html', "_blank");
newWindow.document.addEventListener("onreadystatechange", function(){
console.log('foo'); // This is never run. Can I construct the new window so that it is run "onreadystatechange"?
});
return false;
};
Note that I would like to bind an event to onreadystatechange. I wish to avoid a race condition, can I create a window, bind the events and then load the URL to avoid the race condition?
.addEventListener("onreadystatechange", ...)
Event properties start with "on". The event names on the other hand do not. I.e. it should be
.addEventListener("readystatechange", ...)
I have not tried avoiding the race condition because I know of no way to do so.
Ok, I'm not entirely sure how events and auxiliary browsing context initialization work with window.open(), the spec is quite complex there.
I'd just try setting DOM event breakpoints (chrome debugger has those) and see which events are fired in which order and then check if that works in other browsers.
That said, I think the simplest option here might to read the document.readyState property. If it's "complete" then the site is already fully loaded and no further state change event will be fired and you can execute your script directly instead of waiting for the event.
If you do this should be good to go.
var newwindow = window.open('other.html', "_blank");
var $ = newwindow.$; // add if needed
$(newwindow).bind('someEvent', function() { FunctionThatDoesSomethingInTheNewWindow });
return false;

Bind class method to AJAX success callback within event handler bound outside of class

My issue:
I have created a JavaScript class that is used by our dev team across our site. It is essentially functionality for a grid/table like structure that allows the user to select items and perform actions on these items with provided action buttons.
Action button workflow:
User clicks action button
Popup appears: "Are you sure you want to perform this action on these items?"
User clicks "Yes": AJAX call is made and popup closes upon AJAX success.
User clicks "No": Popup closes.
Right now, these action buttons are individually bound in jQuery by our Devs on each page that needs it. Any given page could have a handful of event bindings.
After successful completion of any of these actions, I would like to run Grid.afterActionComplete() from any given instantiation. I would like to run Grid.afterActionComplete() within the actions AJAX success callback. I know I could expose (return) afterActionComplete in my class and have the Devs run the function themselves, but this is not ideal.
My requirements:
Would like to keep the amount of additional code for Devs to a minimum
Many AJAX request can be made from any given page (some from non-action buttons), so using a global ajaxSuccess event wouldn't necessarily work. Plus, I would hate to use an event with that global of a scope.
My question is two-fold:
How could I dynamically bind Grid.afterActionComplete() to any given action's AJAX success callback? (if possible)
How would I best incorporate the action bindings into the Grid class upon instantiation to further encapsulate my code?
My sample code:
/* [START] Pre-existing code */
var Grid = function(gridID){
var gridID = $(gridID),
afterActionComplete = function(){
// Ideally, I'd like to bind the function here
},
refresh = function(){
// Refresh grid
},
return {
refresh : refresh
}
}
var popup = function(){
$('.popup').show();
// Pops up a window with an Action button and Cancel button
// Just a placeholder to help explain concept
}
/* [END] Pre-existing code */
/*
[START] Dev defined code
Devs will be creating these event bindings across the
site.
*/
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
popup();
$('.popup #actionButton').click(function(){
$.post( "ajax/test.html", function( data ) {
myGrid.refresh();
$('.popup').hide();
// I'd like to inject Grid.afterActionComplete() here
// Maybe create custom event and trigger() it here?
// Ideally, I would love to not require the Devs insert additional code hre, but I'm not sure that's possible
});
});
});
/* [END] Dev defined code */
I've been pondering these questions for a week or so now, and would love any suggestions to help me solve this issue. Thanks!
Assuming all of the "developer code" is very similar, I would think ideally you would want to have the developers pass in appropriate parameters instead of create a bunch of very similar code.
For instance, if you made the popup method part of Grid and has the url and callback passed to the function you could do something like this:
popup = function(url, callback){
var that = this;
$('.popup').show();
$('.popup #actionButton').click(function(){
$.post( url, function( data ) {
// call the passed in callback
callback(data);
// do your post-callback stuff
that.refresh(); // assuming this happens in every callback
$('.popup').hide(); // assuming this happens in every callback
that.afterActionComplete();
});
});
}
Then your example developer code would become this:
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
myGrid.popup("ajax/test.html", function(data){
// do instance-specific stuff here
});
});
Correct me if I am wrong. You want Grid.afterActionComplete() called only on specific AJAX requests, correct? This is why you cannot use .ajaxSuccess()? If that is the case, the best thing you can do is to trigger a custom event.
If you feel that is too much work for the developers, you can abstract the $.post functionality inside a custom function of your Grid class. After you execute the callback, you can then make the call to Grid.afterActionComplete(). If it is mandatory that Grid.afterActionComplete() be called after those requests, it would make more sense to take this route since it seems to be part of the contract. This way you can protect the developers from themselves (i.e., if they forgot to call the function or trigger the custom event) by making it so that they can only make the post using the Grid API.

Keeping bookmarklet from generating multiple event listeners

I currently am working on a bookmarklet that opens an iframe, and sets up a communication of postMessage back and forth. That all works fine.
However, seemingly because the bookmarklet is being loaded as an anonymous function, the listeners are multiplying if I run the bookmarklet more than once on a page.
Is there some sort of way to keep track of these addEventListeners so that they don't double-up?
Do I need to define the rp_receive_message outside of the anonymous function?
Here's an example of the code:
var rp_receive_message = function (e) {
var response = e.data;
console.log("got message with "+ response);
};
if (window.addEventListener) {
window.addEventListener('message', rp_receive_message, false);
} else {
window.attachEvent('onmessage', rp_receive_message);
}
var s1 = window.document.createElement('iframe');
s1.setAttribute('src', 'http://mydomain.com/iframe.html');
s1.setAttribute('id', 'testiframe');
s1.setAttribute('width', '700');
s1.setAttribute('height', '550');
s1.setAttribute('frameBorder', '0');
s1.setAttribute('onload', 'this.contentWindow.postMessage(window.location.href, "http://mydomain.com/iframe.html");');
document.getElementById('container').appendChild(s1);
Probably this will solve the problem:
window.onmessage = rp_receive_message;
As you suggest, the code below might be enough by itself. I don't know if addEventListener and attachEvent will add the same function multiple times, but I wouldn't at all be surprised if they will. I suggest just testing it.
window.rp_receive_message = function(){...}
If you dislike either solution, you've got to set up a global variable, which hardly seems any different or greatly superior to above. The global can be a simple boolean to check if the event has been attached, or it can be a list of attached events that you update yourself. AFAIK, and I'm pretty sure, there is no native JS solution to get a list of event listeners have been attached to a particular event. Libraries such as jQuery maintain lists and let you read them; and possibly have other techniques that are elegant solutions to your general problem.

Categories

Resources