Get Closest Form to an Element in jQuery - javascript

I have this js/jquery script i wrote to check all checboxes with in a form. It works well but this checks all checkboxes that are on page irrespective of the form wrapper they are.
here is the function
function toggleCheck(state)
{ var checkboxes = jQuery("form input:checkbox");
if(state==true){checkboxes.prop('checked',true); }
if(state==false){ checkboxes.prop('checked',false); }
}
usage
<a id="check" href="#" onclick="javascript:toggleCheck(true);" class="btn">Select All</a>
<a id="uncheck" href="#" onclick="javascript:toggleCheck(false);" class="btn">Deselect All</a>
Note this works well but my problem is if i have
<form action="myaction-page">//some form elements and checkboxes </form>
and
<form action="myaction-page-2">//some form elements and checkboxes </form>
and i click the check all link in form1, all checkboxes gets check including those in form2.
Is there a way to separate them without introducing form ids or names?..this is because i have used it extensively in production code and to rewrite all will be trouble.
Any Help will be greatly appreciated.
P.S it is worth noting that the select all and deselect all are within both forms and both forms are on the same page.

Assuming your select/deselect links are inside the form, do:
function toggleCheck(state,elem) {
jQuery(elem).closest("form").find("input:checkbox").prop('checked', state);
}
And change your link JS to (passing the appropriate true/false parameter):
onclick="javascript:toggleCheck(false,this);"
jsFiddle example

The answer is: it depends on your DOM. If your <a> elements are inside the form, you could use $.closest():
function toggleCheck(state) {
$(this)
.closest("form")
.find("input[type=checkbox]")
.prop('checked', state);
}
I slightly altered your code to increase readability, performance (see the additional notes regarding the :checkbox selector) and be more "jQuery"-like.
[edit]
After reading the comments I rewrote the function, still on the assumption that the <a> elements are inside the <form>, this time also with a working jsFiddle:
function toggleCheck(state) {
$(document).find(event.target)
.closest("form")
.find("input[type=checkbox]")
.prop('checked', state);
}
[edit2]
And for the sake of completness regarding my comment about the order of the elements (jsFiddle):
function toggleCheck(state) {
var pos = $(document).find(event.target.localName + "." + event.target.className).index(event.target);
var $form = $($(document).find("form").get(Math.floor(pos/2)));
$form
.find("input[type=checkbox]")
.prop('checked', state)
}
[edit3]
The last edit, I promise. This time I ignore the default callback and do my own thing:
function toggleCheck(state) {
// either write the s tate now in a variable for
// the next callback
}
$(document).on("click", "a.btn", function(e) {
var $ele = $(this);
// or do some string exploration
var toggle = $ele.attr("onclick").indexOf("true") > -1;
var pos = $(document).find("a.btn").index($ele);
var $form = $($(document).find("form").get(Math.floor(pos/2)));
$form
.find("input[type=checkbox]")
.prop('checked', toggle);
});
Works without having to touch anything. But I wouldn't want to use this. ;-)

the .closest method will do what you want.
onclick:
onclick="toggleCheck.call(this,true);"
js:
function toggleCheck(state) {
var checkboxes = jQuery(this).closest("form").find("input:checkbox");
checkboxes.prop('checked',state || false);
}

You can also just apply a data-target to your anchors and eval based on the action of your form elements:
... data-target='myaction-page' ...
... checkboxes[i].form.getAttribute( 'action' ) ...
Shown in detail on this JSFiddle

Related

jquery function to handle a series of href in div rows

Imagine a table (actually constructed of divs) with rows and in the final cell in each row, I have an input text and a link that look like this:
<input type="text" name="message" id="message_#Model.IncidentId" value="">
Send a Comment
After each row (the parent div), I have a chunk of code like the following to ajaxify the link and text input:
$('#send_#Model.IncidentId').click(function () {
var msg = $('#message_#Model.IncidentId').val();
if (msg != '') { $(this).attr('href', this.href + '?msg=' + msg) }
$.post(this.href, function (json) {
if (json.jsonResult === null) {
$("#msg_#Model.IncidentId").html("Sent...");
} else {
window.location.href = json.jsonResult;
}
});
return false;
});
It works. However, there are at least 10 of these on each page. What I'm trying to do is consolidate the jquery into one function to handle all the links. Can I use jquery "this" or pass the IncidentId to the jquery function or something? It seems like "this" would not work because the input text is outside of the link? How can I do this to have one function for the entire page?
Keep in mind it's not imperative that I splash everything with the IncidentId. So, if I need to make one or more of the ids or names generic, that would be ok. It just needs to not get confused about what pair it's handling. I've seen some comments that a form might help, but 10+ forms on a page is ok? Plus, as it stands, there will never be any other input fields than what is shown above.
I appreciate your help. Thanks.
Update: So, I basically used Søren's recommended html5 data-* (data-id) attribute in my link, gave it a class name, and then moved my url down to the function as well...and then simply replaced all my #Model.IncidentIds. The one catch is that I had to use the following to register my click event:
$(document).on('click', ".ajax-link", function () {
I guess because I'm using handlebars to dynamically generate the page? I hadn't tested the original function since I moved it to my infinite scroll layout mentioned in the comments. Thanks all for replying.
Try this:
<input type="text" name="message" data-input-id="1" value="">
<a class="ajax-link" href="#" data-link-id="1">Send a Comment</a>
$('.ajax-link').click(function () {
var id = $(this).attr('data-link-id');
var msg = $('[data-link-id='+id+']').val();
if (msg != '') { $(this).attr('href', this.href + '?msg=' + msg) }
$.post(this.href, function (json) {
if (json.jsonResult === null) {
$("[data-link-id='+id+']").html("Sent...");
} else {
console.debug(json.jsonResult);
}
});
return false;
});
Make sure the link and field have the same id
First, make sure you have some useful class name's in place. E.g.,
<input type="text" class="incident-message" name="message" id="message_#Model.IncidentId" value="">
Send a Comment
That should allow you to create a nice, row-generic script like this:
$('.incident-link').click(function(e) {
e.preventDefault();
var $this = $(this),
$row = $this.closest('div'),
$msg = $row.find('.incident-message');
var msg = $msg.val();
if (msg != '') {
$this.attr('href', $this.attr('href') + '?msg=' + msg);
}
$.post($this.attr('href'), function (json) {
if (json.jsonResult === null) {
// I didn't see any markup for your #msg element, but assuming
// that you give it a useful classname, you can do something
// similar to this:
$row.find('.some-msg-className').html('Sent...');
} else {
window.location.href = json.jsonResult;
}
});
});
As far as grouping the events to a single handler, just use a class instead of id's.
$('.thisKind').click(function () {
or if the content is dynamic, use a single event for the parent with a selector in the on() method
$('#parentId').on("click", ".thisKind", function() {
As far as the this usage, you should familiarize yourself with jquery's DOM traversal using closest() to go up the tree and find() to go down

How to corectly bind events in a loop

On a page I have couple of divs, that look like this:
<div class="product-input">
<input type="hidden" class="hidden-input">
<input type="text">
<button class="remove">X</button>
</div>
I'm trying to bind an event to that remove button with this code (simplified):
$('.product-input').each(function() {
product = $(this);
product_field = product.find('.hidden-input');
product.on('click', '.remove', function(event) {
product_field.val(null);
});
});
It works perfectly when there is only one "product-input" div. When there is more of them, all remove buttons remove value from the hidden field from the last product-input div.
https://jsfiddle.net/ryzr40yh/
Can somebody help me finding the bug?
You dont need to iterate over the element for binding the same event. you can rather bind the event to all at once:
$('.product-input').on('click', '.remove', function(event) {
$(this).prevAll('.hidden-input').val("");
});
If the remove buttons are not added dynamically, you will not need event delegation:
$('.remove').click(function(event) {
$(this).prevAll('.hidden-input').val("");
});
Working Demo
You need to declare product and product_field as local variables, now they are global variables. So whichever button is clicked inside the click handler product_field will refer to the last input element.
$('.product-input').each(function() {
var product = $(this);
var product_field = product.find('.hidden-input');
product.on('click', '.remove', function(event) {
product_field.val(null);
});
});
Demo: Fiddle
But you can simplify it without using a loop as below using the siblings relationship between the clicked button and the input field
$('.product-input .remove').click(function () {
$(this).siblings('.hidden-input').val('')
})
Demo: Fiddle

Using $(this) inside a jQuery AJAX callback

I have been all over Stack Overflow looking for a solution and none seem to work.
I cannot seem to figure out the issue. I have a button inside a <td> and on clicking it I want to make an AJAX call to update a database and upon success of that AJAX call I want to update the class of the <td> to mark the button as clicked.
I have tried var that = this; in the function. I've tried context: this, in the callback.
function setScoreA(event,candidate,rubric,category,score){
var author = document.getElementById("author").value;
if(author != ""){
$.ajax({
context: this,
type: "POST",
url: "stressBoardUpdate.php",
data: "candidate="+candidate+"&category="+category+"&score="+score+"&author="+author,
success: function(){
$(that).parent('td').siblings().removeClass('isScore');
$(that).parent('td').addClass('isScore');
}
});
}else{
alert("Author must contain something...");
}
}
Here is how the function would get invoked.
<input type="button" "="" value="5" onclick="setScoreA('Stress Board','Y235','Stress Board Rubric','Handled Stress','5');">
onclick="setScoreA does not set this to the element clicked but rather to window. The way you are using it. The way you are using it, I'm not sure that you could actually get a reference to the element. Instead of using onclick, you should bind an event listener (which you can do with jQuery anyway):
$("input").on("click", function () {
setScoreA(this, 'Stress Board','Y235','Stress Board Rubric','Handled Stress','5');
});
function setScoreA(element, ...
/* snip */
context: element
If you really wanted to stick with this for some reason, you could use:
setScoreA.call(this, 'Stress Board' ...
First of all, make use data attributes in your code and setup a common .click() listener
HTML:
<input type="button" class=".button-group" data-event="Stress Board" data-candidate="Y235" data-rubric="Stress Board Rubric" data-category="Handled Stress" data-score="5">
jQuery:
$(".button-group").click(function() {
// Do something
});
Also, I presume you are dynamically generating many buttons. The code above could be improved having only 1 click listener for the whole table, rather setting up a click listener for each item.
$("#wrapper").on("click", "input", function() {
var event = $(this).data("event");
var candidate = $(this).data("candidate");
var rubric = $(this).data("rubric");
var category = $(this).data("category");
var score = $(this).data("score");
setScoreA(this, event, candidate, rubric, category, score);
});
JSFIDDLE DEMO
Resources:
.data()
.click()
.on()

Defining a single jQuery function for separate DOM elements

I have several jQuery click functions- each is attached to a different DOM element, and does slightly different things...
One, for example, opens and closes a dictionary, and changes the text...
$(".dictionaryFlip").click(function(){
var link = $(this);
$(".dictionaryHolder").slideToggle('fast', function() {
if ($(this).is(":visible")) {
link.text("dictionary ON");
}
else {
link.text("dictionary OFF");
}
});
});
HTML
<div class="dictionaryHolder">
<div id="dictionaryHeading">
<span class="dictionaryTitle">中 文 词 典</span>
<span class="dictionaryHeadings">Dialog</span>
<span class="dictionaryHeadings">Word Bank</span>
</div>
</div>
<p class="dictionaryFlip">toggle dictionary: off</p>
I have a separate click function for each thing I'd like to do...
Is there a way to define one click function and assign it to different DOM elements? Then maybe use if else logic to change up what's done inside the function?
Thanks!
Clarification:
I have a click function to 1) Turn on and off the dictionary, 2) Turn on and off the menu, 3) Turn on and off the minimap... etc... Just wanted to cut down on code by combining all of these into a single click function
You can of course define a single function and use it on multiple HTML elements. It's a common pattern and should be utilized if at all possible!
var onclick = function(event) {
var $elem = $(this);
alert("Clicked!");
};
$("a").click(onclick);
$(".b").click(onclick);
$("#c").click(onclick);
// jQuery can select multiple elements in one selector
$("a, .b, #c").click(onclick);
You can also store contextual information on the element using the data- custom attribute. jQuery has a nice .data function (it's simply a prefixed proxy for .attr) that allows you to easily set and retrieve keys and values on an element. Say we have a list of people, for example:
<section>
<div class="user" data-id="124124">
<h1>John Smith</h1>
<h3>Cupertino, San Franciso</h3>
</div>
</section>
Now we register a click handler on the .user class and get the id on the user:
var onclick = function(event) {
var $this = $(this), //Always good to cache your jQuery elements (if you use them more than once)
id = $this.data("id");
alert("User ID: " + id);
};
$(".user").click(onclick);
Here's a simple pattern
function a(elem){
var link = $(elem);
$(".dictionaryHolder").slideToggle('fast', function() {
if (link.is(":visible")) {
link.text("dictionary ON");
}
else {
link.text("dictionary OFF");
}
});
}
$(".dictionaryFlip").click(function(){a(this);});
$(".anotherElement").click(function(){a(this);});
Well, you could do something like:
var f = function() {
var $this = $(this);
if($this.hasClass('A')) { /* do something */ }
if($this.hasClass('B')) { /* do something else */ }
}
$('.selector').click(f);
and so inside the f function you check what was class of clicked element
and depending on that do what u wish
For better performance, you can assign only one event listener to your page. Then, use event.target to know which part was clicked and what to do.
I would put each action in a separate function, to keep code readable.
I would also recommend using a unique Id per clickable item you need.
$("body").click(function(event) {
switch(event.target.id) {
// call suitable action according to the id of clicked element
case 'dictionaryFlip':
flipDictionnary()
break;
case 'menuToggle':
toggleMenu()
break;
// other actions go here
}
});
function flipDictionnary() {
// code here
}
function toggleMenu() {
// code here
}
cf. Event Delegation with jQuery http://www.sitepoint.com/event-delegation-with-jquery/

Prototype event handler

I've defined the following HTML elements
<span class="toggle-arrow">▼</span>
<span class="toggle-arrow" style="display:none;">▶</span>
When I click on one of the elements the visibility of both should be toggled. I tried the following Prototype code:
$$('.toggle-arrow').each(function(element) {
element.observe('click', function() {
$(element).toggle();
});
});
but it doesn't work. I know everything would be much simpler if I used jQuery, but unfortunately this is not an option:
Instead of iterating through all arrows in the collection, you can use the invoke method, to bind the event handlers, as well as toggling them. Here's an example:
var arrows = $$('.toggle-arrow');
arrows.invoke("observe", "click", function () {
arrows.invoke("toggle");
});
DEMO: http://jsfiddle.net/ddMn4/
I realize this is not quite what you're asking for, but consider something like this:
<div class="toggle-arrow-container">
<span class="toggle-arrow" style="color: pink;">▶</span>
<span class="toggle-arrow" style="display:none; color: orange;">▶</span>
</div>
document.on('click', '.toggle-arrow-container .toggle-arrow', function(event, el) {
var buddies = el.up('.toggle-arrow-container').select('.toggle-arrow');
buddies.invoke('toggle');
});
This will allow you to have multiple "toggle sets" on the page. Check out the fiddle: http://jsfiddle.net/nDppd/
Hope this helps on your Prototype adventure.
Off the cuff:
function toggleArrows(e) {
e.stop();
// first discover clicked arow
var clickedArrow = e.findElement();
// second hide all arrows
$$('.toggle-arrow').invoke('hide');
// third find arrow that wasn't clicked
var arw = $$('.toggle-arrow').find(function(a) {
return a.identify() != clickedArrow.identify();
});
// fourth complete the toggle
if(arw)
arw.show();
}
Wire the toggle arrow function in document loaded event like this
document.on('click','.toggle-arrow', toggleArrows.bindAsEventListener());
That's it, however you would have more success if you took advantage of two css classes of: arrow and arrow-selected. Then you could easily write your selector using these class names to invoke your hide/show "toggle" with something like:
function toggleArrows(e) {
e.stop();
$$('.toggle-arrow').invoke('hide');
var arw = $$('.toggle-arrow').reject(function(r) {
r.hasClassName('arrow-selected'); });
$$('.arrow-selected').invoke('removeClassName', 'arrow-selected');
arw.show();
arw.addClassName('arrow-selected');
}

Categories