I have a page which, when a user clicks an icon, launches a second window to update some database entries. When the child window is opened, I'd like to attach an event to fire a callback function and update a field on the parent screen. At the moment, the javascript is similar to:
var child = window.open(URL);
child.name = reference; // used to store parameters about which field to update
$(child).on('unload', function() {
var w = this.name.split(':');
fieldUpdate(w[1],w[0]);
});
// also tried ...
// child.onbeforeunload = function...
// child.onunload = function...
// child.onclose = function...
The problem is that the child page, which is used elsewhere and cannot be changed, used an <input type="submit"...> to initiate the postback and update the database. The controller method then reloads the view, which, obviously, clears all the events. So the callback only gets called on the first update.
From my testing, the onbeforeunload gets called before the controller postback, the onunload gets called after, and the onclose doesn't get called at all when using the submit. In all cases, the child.name property persists into the reloaded page, but all events are cleared.
Is there any way that the parent window could set up an event (or reinstate it) so that the callback fired whenever the submit action occurred? I've tried playing around with setting a timeout callback, but there seem to be problems with getting/retaining the child window parameter.
Update:
For information, the method I wish to call is similar to (actual code has more error checking etc.):
fieldUpdate: function(fieldName, id) {
$.post(
baseURL + 'getField',
$.param({id: id, name: name}, true),
function(json) {
$('#' + json.Values.name + json.Values.id).val(json.Values.count);
}
);
}
This is in answer to Julian's comment about using AJAX. As you can see, I already do, but it's how to trigger the call from the clicking of a 'submit' button in the child page that I'm having trouble with.
If I could modify the child page, this would be fairly easy, but, unfortunately, that's not an option as it would break too much other code.
Related
I have a Chrome extension that intercepts and checks tweets before they get posted. To do this, I've add an event listener to the Tweet button. Sine the content is dynamic, I use the solution proposed in this thread:
initialize : function() {
let that = this;
let jsInitChecktimer = setInterval(checkForJsFinished, 111);
function checkForJsFinished () {
if (document.querySelector("div[data-testid='tweetButtonInline']")) {
clearInterval (jsInitChecktimer);
console.log("Button found");
that.addSubmitNewTweetClickHandler();
}
}
},
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.addEventListener('click', function(e) {
console.log("CLICK");
// Stop default event from happening
e.preventDefault();
e.stopImmediatePropagation();
// Do stuff
});
},
If the tweet passed the checks alright, it gets submitted by programmatically triggering the event using .trigger('click').
This works fine, but only once. After a tweet has been submitted and posted, the event listener on the Tweet button is gone, and I cannot intercept the next tweet to check it. I've tried calling initialize() after submitted again -- maybe the button gets removed and newly added to the DOM (it actually disappears fire a moment when submitting a tweet) -- but the querySelector finds the button immediately. But even after calling initialize() again, no click even on the Tweet button fires.
What could be the issue here? My problem is that I don't even know where to look for and how to debug this.
After many more hours, I've finally figured it out. The problem was essentially the highly dynamic content of the new Twitter website. After submitting a tweet, the Tweet button gets indeed removed and added again. In needed to do a serious of changes:
Use a MutationObserver to keep track of any changes. Every time there's a change, call the initialize() function. To avoid too many calls, I do this in case of certain changes (unnecessary detail here)
Change the addSubmitNewTweetClickHandler() method so that the event listener first gets removed in order to avoid duplicate listeners (please note that I use objects hence the use of this compared to my original question)
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.removeEventListener('click', this.handleSubmitNewTweetClick );
this.handleSubmitNewTweetClick = this.handleSubmitNewTweetClick.bind(this)
buttonSubmitTweet.addEventListener('click', this.handleSubmitNewTweetClick );
},
This change required to create the reference function handleSubmitNewTweetClick
Overall, it's still not a perfect solution since I call initialize() many unnecessary time. However, I failed to reliably identify when the Tweet button was added to the document. When I used the MutationObserver none of the added nodes had the attribute data-testid which I need to identify the correct button. I have node idea why this attribute was not there. Maybe the attribute is added some times after added to button, but even with an additional MutationObserver looking for attribute changes I could detect this.
Anyway, it works now and it's only for a prototype.
I am trying to figure out why jQuery is not binding to all future 'click' events on my page. It only works the first time I do this. The AJAX request fires, and displays adds the correct data to the DOM, however any subsequent attempts do not fire. When I select a button 'View All Dealerships', the AJAX requests returns JSON array of the dealerships and displays them on the page.
$(function(){
$("body").on("click", "#all-dealerships", function(e){
let path = $(this).attr("href");
$.ajax({
url: path,
dataType: 'json',
success: function(response){
var source = $("#dealerships-template").html()
var template = Handlebars.compile(source)
var result = template(response)
var a = () => document.getElementsByTagName("body")[0].innerHTML = ""
a()
document.getElementsByTagName("body")[0].innerHTML += result
}
})
e.preventDefault()
})
})
The variable names and structure is just for testing.
Body is a static element that does not change. I tried wrapping the button in a div and using that as the parent object that binds the event, however I get the same result. The page loads just not with AJAX. The event only gets bound when I do a full page refresh. Navigating to other pages on the site does not fix the problem.
The button that triggers the AJAX request is not present when the new elements are added to the DOM. It gets removed. The action of navigating back to that button does not re-trigger the click event.
What you are doing is
You are binding an event and in ajax call you are removing all elements inside the body, however it may be possible that dummy 'div' element can be present in DOM.
So due to this and you binded an event on id (which binds event only on one element) your click is not triggered
So what you have to do is,
Insted of
document.getElementsByTagName("body")[0].innerHTML = ""
AND
document.getElementsByTagName("body")[0].innerHTML += result
Do this
$('body').empty();
AND
$("body").html(result);
this will make sure that all elements inside the the body will be removed.
What ended up fixing my problem was changing the event bind to $(document).on(...) instead of $('body').on(...)
When the new HTML content was appended to the DOM the button that triggered the AJAX request was no longer available. Navigating back to the page where the button existed did not trigger the event bound to the body element. Targeting the document ensured that the event would re-fire each time.
Does AJAX loaded content get a "document.ready"?
I'm working on an app that already renders a calender using fullcalendar, whenever the page refreshes the time slots are always rendered with the correct colors using the event render callback. It also correctly changes the color of the time slot when the event is clicked upon, using the event click callback.
This is all nice, however I'm trying to programmatically manipulate the renderings of the time slots based on some other stuff the user does after the calendar has fully loaded. In code this is what i'm trying to do
var eventClick = function(calEvent, jsEvent, view) {
// first we change the color of the event to indicate that it was clicked
calEvent.backgroundColor = orangeColor;
calEvent.textColor= darkGreyColor;
calEvent.active=true;
$(this).css('background', orangeColor).css('color', darkGreyColor);
// i cache both the calEvent and the element to manipulate them later on
cachedActiveEvents.push(calEvent);
// here i'm storing this div: <div class="fc-event fc-event-vert ..>
// <div class="fc-event-inner">
// <div class="fc-event-time">12:30 - 1:20</div>
// <div class="fc-event-title">event title</div>
// ..
cachedActiveEventViews.push($($(jsEvent.target).parent().parent()));
..
once clicked, the program displays a modal form that the user fills. Upon completion and dismissal of the dialog, the clicked event needs to change its color to reflect a change of status, that's where i call this method:
function hideLessonPlan() {
..
$.map(cachedActiveEvents, function(calEvent, eventIndex) {
var element = cachedActiveEventViews[eventIndex];
calEvent.backgroundColor = blackColor;
calEvent.textColor = "white"
element.css('background', blackColor).css('color','white');
});
}
This simply don't work. Using Chrome dev tool breakpoints i ensured that the function hideLessonPlan actually talks to the right variables.
Upon further investigation i realized that in both event render and event click callbacks.. the function updateEvent(event) is called after the background properties have been set.. however in my case.. this event is not called after setting the properties. So my question is more of: how can I actually call this upateEvent method from the outside? It seems like a private variable.
update: i made it work but simply by storing the css properties of the selected div and then using jquery to find the same div and highlighting it manually afterwords. not too happy about this hacky way.. hoping someone would come along and show me how to do it through fullcalendar.
it turns out that i simply had to update the property of the calendarEvent ie
calEvent.isActive = true;
calEvent.status = 0; // ie in progress
then call update event:
$('#calendar').fullCalendar('updateEvent',calEvent);
that took care of all the rendering for me
For my purposes, certain click events will populate a div.slide-content element with an HTML template which template may contain a div.video-wrapper element to which I then need to append a video player element whose properties are determined by the state of certain global vars at the time it's loaded.
My dilemma is either a failure to bind an appropriate listener to div.video-wrapper when it arrives, or else to trigger a the corresponding event when the AJAX call completes (or worse I'm doing both). I can make it work with events like click or hover, but I would prefer to use an event the user isn't going to be able to generate themselves.
// listen for a click event on the given 'slide'
$(".ajax-link").click(function () {
toc_index = parseInt($(this).attr("data-slide-number"));
fetch_slide();
});
// load the next slide's content
function fetch_slide() {
$(slide_content).load("slides/slide_" + toc_index + ".php").fadeIn();
$("div.video-wrapper").trigger('ready');
/* note that I've also tried triggering this via $(document).ajaxComplete()
as well; neither does anything unless I use events that are problematic */
}
/* this function needs to be called if #slide-content contains
an instance of div.video-wrapper */
function buildVideoElement(id, src) {
var element = ethovid.template.replace("%%VIDEO_ID%%", id);
element = element.replace("%%VIDEO_SOURCE%%", src);
return $("div.video-wrapper").append(element);
/* my (failing) attempt to bind a listener to .video-wrapper elements
note that though I'm trying to use the 'ready' event, really I've just chosen an
event that the user can't trigger themselves and that matches the trigger in
fetch_slide() above.
*/
$(document).on("ready", "div.video-wrapper", function() { buildVideoElement();});
UPDATE
Also attempted to use an event like click and then unbind it, but this doesn't work, either.
I won't swear this is true, but I'm pretty sure that the document ready event is only ever fired once, so $(document).on("ready", "div.video-wrapper", ... will not be called when you expect it to be.
Fortunately, the load method accepts a function callback argument that will be executed after the new content is loaded into the DOM. So, I think you want something like this:
$(slide_content).load("slides/slide_" + toc_index + ".php", function () {
// This function is executed after "slides/slide_" + toc_index + ".php"
// has been fetched, parsed, and loaded into $(slide_content).
var id = ?; // Get id from somewhere
var src = ?; // Get src from somewhere
buildVideoElement(id, src);
}).fadeIn();
From the code sample you've given, I can't tell where the values for id or src would come from, but it does look like you will need to pass them to buildVideoElement.
By the way, you do not need that call to $("div.video-wrapper").trigger('ready'); It won't do anything as it will almost certainly execute before the load function has had time to even establish its AJAX connection to the server.
I have a form with multiple fields, and each time the user changes a field the form is submitted (via hidden iframe), and the response is placed within an appropriate div on the page via a callback. The first time this works fine. But on each subsequent field change and submission, the response is shown in every div that has been filled with a response (so they all show the same thing, not the desired behavior).
Can anyone tell me why this is happening? It seems that there is some retention of the selectors that have been called before (since last page load)... but I'm not sure. Here's my code:
$(function ()
{
$('#ImageAddForm input').change(function (){
form = $('#ImageAddForm');
var fldDiv = $(this).parent().attr('id'); // eg Image11
var thDiv = fldDiv.replace('Image', 'Thumb'); // eg Thumb11
$(form).iframePostForm({
post : function (){
var msg = 'Uploading file...';
$("#" + thDiv).html(msg);
},
complete : function (response){
$("#" + thDiv).html(response);
$(':input', '#ImageAddForm').not(':hidden').val('');
}
});
form.submit();
});
});
I'm not familiar with that plug-in, but I have a suspicion about what might be causing your problem. You are attaching some functionality to your form with the plug-in inside of your change event. This means that on every change you are attaching again, which is likely to cause some problems. Two solutions suggest themselves:
1) If the plug-in has some kind of call to unbind or destroy itself, call that right before binding the plug-in to the form. This should prevent any weird behavior caused by multiple binding.
2) Better solution: bind the plug-in to the form outside your change event, and scope your variables (fldDiv, tdDiv) such that they will be accessible to both your change event (so that they can be modified based on what changed) and the functions used by the plug-in (for post and complete). This way you will only bind the plug-in once, but can still pass and receive different data based on what field changed.