JQuery :has(), I need to select only children - javascript

I have some hierarchical data I am manipulating with JQuery, and I am trying to add some type of visual indication about which elements in my tree are selected. Here is a sample of the html I am generating (which we are manipulating):
<li>
<input class="selectedcheck" checked disabled type="checkbox" name="ign-130">
<input class="millermultiselect" type="checkbox" name="chk-130">
Egypt
<ul>
<li>
<input class="selectedcheck" checked disabled type="checkbox" name="ign-131">
<input class="millermultiselect" type="checkbox" name="chk-131">
Cairo
</li>
</ul>
</li>
All of this resides within a div#miller. The first check box (.selectedcheck), is used only to show that a region is selected (it starts out hidden, and is shown when the parent region is selected, so if you clicked the .millermultiselect in Egypt, the .selectedcheck in Cairo would become visible replacing Cairo's .millermultiselect). What I would like to do is also add a class to any li which contains (as a direct child) a checked check box. I am attempting to accomplish this with the following JQuery code:
$("#miller li:has(input:checkbox:checked:visible)").addClass('chosen');
The problem that I am having is that since all of the regions are composed of nested ul's, checking a check box in any region also adds .chosen to any li higher in the tree... so clicking the check box for Cairo adds the .chosen class to Egypt and Africa.
Anyone have an idea how I would go about only capturing li's that have a visible, checked check box as a direct child?
Here is my whole js file just in case it provides insight:
$(document).ready(function() {
$(".selectedcheck").hide();
showSelected = function(){
$("#miller li").removeClass("chosen");
$("#miller li:has(input:checkbox:checked:visible)").addClass('chosen');
};
$("#miller a").click(function(event) {
event.preventDefault();
$("#miller li").removeClass("selected");
$(this).parent().addClass("selected");
data = $(this).data();
$("#addChild").attr("href", "addChild.php?parentid=" + data.self).html("Add item under " + data.nodename);
$("#addSibling").attr("href", "addChild.php?parentid=" + data.parent).html("Add item next to " + data.nodename);
$("#editRegion").attr("href", "editRegion.php?id=" + data.self).html("Edit " + data.nodename);
$("#deleteRegion").attr("href", "deleteRegion.php?id=" + data.self).html("Delete " + data.nodename);
$("#addResort").attr("href", "addResort.php?regionid=" + data.self).html("Add resort to " + data.nodename);
$("#miller ul ul").hide();
$(this).parents("#miller ul").show();
$(this).siblings("#miller ul").show();
showSelected();
});
$("#miller .millermultiselect").click(function(event) {
$("#miller li").removeClass("selected");
$(this).parent().addClass("selected");
$("#miller ul ul").hide();
$(this).parents("#miller ul").show();
if($(this).is(':checked')) {
$(this).parent().find(".millermultiselect").not(this).hide();
$(this).parent().find(".millermultiselect").not(this).removeAttr("checked");
$(this).parent().find(".selectedcheck").show();
} else {
$(this).parent().find(".millermultiselect").not(this).show();
$(this).parent().find(".selectedcheck").hide();
}
$(this).siblings(".selectedcheck").hide();
$(this).siblings("#miller ul").show();
showSelected();
});
});

You can use the direct descendant selector and then get it's parent:
$("#miller li > input:checkbox:checked:visible").parent().addClass("chosen");

how I would go about only capturing li's that have a visible, checked check box as a direct child?
How about reversing the problem?
$('input:checkbox').is(':visible').is(':checked').parent('li')

Related

Output input selection as text to element

I'm a UI Designer working on a multi-page Q&A form, I'm a beginner with jQuery mostly mashing snippets together.
Here's the code: http://codepen.io/covanant/pen/GJZYLq
This part of the form is basically multiple accordions wrapped into tabs, I have most of it working as required but one of the things I need to do, is that whenever I a choice or option, I want to be able to output that option to an element as text right underneath the question.
The element is:
<span class="selected-answer"></span>
You can see it displayed in the first question in the demo, the way that I'd like it to work is that whenever I click the Close All button, it will fadeIn the .selected-answer element and when I click Open All, it will fadeOut the .selected-answer element.
The buttons:
Open All
Close All
jQuery:
// Open All & Close All buttons
$('.closeall').click(function(){
$('.panel-collapse.in')
.collapse('hide');
});
$('.openall').click(function(){
$('.panel-collapse:not(".in")')
.collapse('show');
});
First, it doesn't make sense to give each of your select options the same value attribute. By convention, these should be distinct. If you aren't using the value attribute, you can remove it altogether. Otherwise, you should change it to something like:
<select>
<option value="None Selected">None Selected</option>
<option value="Photocell On">Photocell On</option>
<option value="Off Control Only">Off Control Only</option>
<option value="Photocell On / Off Control Only">Photocell On / Off Control Only</option>
</select>
Once that is sorted out, you need to go up the DOM hierarchy and find the right span element to change.
$('select').on('change', function() {
var span = $(this).closest('div.panel').find('span.selected-answer');
span.text($(this).val());
});
For the checkbox questions, you I would do something like this:
HTML:
<span class="selected-answer">
<ul class="checked-options">
<li data-check="checkbox1">nWifi (nLight)</li>
<li data-check="checkbox2">nLightFixtures</li>
<li data-check="checkbox3">xCella (LC&D)</li>
<li data-check="checkbox4">Daylight Harvesting</li>
<li data-check="checkbox5">xPoint (LC&D)</li>
<li data-check="checkbox6">nWifi (nLight)</li>
</ul>
</span>
CSS:
.checked-options li {
display: none;
}
jQuery:
$('input[type="checkbox"]').on('change', function() {
var checkbox = $(this);
var id = checkbox.attr('id');
if ($(this).prop('checked'))
$('li[data-check="' + id + '"]').show();
else
$('li[data-check="' + id + '"]').hide();
});
As for the fading, this should do the trick:
// Open All & Close All buttons
$('.closeall').click(function(){
$('.panel-collapse.in')
.collapse('hide');
$('.selected-answer').fadeIn();// <-- Fade in
});
$('.openall').click(function(){
$('.panel-collapse:not(".in")')
.collapse('show');
$('.selected-answer').fadeOut();// <-- Fade out
});
Also, depending on whether you want all the questions open or closed by default when the form first loads, you may need to hide all the .selected-answer elements on page load.
Here's the updated codepen.
I agree with VCode on using distinct values for each option in the select elements. But instead of using the value you provide for each option, I think you should use the actual label, that way you can have a more description label, than the option value.
I modified a few of your existing functions to actually populate the selected answer. First I noticed that you already have a function for handling changes to your select - in there I added a small snippet to get the selected answer and pass it to nextQuestion.
$(".panel-body select").change(function() {
var selectElem = $(this);
var answer = selectElem.find("option:selected").text();
nextQuestion(selectElem, answer);
});
Then you also have input elements. Here is your modified input change function:
$(".panel-body input").change(function() {
var inputElem = $(this);
var inputType = inputElem.attr('type');
// common parent for input
var commonParent = inputElem.closest(".panel-body");
var answers = commonParent
.find("input:checked")
.closest("."+inputType)
.find("label")
.map(function(){return this.innerText;})
.get()
.join(", ");
nextQuestion(inputElem, answers);
});
And now as you may have noticed, I added a parameter to the nextQuestion function. I put this code in nextQuestion because you were already accessing the parent there so I wanted to re-use that logic to populate the selected answer.
function nextQuestion(currentQuestion,selectedAnswer) {
var parentEle = currentQuestion.parents(".panel");
if (arguments.length>1) {
parentEle.find('.selected-answer').text(selectedAnswer);
}
if (parentEle.next()) {
parentEle.find(".fa-question").addClass("fa-check check-mark").removeClass("question-mark fa-question").text("");
}
}
Just like VCode mentioned, you can do the fading of the answers using fadeIn/fadeOut
// Open All & Close All buttons
$('.closeall').click(function(){
$('.panel-collapse.in')
.collapse('hide');
$('.selected-answer').fadeIn();// <-- Fade in
});
$('.openall').click(function(){
$('.panel-collapse:not(".in")')
.collapse('show');
$('.selected-answer').fadeOut();// <-- Fade out
});
// hide all .selected-answers
$('.selected-answer').hide();
Here is a link to your codepen with my modifications: http://codepen.io/anon/pen/ZGpXXK

Jquery - find closest text by class

Here is my HTML and script. I simply want to find the text of what is clicked, and include the text of it's previous element with a certain class, in the alert. Elements are not necessarily children/parents here.
So, if Prince5 is clicked, the alert should read King1: Queen3: Prince5
I'm close, but it's currently always giving the text as Queen1, since it's the first text in that column.
Any ideas?
<li class="king"><a href="#" >King1</a>
<span class="column1">
<li>Queen1</li>
<li> Prince1</li>
<li> Prince2</li>
<br>
<li>Queen2</li>
<li> Prince3</li>
<li> Prince4</li>
<br>
<li>Queen3</li>
<li> Prince5</li>
<li> Prince6</li>
</span>
<script type="text/javascript">
$("li a").click(function() {
var three= $(this).text(),
one = $(this).closest('.king').find('a:first').text();
two = $(this).closest(".column1").find("a.queen:first").text();
alert(one + ":" + two + ":" + three);
});
</script>
There are a number of problems with the HTML shown. A span is not a valid parent for the LI elements so I added a UL.
Basically you can go back up the ancestors of the clicked link, then sideways through previous siblings (that match .queen), then down again to the link on the first match:
$("li a").click(function () {
var three = $(this).text(),
one = $(this).closest('.king').find('a:first').text();
two = $(this).closest("li").prevAll("li:has(a.queen)").first().find('a').text();
alert(one + ":" + two + ":" + three);
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/7q3e9p7g/1/

Javascript - Dynamically added checkboxes not working as expected in Kendo Panelbar headers

I have this fiddle which works when adding static checkboxes.
I'm creating the checkboxes for the accordion panels like so:
liID = "tab" + x;
$("<li id = '"+ liID +"' class='k-item k-last k-state-default' aria-expanded='false'><input type='checkbox' id='c" + x + "' class='cbSelect' /><label for='c" + x + "'><span id='cbSelect" + x + "'></span>" + liID + "</label></li>").appendTo("#panelbar");
The above x-value is the for loop variable.
I have used the following styles on the checkboxes:
input[type="checkbox"]
{
display:none;
}
input[type="checkbox"] + label span
{
display:inline-block;
width:14px;
height:14px;
margin:-1px 4px 0 0;
vertical-align:middle;
background:url('../images/checkBox.png') right top no-repeat;
cursor:pointer;
float:right;
margin-top:10px;
margin-right:10px;
}
input[type="checkbox"]:checked + label span
{
background:url('../images/checkBox.png') -1px top no-repeat;
}
The checkbox.png image above is the following:
Which displays the checkboxes, but does not allow the check event when clicked, as it first opens the tab before allowing this.
I have tried using the following:
$("#panelbar").on("click", "input.cbSelect", function(evt)
{
evt.stopPropagation();
});
but I'm not sure whether I'm linking it properly as this is linked with the normal checkbox which I have hidden using display:none;
When the normal checkbox is displayed with the newly styled checkbox, and when clicking on the original checkbox, it works as expected. Instead of clicking on the original checkbox, I want to be able to click on the newly created checkbox and use the stopPropagation on it.
That is the first issue, the next is that I'm creating a checkbox for a grid like so:
var chkBox = "<input type='checkbox' id='cUpload1' class='cbSelect' /><label for='cUpload1'><span id='cbSelectUpload'></span>test1</label>";
Then trying to add it to a simple dataSourse like so:
var uploadedFiles =
[
{
facility: "Sunrise medical Laboratories",
documentName: "Lab Results",
documentType: "PDF",
selected: chkBox
}
];
The styles used for the above checkbox aren't applied to the checkbox as nothing is displayed.
Any ideas why it isn't working?
btw, I'm trying to implement all of the above into this fiddle. The above coding isn't currently in the fiddle.
Any help will be appreciated!
Your first issue is mainly due to the interaction with kendo.
The selector "input.cbSelect" is not correct, since you are replacing the original checkboxes with label-only ones. So actually you are clicking on the label and not on the checkbox itself. The correct one would be "li label span" but even then it wouldn't work because you cannot prevent kendo from selecting the panel.
Even
$("#panelbar").on("click", "label span", function(evt) {
evt.stopImmediatePropagation();
return false;
})
wouldn't prevent the kendo selection.
But what you actually can do is unbinding the child from the parent with a html action, so the kendo handler doesn't trigger:
Solution:
In your template replace
<span id='cbSelect" + x + "'></span>
with
<a href="#" id='cbSelect" + x + "'></a>
and in your input css the span with a.
The a-Element triggers a seperate action, so now you can stop the kendo Handler with
$("#panelbar").on("click", "label a", function(evt) {
evt.stopImmediatePropagation();
});
The problem now is, that the checking of the checkbox by clicking on the label also doesn't work. So we have to do this manually by extending the handler above:
$("#panelbar").on("click", "label a", function(evt) {
var chkbx = $(this).closest('li').find('input');
chkbx.prop('checked', !chkbx.prop('checked'));
evt.stopImmediatePropagation();
})
Working example:
http://jsfiddle.net/Nitro3k/5nX8r/47/
Alternative:
If you do not want the link inside the label, you can also:
remove the span completely, style the label directly instead and call return false; instead of evt.stopImmediatePropagation();
build a custom checkbox, since you have to trigger it with js anyway:
Remove the lable, style a block instead (e.g .checkbox) together with a checked class (e.g. .checkbox.checked).
Build the block around the checkbox:
<div class="checkbox"><input type='checkbox' id='c1' class='cbSelect' /></div>
and add the handler:
$(document).on('click', '.checkbox', function(){
var chkbx = $(this).find('input');
chkbx.prop('checked', !chkbx.prop('checked'));
$(this).toggleClass('checked');
})

Append sub-elements to its parent element only

I am trying to convert a bunch of menu dropdowns into multiple elements. I have been able to do this when theres only one dropdown on the page but once I add the others, my script seems to run through each menu multiple times. I am new to Javascript/Jquery but I was wondering if there was a way to make it only input to its parent element?
Here is my current script that works for a single dropdown:
$('.mylinks li').each(function() {
var inputClass = $('.mylinks .link').html().toLowerCase();
$('body').prepend('<select class="'+inputClass+'" onchange="window.location.href=this.options[this.selectedIndex].value"></select>');
$('a').each(function() {
var linkName = $(this).html();
var linkVal = $(this).attr('href');
$('select').append('<option value="'+linkVal+'">'+linkName+'</option>');
});
});
HTML
<div class="mylinks">
<ul>
<li>
Drop 1
Link 1
Link 2
Link 3
</li>
<li>
Drop 2
Link 1
Link 2
Link 3
</li>
</ul>
</div>
I have an example of this error here as well: http://jsfiddle.net/UdTcF/
You can try this code:
$(document).ready(function () {
$('.mylinks li').each(function () {
var inputClass = $('.mylinks a').html().toLowerCase();
var select = $('<select class="' + inputClass + '" onchange="window.location.href=this.options[this.selectedIndex].value"></select>');
$('body').prepend(select);
$(this).find('a').each(function () {
var linkName = $(this).html();
var linkVal = $(this).attr('href');
select.append('<option value="' + linkVal + '">' + linkName + '</option>');
});
});
});
Use a variable select to save the <select> you want to add, and late can add <option> to this variable using select.append().
And use $(this).find('a') instead of $('a') to find <a> in certain <li> but not all <a>.
Here is jsfiddle.
Here:
$('a').each,
you're selecting all a elements. I think that you want to select just only those in the current li. SO, just replace add:
$('a',this).each,
it will select only a elements which are children of this, eg the li element.

Modify this function to remove values if they're unchecked

I'm using this function with an unordered list (<ul>) in order to replicate the functionality of a Select dropdown element. The function correctly shows the user's selected values in the designated container when they are checked, but it isn't removing them when an item is unchecked.
I've included the relevant snippet below, and posted a working example with the complete code here: http://jsfiddle.net/chayacooper/GS8dM/7/
JS
$(document).ready(function () {
$(".dropdown_box").click(function () {
$("#select_colors").show();
});
$(".dropdown_container ul li").click(function () {
var text = $(this.children[0]).find("input").val();
var currentHtml = $(".dropdown_box span").html();
$(".dropdown_box span").html(currentHtml.replace('Colors', ''));
$(".dropdown_box span").append(', ' + text);
});
});
HTML
<div class="dropdown_box"><span>Colors</span></div>
<div class="dropdown_container">
<ul id="select_colors">
<li><label><input type="checkbox" name="color[]" value="Black" />Black</label></li>
</ul>
</div>
You should give the container id to the function. Then, before you add the text of the selection, you should make sure that it is not in the text. If it is, delete it.

Categories