How to maintain event observation after an element has been updated - javascript

I'm working on a project where I'd place an search box with a default set of results, over the layout using Prototype which is triggered from a click event.
When the output is set, that new page has it's own Javascript which is uses, to dynamically filter the results.
Now the new set of results do not work with the Javascript set previously.
How can I maintain a persistent event with calling new events each time?
Or is that what I am supposed to do.
Here is a bit of code which is loaded in 'loaded_page.php'
<script language="javascript">
var o = new Compass_Modal('add_button', 'history');
var placetabs = new Tabs('tabs', {
className: 'tab',
tabStyle: 'tab'
});
$$('.add_button').each(function(s, index){
$(s).observe('click', function(f) {
loadData();
});
});
function loadData() {
new Ajax.Request('/sponsors/search', {
onComplete: function(r) {
$('overlay').insert({
top:'<div id="search_table">'+r.responseText+'</div>'
});
}
})
}
</script>
Then in the included page which is inserted via javascript:
<div id="search_overlay">
<div id="form_box">
<img src="/images/closebox2.png" class="closebox" />
<form method="post" id="search_form" class="pageopt_left">
<input type="text" name="search_box" id="search_box" value="search" />
</form>
</div>
<div id="table_overlay">
<table class="sortable" id="nf_table" cellpadding="0" border="0" cellspacing="0">
<tr>
<th id="name_th">Name</th><th id="amount_th">Amount</th><th id="tax_letter_th">Tax Letter</th><th id="date_th">Date</th><th id="add_th">Add</th></tr>
<tr>
<td>Abramowitz Foundation (The Kenneth & Nira)</td><td><input type="text" name="amount" value="" id="amount_111" /></td><td><input type="checkbox" name="tax_letter" value="1" id="tax_letter_111" /></td><td><input type="text" name="date" value="" id="date_111" /></td><td><img src="/images/icons/add.png" title="add contact" /></td></tr>
... more rows
</table>
</div>
</div>
<script language="javascript">
var c = new Compass_Search('contacts', 'table_overlay');
c.set_url('/sponsors/sponsor_search');
$$('.add_button').each(function(s, index) {
$(s).observe('click', function(e) {
$(e).stop();
var params = $(s).href.split("/");
var userid = params[5];
var amount = 'amount_'+params[5];
var date = 'date_'+params[5];
var tax = 'tax_letter_'+params[5];
if(!Form.Element.present(amount) || !Form.Element.present(date)) {
alert('your amount or date field is empty ');
} else {
var add_params = {'amount':$F(amount), 'date':$F(date), 'tax':$F(tax), 'id':userid};
if(isNaN (add_params.amount)) {
alert('amount needs to be a number');
return false;
} else {
new Ajax.Request('/sponsors/add', {
method: 'post',
parameters: add_params,
onComplete: function(e) {
var post = e.responseText;
var line = 'amount_'+add_params.id;
$(line).up(1).remove();
}
})
}
}
});
});
this.close = $$('.closebox').each(function(s, index) {
$(s).observe('click', o.unloader.bindAsEventListener(this));
})
</script>
You'll notice in the inserted portion, a new Javascript which also, updated its own content with yet new observers. When the content gets updated, the observers do not work.

Use event delegation.
Besides making it possible to replace "observed" elements, this approach is also faster and more memory efficient.
document.observe('click', function(e){
var el = e.findElement('.add_button');
if (el) {
// stop event, optionally
e.stop();
// do stuff... `el` now references clicked element
loadData();
}
});

As you're calling Element#insert, I suppose String#evalScripts should be automatically called (as having looked in the implementation of those methods)
Maybe you can try to do some console.log()/alert() messages before the Element#observe calls, to see where it is going wrong. It may be because the Javascript is not evaluated at all, or that there is something wrong with your code or something like that.
As a tip: check the documentation on Array#invoke, with that method you can rewrite something like:
$$('.add_button').each(function(s, index){
$(s).observe('click', function(f) {
loadData();
});
});
into this:
$$('.add_button').invoke('observe', 'click', function(event){
loadData();
});
// $$('.add_button').invoke('observe', 'click', loadData);

Related

onclick in javascript not triggering, while Id is correct

this is my javascript code the onclick functions do not trigger when i push the button I have tried with an event listener that listen only for the parent of the button aka the form but nothing in that case it fires once and it does not keep listening for further button clicks:
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
document.addEventListener('DOMContentLoaded', () =>{
const room__message = Handlebars.compile(document.querySelector('#room__message').innerHTML);
document.querySelector('#send__button').onclick = () =>{
console.log('hola el boton fue pulsado')
let message = document.querySelector('#message__input').value
let user = localStorage.getItem('user')
let channel = localStorage.getItem('channel')
console.log(message)
socket.emit('send message', {'message':message, 'user':user, 'room':channel})
document.querySelector('#message__input').value = '';
}
socket.on('connect', () =>{
socket.emit('join', { 'channel':localStorage.getItem('channel'), 'user':user })
load__list();
load_messages(localStorage.getItem('channel'))
});
document.querySelector('#add__room').onclick = () => {
let list = JSON.parse(localStorage.getItem('channel__list'));
let name_ = prompt("Please enter you'r new Channel's name", "");
while (name_ in list || name != null){
name_ = prompt("this name is already in the database", "");
}
if (name_ != null){
list.push(name_)
}
socket.emit('new room', {'name':name_})
};
socket.on('broadcast', data =>{
let message = data.message;
let user = data.user;
let timestamp = data.timestamp;
const msj = room__message({'message':message, 'user':user, 'timestamp':timestamp})
document.querySelector('.message__cont').innerHTML += msj;
});
});
the html looks like this:
<body>
<ul id="channel__list">
<li>
<button id="add__room">+</button>
</li>
</ul>
<div id="chanel__container">
<form id="channel__form" action="" >
<input type="text" id="message__input" autocomplete="off" placeholder="message">
<input type="submit" id="send__button" value="send">
</form>
</div>
</body>
it does run in a flask server I dont know if that may be an issue
First of all, there are quite a few syntax errors in your code.
Inside your HTML code
<from> tag is to be changed to <form>
Inside your JS code
ID room__message is not declared hence this will never return anything.
You have opened a function socket.io('connect') function but never closed.
Coming back to why the on click buttons are not getting triggered *
The possible reason this could be happening is that by the time your document readyState is already completed in this case the event DOMContentLoaded will not be fired at any point in time. This can be avoided by providing/checking document ready state.
Below are two sample codes (I am still not sure why you are using on click function inside a listener while you can use JS shorthand and directly use it)
Proper HTML code
<body>
<ul id="channel__list">
<li>
<button id="add__room">+</button>
</li>
</ul>
<div id="chanel__container">
<form id="channel__form">
<input
type="text"
id="message__input"
autocomplete="off"
placeholder="message"
/>
<input type="submit" id="send__button" value="send" />
</form>
</div>
</body>
Your actual JS code (a bit modification)
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadDomContent());
} else {
console.log("Current document load state: ", document.readyState);
loadDomContent();
}
function loadDomContent() {
const room__message = Handlebars.compile(
document.querySelector("#room__message").innerHTML
);
document.querySelector("#send__button").onclick = () => {
/* do something */
};
socket.on("connect", () => {
socket.emit("join", {
/* do other stuff }); */
});
});
document.querySelector("#add__room").click = () => {
console.log("I am working");
/* do something else */
};
socket.on("broadcast", data => {
/* send some stuff over */
});
}
Also, you can write something like this
<button onclick="addNewRoom()" id="add__room">+</button>
An declaring that as:
function addNewRoom(){
// do something
}
My Solution
the issue was in deed in the html, because it was changing the position of th ebutton by adding more elements to its container im guessing it changed the address to which the event was pointing at, thanks everybody for their input.

google invisible recaptcha keeps running without execute

I'm trying to use google invisible recaptcha on my web form (php and codeigniter 3). but somehow whenever I click on the Submit button, the google recaptcha keeps generating questions as if ignoring all the other codes before the execute command. so none of the console.log and alert ever appear. what is wrong with my code?
my code looks like this:
HTML
<form id="form_signup" method="post" action="/signup">
<input type="text" name="username"/>
<div class="g-recaptcha"
id="form_signup-recaptcha"
data-size="invisible"
data-sitekey="<?php echo $mysitekey; ?>"
data-callback="onSubmitFormSignupUser">
</div>
<button type="button" id="formSignup-btnSubmit">
Submit
</button>
</form>
JS
var widgetId = '';
var onLoadRecaptcha = function() {
widgetId = grecaptcha.render('formSignup-btnSubmit', {
'sitekey' : $('#form_signup-recaptcha').attr('data-sitekey'),
'callback' : $('#form_signup-recaptcha').attr('data-callback'),
});
};
var onSubmitFormSignupUser = function(response) {
console.log('response', response);
if ($('[name="username"]').val()) {
alert('yes');
grecaptcha.execute(widgetId);
doSubmitFormToServer('#form_signup');
}
else {
alert('no');
grecaptcha.reset(widgetId);
}
}
var doSubmitFormToServer = function(selector) {
var myData = $(selector).serializeArray();
console.log('send form data', myData);
}
Well, you had a typo in the id, at least, here id="form_signup-recaptcha" and here: 'sitekey' : $('#formSignup-recaptcha').attr('data-sitekey'),, other than that, it is not clear, was it invoked at all, or not, as you've not provided the part of including the script, which should contain ?onload=onLoadRecaptcha parameter.
The code is below, but it won't work here, because of null origin. Check Codepen instead: https://codepen.io/extempl/pen/abOvBZv
sitekey used is one is for testing purposes only, as described here: https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-v2-what-should-i-do
var widgetId = "";
var onLoadRecaptcha = function() {
widgetId = grecaptcha.render("formSignup-btnSubmit", {
sitekey: $("#form_signup-recaptcha").attr("data-sitekey"),
callback: $("#form_signup-recaptcha").attr("data-callback")
});
};
var onSubmitFormSignupUser = function(response) {
console.log("response", response);
if ($('[name="username"]').val()) {
grecaptcha.execute(widgetId);
doSubmitFormToServer("#form_signup");
} else {
$(".status").text("failed");
grecaptcha.reset(widgetId);
}
};
var doSubmitFormToServer = function(selector) {
var myData = $(selector).serializeArray();
$(".status").text("submitted");
console.log("send form data", myData);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://www.google.com/recaptcha/api.js?onload=onLoadRecaptcha"></script>
<body>
<form id="form_signup" method="post" action="/signup">
<input type="text" name="username" />
<div
class="g-recaptcha"
id="form_signup-recaptcha"
data-size="invisible"
data-sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
data-callback="onSubmitFormSignupUser">
</div>
<button type="button" id="formSignup-btnSubmit">
Submit
</button>
<span class="status"></span>
</form>
</body>
it turns out that the solution is so simple.
this code
var onLoadRecaptcha = function() {
widgetId = grecaptcha.render("formSignup-btnSubmit", { // wrong element ID
sitekey: $("#form_signup-recaptcha").attr("data-sitekey"),
callback: $("#form_signup-recaptcha").attr("data-callback")
});
};
should be like this
var onLoadRecaptcha = function() {
widgetId = grecaptcha.render("form_signup-recaptcha", { // corrent element ID
sitekey: $("#form_signup-recaptcha").attr("data-sitekey"),
callback: $("#form_signup-recaptcha").attr("data-callback")
});
};
because the recaptcha element is like this
<div
class="g-recaptcha"
id="form_signup-recaptcha"
data-size="invisible"
data-sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
data-callback="onSubmitFormSignupUser">
</div>
so basically the parameters for grecaptcha.render should follow the properties in the element that has g-recaptcha class. my mistake was that I used the button id, even though the element with g-recaptcha class was the div.
I don't remember reading about this particular thing in the documentation. I guess I'm too stupid to realize that before this.. I hope this makes things clear for others with the same problem.

parsley.js - prevent isValid from firing events / just check true or false

Hello I am at my wit's end and I've been stuck creating a more complex version of the form than the example I provide.
I have JS object that is representation of the form. I use parsley's "isValid" on the form itself (checkAll and checkGroup function). These methods are fired on every input that is marked with data-parsley-required attribute. The reason for this is I need to know the state of the whole form and it's parts so I can enable/disable step buttons.
Everything works fine but I also need to call external API when all validations have successed, see line 35. The methods checkAll and checkGroup are basically firing the events again, thus making more AJAX calls (we have limit on calls to the API). Is there a way to force method isValid to just check if the field has been validated and get true/false value out of it?
The whole thing is coded and depends on this structure so the best way would be to have similar functionality. I'm not so experienced so I make lot of mistakes. My example is very simplified version of my actual form but when you open console window you can see what I mean. Uncomment lines 32 and 33 to see the difference and you will know what I mean.
Example code
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<form action="" id="myform">
<div id="section1">
<input type="text" id="field1" data-parsley-required data-parsley-trigger="input" data-parsley-group="group1" data-parsley-lengthvalidator data-parsley-remote="http://pokeapi.co/api/v2/pokemon/1" data-parsley-remote-validator="remotevalidator" /><br />
<button id="next" disabled>Next</button><br />
</div>
<div id="section2">
<input type="text" id="field2" data-parsley-required data-parsley-trigger="input" data-parsley-group="group2" />
</div>
<input type="submit" id="submit-button" disabled />
</form>
</body>
</html>
JS:
function Form(form) {
this.form = form;
this.validations = {};
this.formValid = false;
this.checkAll = function() {
var result = $(form).parsley().isValid();
if (result) {
$('#submit-button').removeAttr('disabled');
console.log('form validated');
} else {
$('#submit-button').attr('disabled', true);
}
this.formValid = result;
};
this.checkGroup = function(e) {
var group = $(e.target).attr('data-parsley-group');
var result = $(form).parsley().isValid({group: group});
if (result) {
$('#next').removeAttr('disabled');
console.log('group validated');
} else {
$('#next').attr('disabled', true);
}
this.validations[group] = result;
};
this.initialize = function() {
var self = this;
$(this.form).parsley();
$('*[data-parsley-required]').on('input', function(e) {
self.checkAll();
self.checkGroup(e);
});
$('#field1').parsley().on('field:success', function() {
console.log('calling another API')
})
Parsley.addValidator('lengthvalidator', {
validateString: function(value) {
console.log('local validator');
return value.length > 0;
}
});
Parsley.addAsyncValidator('remotevalidator', function(response) {
console.log('remote validator');
return response.responseJSON.name === 'bulbasaur';
})
}
}
var form = new Form('#myform');
form.initialize();

adjusting default value script to work with multiple rows

I am using a default value script (jquery.defaultvalue.js) to add default text to various input fields on a form:
<script type='text/javascript'>
jQuery(function($) {
$("#name, #email, #organisation, #position").defaultvalue("Name", "Email", "Organisation", "Position");
});
</script>
The form looks like this:
<form method="post" name="booking" action="bookingengine.php">
<p><input type="text" name="name[]" id="name">
<input type="text" name="email[]" id="email">
<input type="text" name="organisation[]" id="organisation">
<input type="text" name="position[]" id="position">
<span class="remove">Remove</span></p>
<p><span class="add">Add person</span><br /><br /><input type="submit" name="submit" id="submit" value="Submit" class="submit-button" /></p>
</form>
I am also using a script so that users can dynamically add (clone) rows to the form:
<script>
$(document).ready(function() {
$(".add").click(function() {
var x = $("form > p:first-child").clone(true).insertBefore("form > p:last-child");
x.find('input').each(function() { this.value = ''; });
return false;
});
$(".remove").click(function() {
$(this).parent().remove();
});
});
</script>
So, when the page loads there is one row with the default values. The user would then start adding information to the inputs. I am wondering if there is a way of having the default values show up in subsequent rows that are added as well.
You can see the form in action here.
Thanks,
Nick
Just call .defaultValue this once the new row is created. The below assumes the format of the columns is precticable/remains the same.
$(".add").click(function() {
var x = $("form > p:first-child");
x.clone(true).insertBefore("form > p:last-child");
x.find('input:not(:submit)').defaultvalue("Name", "Email", "Organisation", "Position");
return false;
});
You should remove ids from the input fields because once these are cloned, the ids, classes, everything about the elements are cloned. So you'll basically end up with multiple elements in the DOM with the same id -- not good.
A better "set defaults"
Personally I would remove the "set defaults plugin" if it's used purely on the site for this purpose. It can easily be re-created with the below and this is more efficient because it doesn't care about ordering of input elements.
var defaults = {
'name[]': 'Name',
'email[]': 'Email',
'organisation[]': 'Organisation',
'position[]': 'Position'
};
var setDefaults = function(inputElements)
{
$(inputElements).each(function() {
var d = defaults[this.name];
if (d && d.length)
{
this.value = d;
$(this).data('isDefault', true);
}
});
};
Then you can simply do (once page is loaded):
setDefaults(jQuery('form[name=booking] input'));
And once a row is added:
$(".add").click(function() {
var x = $("form > p:first-child");
x.clone(true).insertBefore("form > p:last-child");
setDefaults(x.find('input')); // <-- let the magic begin
return false;
});
For the toggling of default values you can simply delegate events and with the help of setDefault
// Toggles
$('form[name=booking]').delegate('input', {
'focus': function() {
if ($(this).data('isDefault'))
$(this).val('').removeData('isDefault');
},
'blur': function() {
if (!this.value.length) setDefaults(this);
}
});
Fiddle: http://jsfiddle.net/garreh/zEmhS/3/ (shows correct toggling of default values)
Okey, first of all; ids must be unique so change your ids to classes if you intend to have more then one of them.
and then in your add function before your "return false":
var
inputs = x.getElementsByTagName('input'),
defaults = ["Name", "Email", "Organisation", "Position"];
for(var i in inputs){
if(typeof inputs[i] == 'object'){
$(inputs[i]).defaultvalue(defaults[i]);
}
}

jQuery events not firing

I have been trying to get this to work. Basically I have a search box that has a default string in it (i.e. Search) and it should go away when the user clicks on the input field.
Here is the code:
HTML:
<form method="get" action="index.php" id="search">
<span id="searchLogo"></span>
<input type='text' name='q' id='searchBox' value="Search <?php print $row[0]?> tweets!" autocomplete="off" />
</form>
Javascript/jQuery: (defaultString is a global variable that has the value of the textbox)
function clearDefault() {
var element = $('#searchBox');
if(element.attr('value') == defaultString) {
element.attr('value',"");
}
element.css('color','black');
}
$('#searchBox').focus(function() {
clearDefault();
});
Problem is here:
if(element.attr('value') == defaultString) {
element.attr('value',"");
}
Change it with:
if(element.val() == defaultString) {
element.val('value');
}
Update: Check it here:
http://jsfiddle.net/mr3T3/2/
The problem was that the event binding was not inside the $(document).ready() handler.
Fixed:
function clearDefault() {
var element = $('#searchBox');
if(element.val() == defaultString) {
element.val("");
}
element.css('color','black');
}
$(document).ready(function() {
$('#searchBox').focus(function() {
clearDefault();
});
});
It could be that event in fact is firing and your problem is in
if(element.attr('value') == defaultString) {
element.attr('value',"");
}
is "defaultString" properly defined?
put a simple alert() inside clearDefaults() and see if the event works.
i don't think clearDefault is reusable function, so don't create unnecessary function for small block of code. See the following code sample, i added a small improvement in your functionality.
<form method="get" action="index.php" id="search">
<span id="searchLogo"></span>
<input type='text' name='q' id='searchBox' default="Search tweets!" value="Search tweets!" autocomplete="off" />
</form>
$(document).ready(function() {
$("#searchBox").focus(function(e){
var $this = $(this);
if($this.val() == $this.attr('default')) $this.val('');
else if($this.val().length == 0 ) $this.val($this.attr('default'));
});
$("#searchBox").blur(function(e){
var $this = $(this);
if($this.val().length == 0 ) $this.val($this.attr('default'));
});
});
I added a default attribute to store default value and used it later on blur event.
See the example in jsFiddler

Categories

Resources