jQuery selector "memory" - javascript

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.

Related

How to run a Javascript/Jquery Function using both $(document).ready(function() and Another Event Function?

I'm not a javascript/jquery coder, and not sure if what I'm trying to do is possible.
I have a html/php/ajax form that is updated an sql database as the user fills it out. As they fill the form, there is a progress bar ran by javascript/jquery that updates as the user types in the input. The start of the function looks like this:
$("#update input").keyup(function() {
This works great. My problem is when the page is reloaded. My code is pulling sql data from the database to fill the value of every input on the page that has a value so that a user can come back and completely the form later. When the user reloads the page, the only way for the script to activate is if the user types in an input field.
I thought I would fix the issue by changing the my initial javascript/jquery function with $(document).ready(function() . This caused the script to only run when the page was loaded and not when the form was being filled out. I need both the script to run on page ready, and when a user is typing in the input filled. Is there a way I can run both $(document).ready(function() AND $("#update input").keyup(function() { simultaneously? Or is there a better why to accomplish this? Thanks!
Let me know if I need to post more code.
Here's a generic approach attaching declared functions to events.
function handler (e) {}
element.addEventListener('click', handler);
You're free to call handler everywhere, also inside $(document).ready, or if there's no other code in your DOMReady handler, you can just pass a reference as an argument:
$(document).ready(handler);
In your specific case you most likely want something like this:
$(document).ready(function () {
function handler (e) {...}
handler();
$("#update input").keyup(handler);
});
If the handler function uses the event object (e in the example), in modern browsers it's also available as a global event object, or in jQuery, e.originalEvent. The object doesn't exist if there's no event fired, though, in that case you've to pass a fake event object, containing the provided properties, to the handler, if it is needed.

Javascript click event listener fires only once

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.

Changing onclick for a greasemonkey function before it is defined

I will describe my problem, hopefully someone can provide a solution.
I am adding a GreaseMonkey script to an existing page, the page loads with ajax an element with an onclick:
onclick="getProperty('http:/...')"
I searched for a way to override their getProperty, which was hard on itself because I can't know what object will have this onclick before it arrives via ajax.
I can't bind my own click and unbind the previous one because there's no trigger for such a function when the content is dynamic. I tried adding my click and preventing the previous from being called by that doesn't work.
The selector I'm using looks like this:
a[onclick^='getProperty']
What I discovered was a method someone wrote for this exact problem, called waitForKeyElements. I hope someone here is already familiar with it.
What it does is check if an element matching a selector was added by ajax to the page, and if so runs a function.
This method let me workaround binding my function to override theirs:
waitForKeyElements("a[onclick^='getProperty']", updateToNewGetProperty);
function updateToNewGetProperty(ajaxLinks){
ajaxLinks.each(function(){
var oldOnclick = $(this).attr("onclick");
var UrlStartPos = oldOnclick.indexOf('\'') + 1;
var UrlEndPos = oldOnclick.indexOf('\'',UrlStartPos);
var Url = oldOnclick.substring(UrlStartPos,UrlEndPos);
$(this).attr("onclick", ""); // unbind
$(this).click(function() { // bind mine
myGetProperty(Url);
return false;
});
});
}
This works, it unbinds the previous javascript onclick and sets the jquery click.
However then I discovered that another segment of their code grabs the URL value that is inside the onclick, so I can't use the jquery click bind as that leaves the onclick empty.
I had to revert back to:
$(this).attr("onclick",$(this).attr("onclick").replace('getProperty', 'myGetProperty'));
This returns function not defined when clicking the link, I believe because the original page loads the ajax content before the greasemonkey is fully loaded. Triggering the waitForKeyElements before my function is registered.
Any help / advice would be greatly appreciated

Can a page event be persisted across a page load?

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.

Display native validation message in JavaScript

I'm currently developing a form which will be powered by HTML5 features and jQuery. One of the things I've been tasked with is ensuring that the native "Please fill in this field" message is still available to browsers which natively support validation.
My setup looks like so:
|----------|
| FORM |
|----------|
===BUTTON===
The form has several parts to it, and so the button is global across them all. The form then slides to the next section if complete.
Here is what I have now, this correctly fires the button event to the form and triggers a submit event.
$(".next").click(function() {
var $incoming = $(".nextPart #" + (currentSlide));
var incomingID = $incoming.data("cardid");
var partCode = "form-" + incomingID;
$("form[name='" + partCode + "']").trigger("submit");
});
$("form").bind('submit', function(event) {
var goForth = true;
if(!event.currentTarget.checkValidity()) goForth = false;
if(!goForth) return false;
/* Do some stuff with progress bar and more things */
return true;
});
However, even though the form submit fails, there is no validation message. Is there a way to pragmatically fire this on an element, or have I done something stupid?
For clarification, this is a screenshot of the validation message I am on about.
instead of event.currentTarget.checkValidity -- do this...
function checkValidity(elem) {
// check validity here
// retun true/false
}
and then in your submit handler do this...
if(checkValidity(event.currentTarget)) { ...
Also, it is generally NOT a good idea to trigger native browser events -- trigger is good for custom events -- if you need to submit the form you can call the submit() method of the form object like this..
$("form[name='" + partCode + "']").get(0).submit();
As I described in my first post, I was using a click event on a button which was in a global scope. This was then sending a submit action to the form which, although was sending the form to be submitted, it wasn't firing the bubble event (whatever the hell that is) on the elements.
To fix this, I wrapped all of my slides in one form instead of multiples. I kept my submit button outside of the slides, so it still acted as a global navigation item.
Removing the click event from this button and changing the type of it to submit now gets the bubble displaying.
Not the best fix, since the bubble should be able to be trigged without having to submit the form, however, I guess with HTML5 validation, you can define the parameters for what is accepted and what isn't.

Categories

Resources