Taming the Select - Don't replace disabled OPTION - javascript

I have found a great piece of Javascript called Taming Select. I know it's quite old but has worked absolute wonders with rendering the < select > input into a UL dropdown list.
My problem is that my < select > DOM element has dynamically disabled < option > children, yet this piece of code does not discern whether it is disabled or not.
I was wondering a couple of things:
How do I get Javascript to identify disabled < options >?
Should I delete the DOM element completely or inject CSS classes into the newly made list items to disable them with user-select and pointer-events?
I have been on the search for nearly 8 hours and I can't seem to figure out how to do number 1 on my list.
I have tried getElementsByTagName('option').disabled and other variations of getElementsByTagName and nothing happens; even when I modify some examples in W3Schools.
Below is the code for TamingSelect:
function tamingselect()
{
if(!document.getElementById && !document.createTextNode){return;}
// Classes for the link and the visible dropdown
var ts_selectclass='turnintodropdown'; // class to identify selects
var ts_listclass='turnintoselect'; // class to identify ULs
var ts_boxclass='dropcontainer'; // parent element
var ts_triggeron='activetrigger'; // class for the active trigger link
var ts_triggeroff='trigger'; // class for the inactive trigger link
var ts_dropdownclosed='dropdownhidden'; // closed dropdown
var ts_dropdownopen='dropdownvisible'; // open dropdown
/*
Turn all selects into DOM dropdowns
*/
var count=0;
var toreplace=new Array();
var sels=document.getElementsByTagName('select');
for(var i=0;i<sels.length;i++){
if (ts_check(sels[i],ts_selectclass))
{
var hiddenfield=document.createElement('input');
hiddenfield.name=sels[i].name;
hiddenfield.type='hidden';
hiddenfield.id=sels[i].id;
hiddenfield.value=sels[i].options[0].value;
sels[i].parentNode.insertBefore(hiddenfield,sels[i])
var trigger=document.createElement('a');
ts_addclass(trigger,ts_triggeroff);
trigger.href='#';
trigger.onclick=function(){
ts_swapclass(this,ts_triggeroff,ts_triggeron)
ts_swapclass(this.parentNode.getElementsByTagName('ul')[0],ts_dropdownclosed,ts_dropdownopen);
return false;
}
trigger.appendChild(document.createTextNode(sels[i].options[0].text));
sels[i].parentNode.insertBefore(trigger,sels[i]);
var replaceUL=document.createElement('ul');
for(var j=0;j<sels[i].getElementsByTagName('option').length;j++)
{
var newli=document.createElement('li');
var newa=document.createElement('a');
newli.v=sels[i].getElementsByTagName('option')[j].value;
newli.elm=hiddenfield;
newli.istrigger=trigger;
newa.href='#';
newa.appendChild(document.createTextNode(
sels[i].getElementsByTagName('option')[j].text));
newli.onclick=function(){
this.elm.value=this.v;
ts_swapclass(this.istrigger,ts_triggeron,ts_triggeroff);
ts_swapclass(this.parentNode,ts_dropdownopen,ts_dropdownclosed)
this.istrigger.firstChild.nodeValue=this.firstChild.firstChild.nodeValue;
return false;
}
newli.appendChild(newa);
replaceUL.appendChild(newli);
}
ts_addclass(replaceUL,ts_dropdownclosed);
var div=document.createElement('div');
div.appendChild(replaceUL);
ts_addclass(div,ts_boxclass);
sels[i].parentNode.insertBefore(div,sels[i])
toreplace[count]=sels[i];
count++;
}
}
/*
Turn all ULs with the class defined above into dropdown navigations
*/
var uls=document.getElementsByTagName('ul');
for(var i=0;i<uls.length;i++)
{
if(ts_check(uls[i],ts_listclass))
{
var newform=document.createElement('form');
var newselect=document.createElement('select');
for(j=0;j<uls[i].getElementsByTagName('a').length;j++)
{
var newopt=document.createElement('option');
newopt.value=uls[i].getElementsByTagName('a')[j].href;
newopt.appendChild(document.createTextNode(uls[i].getElementsByTagName('a')[j].innerHTML));
newselect.appendChild(newopt);
}
newselect.onchange=function()
{
window.location=this.options[this.selectedIndex].value;
}
newform.appendChild(newselect);
uls[i].parentNode.insertBefore(newform,uls[i]);
toreplace[count]=uls[i];
count++;
}
}
for(i=0;i<count;i++){
toreplace[i].parentNode.removeChild(toreplace[i]);
}
function ts_check(o,c)
{
return new RegExp('\\b'+c+'\\b').test(o.className);
}
function ts_swapclass(o,c1,c2)
{
var cn=o.className
o.className=!ts_check(o,c1)?cn.replace(c2,c1):cn.replace(c1,c2);
}
function ts_addclass(o,c)
{
if(!ts_check(o,c)){o.className+=o.className==''?c:' '+c;}
}
}
window.onload=function()
{
tamingselect();
// add more functions if necessary
}
Any help would be greatly appreciated. I have very very little Javascript experience (to be honest, the closest I have come to studying it was ActionScript 10 years ago).
Thanks in advance.
Michael

You're looking for the .hasAttribute() function which returns a bool.
ie. el.hasAttribute('disabled').
https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute
As mentioned in the comment, to actually iterate through the options to check for the attribute you should do the following:
// returns an array like HTMLCollection of the elements that match the
// tag
let options = document.getElementsByTagName('option')
// uses the array method forEach on the HTMLCollection.
// note that HTMLCollections do not natively have the forEach method
// but still act behave normal arrays when using iteration.
Array.prototype.forEach.call( options, el => {
el.hasAttribute('disabled')
})

Related

Remove class based on elements but not others

I have these sections on this side scrolling site. And want to add a class which will change styling depending if you're on a certain section.
I'm working on this function. The top is what determines the section of the side scroller you are viewing.
The let variables and below is where it stops working. I'm trying to have it so if a nonHome ID section is clicked, for example "slide-1", then add the class 'nav-visibilty'. If they are a match "slide-2" and "slide-2" then remove said class. Am I close?
https://codepen.io/mikayp-the-styleful/pen/NWPxoXR?editors=1111
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
console.log('add')
} else{
nonHomeID.classList.remove("nav-visibility");
console.log('rem')
}
}
I am still not totally clear on the behavior that you want, but there are two errors in the code that can be fixed:
It seems like you are always using 'slide-2' instead of the slideId in your event handler.
As mentioned in a comment, nonHomeID is being used incorrectly in your comparison (it is either a string or an element, but you are using it as if it was a string in the if condition, and as the element in the else branch.) Here I have kept it as an element and renamed it for clarity.
Fixing these errors results in code that applies the nav-visibility class to all slides except the one selected by the button. Is that the desired behavior?
let nonHome = document.querySelectorAll(".slide-container section");
let nonHomeSelected = document.getElementById(slideId);
var i;
setTimeout(function() {
for (i = 0; i < nonHome.length; i++) {
if (nonHome[i] != nonHomeSelected) {
nonHome[i].classList.add("nav-visibility");
console.log("add");
} else {
nonHome[i].classList.remove("nav-visibility");
console.log("rem");
}
}
}, 1000);
Edit to add: If the goal is to add nav-visibility to all only the specific slideId, you should not be adding in a loop, i.e. you need to pull your check for whether the slide is Home outside the loop. There are conceptually two steps here: remove the class from all elements that are no longer to have it, then add the class to the element that needs it.
let slideToAddVisibilityTo = document.getElementById(slideId)
let homeSlide = document.getElementById('slide-2')
let allSlides = document.querySelectorAll(".slide-container section")
for (let i = 0; i < allSlides.length; ++i)
allSlides[i].classList.remove('nav-visiblity')
if (slideToAddVisibilityTo != homeSlide)
slideToAddVisibilityTo.classList.add('nav-visibility')
Just hide them all, then show the clicked one:
function showSection(id) {
var sections = document.getElementsByTagName("section");
for(var i=0; i<sections.length; i++) sections[i].classList.remove("nav-visibility");
var current = document.getElementById(id);
current.classList.add("nav-visibility");
}
Example: showSection("foo") will remove nav-visibility from all sections, then add it to the section with id foo.

How do I filter an unorderded list to display only selected items using Javascript?

I have this JSFiddle where I am trying to make it so that the items in an unordered list are visible only if the option selected in a drop down matches their class. List items may have multiple classes, but so long as at least one class matches, the item should be made visible.
The Javascript looks like this:
function showListCategories() {
var selection = document.getElementById("listDisplayer").selectedIndex;
var unHidden = document.getElementsByClassName(selection);
for (var i = 0; i < unHidden.length; i++) {
unHidden[i].style.display = 'visible';
}
};
The idea is that it gets the current selection from the drop down, creates an array based on the matching classes, then cycles through each item and sets the CSS to be hidden on each one.
However, it's not working. Can anyone tell me where I'm going wroing?
Note that I haven't yet coded the "show all" option. I think I'll probably be able to figure that out once I have this first problem solved.
In your fiddle change load script No wrap - in <head>.
Just change your function like following
function showListCategories() {
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
lis[i].style.display = 'none';
}
//above code to reset all lis if they are already shown
var selection = document.getElementById("listDisplayer").value;
lis = document.getElementsByClassName(selection);
for (var i = 0; i < lis.length; i++) {
lis[i].style.display = 'block';
}
};
and in css it should be none not hidden
.cats, .rats, .bats {
display: none;
}
If you want to show all li when showAll is selected, add all classes to all lis.
You have a few things going on. First, your fiddle is not setup correctly, if you open the console you'll see:
Uncaught ReferenceError: showListCategories is not defined
This means that the function doesn't exist at the point you attach the event or that the function is out of scope, because by default jsFiddle will wrap your code in the onLoad event. To fix it you need to load the script as No wrap - in <body>.
Second, there's no such thing as a display:visible property in CSS. The property you want to toggle is display:none and display:list-item, as this is the default style of <li> elements.
Now, to make this work, it is easier if you add a common class to all items, let's say item, that way you can hide them all, and just show the one you want by checking if it has a certain class, as opposed to querying the DOM many times. You should cache your selectors, it is not necessary to query every time you call the function:
var select = document.getElementById('listDisplayer');
var items = document.getElementsByClassName('item');
function showListCategories() {
var selection = select.options[select.selectedIndex].value;
for (var i=0; i<items.length; i++) {
if (items[i].className.indexOf(selection) > -1) {
items[i].style.display = 'list-item';
} else {
items[i].style.display = 'none';
}
}
}
Demo: http://jsfiddle.net/E2DKh/28/
First there is no property in Css like display:hidden; it should be display: none;
here is the solution please not that i am doing it by targeting id finished
Js function
var selection = document.getElementById("listDisplayer");
var list = document.getElementsByTagName('li');
selection.onchange = function () {
var value = selection.options[selection.selectedIndex].value; // to get Value
for (var i = 0; i < list.length; i++) {
if (list[i].className.indexOf(value) > -1) {
list[i].style.display = "list-item";
} else {
list[i].style.display = "none"
}
}
}
css Code
.cats, .rats, .bats {
display: none;
}
JSFIDDLE
You have many things wrong in your code and a wrong setting in the jsFiddle. Here's a working version that also implements the "all" option:
Working demo: http://jsfiddle.net/jfriend00/5Efc5/
function applyToList(list, fn) {
for (var i = 0; i < list.length; i++) {
fn(list[i], list);
}
}
function hide(list) {
applyToList(list, function(item) {
item.style.display = "none";
});
}
function show(list) {
applyToList(list, function(item) {
item.style.display = "block";
});
}
function showListCategories() {
var value = document.getElementById("listDisplayer").value;
var itemList = document.getElementById("itemList");
var items = itemList.getElementsByTagName("li");
if (value === "all") {
show(items);
} else {
// hide all items by default
hide(items);
show(itemList.getElementsByClassName(value));
}
}
Changes made:
You have to fetch the .value of the select to see what the value was of the option that was picked. You were using the selectedIndex which is just a number.
A common technique for displaying only a set of objects is to hide all of them, then show just the ones you want. Since the browser only does one repaint for the entire operation, this is still visually seamless.
When finding items that match your class, you should be searching only the <ul>, not the entire document. I added an id to that <ul> tag so it can be found and then searched.
To save code, I added some utility functions for operating on an HTMLCollection or nodeList.
Tests for the "all" option and shows them all if that is selected
Changed the jsFiddle to the Head option so the code is available in the global scope so the HTML can find your change handler function.
Switched style settings to "block" and "none" since "visible" is not a valid setting for style.display.

js - swap image on click

I'm trying to replace one div with another and turn the others off:
JS
function imgToSWF1() {
document.getElementById('movie-con2 movie-con3 movie-con4').style.display = 'none';
document.getElementById('movie-con').style.display = 'block';
}
function imgToSWF2() {
document.getElementById('movie-con movie-con3 movie-con4').style.display = 'none';
document.getElementById('movie-con2').style.display = 'block';
}
function imgToSWF3() {
document.getElementById('movie-con movie-con2 movie-con4').style.display = 'none';
document.getElementById('movie-con3').style.display = 'block';
}
function imgToSWF4() {
document.getElementById('movie-con movie-con2 movie-con3').style.display = 'none';
document.getElementById('movie-con4').style.display = 'block';
}
HTML
<span onmouseover="src=imgToSWF1();"><div class="numbers">01</div></span>
<span onmouseover="src=imgToSWF2();"><div class="numbers">02</div></span>
<span onmouseover="src=imgToSWF3();"><div class="numbers">03</div></span>
<span onmouseover="src=imgToSWF4();"><div class="numbers">04</div></span>
I can't seem to get this to work and I believe that targetting multiple ID's isn't possible like this? Anyway any advice would be smashing - thanks!
You're correct that you cannot supply multiple ids to document.getElementById(). Instead, you can grab them all individually using an array. There are many ways to accomplish what you are trying to do. This is a straightforward method based on iterating through the array of the elements to hide and hiding all of them.
This method expects you to define the array of nodes to hide in each of your functions, and so is non-ideal.
// Example:
function imgToSWF1() {
var nodes = ['movie-con2', 'movie-con3', 'movie-con4'];
// Loop over and hide every node in the array
for (var i=0; i<nodes.length; i++) {
document.getElementById(nodes[i]).style.display = 'none';
}
document.getElementById('movie-con').style.display = 'block';
}
Better:
This can be refactored into one function, however. Make one function which recieves the node you want to show as an argument. Hide the others. The array should contain all nodes that may be hidden in any circumstance, and be defined at a higher scope than the function.
// Array holds all nodes ids
var nodes = ['movie-con', 'movie-con2', 'movie-con3', 'movie-con4'];
// Function receives a node id which will be shown
function imageToSWF(nodeId) {
// Loop over and hide every node in the array
for (var i=0; i<nodes.length; i++) {
document.getElementById(nodes[i]).style.display = 'none';
}
// And unhide/show the one you want, passed as an argument
document.getElementById(nodeId).style.display = 'block';
}
Call as:
<span onmouseover="imageToSWF('movie-con');"><div class="numbers">01</div></span>
<span onmouseover="imageToSWF('movie-con2');"><div class="numbers">02</div></span>
<span onmouseover="imageToSWF('movie-con3');"><div class="numbers">03</div></span>
<span onmouseover="imageToSWF('movie-con4');"><div class="numbers">04</div></span>
You have to target one element at a time with document.getElementById, eg. document.getElementById('movie-con2').style.display='none'; document.getElementById('movie-con3').style.display='none';
etc, etc.
You could also use Jquery siblings selector to show or hide all elements that are siblings within a parent tag.
You definitely can't do that in straight up javascript. The document.getElementById function only returns a single element from the DOM.
Some docs can be had here:
http://www.w3schools.com/jsref/met_doc_getelementbyid.asp
https://developer.mozilla.org/en-US/docs/DOM/document.getElementById
If you were to use a toolkit like jQuery you could do something like:
$('div[id^="movie-con"]').hide(); // hide everything
$('div["movie-con"' + index + "]").show(); // show the one we want
Since you're not using jQuery it's not quite as easy. Something like:
var matches = document.getElementById("containerDiv").getElementsByTagName("img");
for( var i = 0; i < matches.length; i++ )
matches[i].style.display = "none";
document.getElementById('movie-con' + index).style.display = "block";

AJAX: Applying effect to CSS class

I have a snippet of code that applies a highlighting effect to list items in a menu (due to the fact that the menu items are just POST), to give users feedback. I have created a second step to the menu and would like to apply it to any element with a class of .highlight. Can't get it to work though, here's my current code:
[deleted old code]
The obvious work-around is to create a new id (say, '#highlighter2) and just copy and paste the code. But I'm curious if there's a more efficient way to apply the effect to a class instead of ID?
UPDATE (here is my updated code):
The script above DOES work on the first ul. The second ul, which appears via jquery (perhaps that's the issue, it's initially set to hidden). Here's relevant HTML (sort of a lot to understand, but note the hidden second div. I think this might be the culprit. Like I said, first list works flawlessly, highlights and all. But the second list does nothing.)?
//Do something when the DOM is ready:
<script type="text/javascript">
$(document).ready(function() {
$('#foo li, #foo2 li').click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
//When a link in div is clicked, do something:
$('#selectCompany a').click(function() {
//Fade in second box:
//Get id from clicked link:
var id = $(this).attr('id');
$.ajax({
type: 'POST',
url: 'getFileInfo.php',
data: {'id': id},
success: function(msg){
//everything echoed in your PHP-File will be in the 'msg' variable:
$('#selectCompanyUser').html(msg)
$('#selectCompanyUser').fadeIn(400);
}
});
});
});
</script>
<div id="selectCompany" class="panelNormal">
<ul id="foo">
<?
// see if any rows were returned
if (mysql_num_rows($membersresult) > 0) {
// yes
// print them one after another
while($row = mysql_fetch_object($membersresult)) {
echo "<li>"."".$row->company.""."</li>";
}
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($membersresult);
// close connection
mysql_close($link);
?>
</ul>
</div>
<!-- Second Box: initially hidden with CSS "display: none;" -->
<div id="selectCompanyUser" class="panelNormal" style="display: none;">
<div class="splitter"></div>
</div>
You could just create #highlighter2 and make your code block into a function that takes the ID value and then just call it twice:
function hookupHighlight(id) {
var context = document.getElementById(id);
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
}
hookupHighlight("highliter1");
hookupHighlight("highliter2");
jQuery would make this easier in a lot of ways as that entire block would collapse to this:
$("#highlighter1 li, #highlighter2 li").click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
If any of the objects you want to click on are not initially present when you run this jQuery code, then you would have to use this instead:
$("#highlighter1 li, #highlighter2 li").live("click", function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
change the replace in /highlight/ig, it works on http://jsfiddle.net/8RArn/
var context = document.getElementById('highlighter');
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/highlight/ig, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
So all those guys that are saying just use jQuery are handing out bad advice. It might be a quick fix for now, but its no replacement for actually learning Javascript.
There is a very powerful feature in Javascript called closures that will solve this problem for you in a jiffy:
var addTheListeners = function (its) {
var itemPtr;
var listener = function () {
// do AJAX stuff
// just need to visit one item now
if (itemPtr) {
var classname = itemPtr.className;
itemPtr.className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
itemPtr = this;
}
for (var i = 0; i < its.length; i++) {
its[i].addEventListener ('click', listener, false);
}
}
and then:
var context = document.getElementById ('highlighter');
var items = context.getElementsByTagName ('li');
addTheListeners (items);
And you can call add the listeners for distinct sets of doc elements as many times as you want.
addTheListeners works by defining one var to store the list's currently selected item each time it is called and then all of the listener functions defined below it have shared access to this variable even after addTheListeners has returned (this is the closure part).
This code is also much more efficient than yours for two reasons:
You no longer iterate through all the items just to remove a class from one of them
You aren't defining functions inside of a for loop (you should never do this, not only for efficiency reasons but one day you are going to be tempted to use that i variable and its going to cause you some problems because of the closures thing I mentioned above)

Loop through javascript improvement

The below code works properly, but it is hard coded. I would like to be able to create an array of field sets, hide those fields, then each time I click on the "#createEventForm-eventInformation-addElement" button it displays the next one. The problem with the below code is that it is hard coded and thus would break easily and be much larger than using loops. Can someone help me make this better.
$("#fieldset-group1").hide();
$("#fieldset-group2").hide();
$("#fieldset-group3").hide();
$("#fieldset-group4").hide();
$("#fieldset-group5").hide();
$("#fieldset-group6").hide();
$("#fieldset-group7").hide();
$("#fieldset-group8").hide();
$("#fieldset-group9").hide();
$("#createEventForm-eventInformation-addElement").click(
function() {
ajaxAddEventInformation();
if($("#fieldset-group1").is(":hidden"))
{
$("#fieldset-group1").show();
}
else
{
$("#fieldset-group2").show();
}
}
);
You should use the ^= notation of the jquery selectors which means starting with ..
// this will hide all of your fieldset groups
$('[id^="fieldset-group"]').hide();
Then
$("#createEventForm-eventInformation-addElement").click(
function() {
ajaxAddEventInformation();
// find the visible one (current)
var current = $('[id^="fieldset-group"]:visible');
// find its index
var index = $('[id^="fieldset-group"]').index( current );
// hide the current one
current.hide();
// show the next one
$('[id^="fieldset-group"]').eq(index+1).show();
}
);
A quick idea.
Add a class to each fieldset lets say "hiddenfields". Declare a global variable to keep track of which field is shown.
$(".hiddenfields").hide();//hide all
var num = 0;//none shown
$("#createEventForm-eventInformation-addElement").click(
function() {
ajaxAddEventInformation();
num++;
$("#fieldset-group" + num).show();
}
);
Here is one simple solution.
var index = 0;
var fieldsets = [
$("#fieldset-group1").show(),
$("#fieldset-group2"),
$("#fieldset-group3"),
$("#fieldset-group4"),
$("#fieldset-group5"),
$("#fieldset-group6"),
$("#fieldset-group7"),
$("#fieldset-group8"),
$("#fieldset-group9")
];
$("#createEventForm-eventInformation-addElement").click(function() {
ajaxAddEventInformation();
fieldsets[index++].hide();
if (index < fieldsets.length) {
fieldsets[index].show();
}
else {
index = 0;
fieldsets[index].show();
}
});
Add a class 'fieldset' to all fieldsets, then:
$("#createEventForm-eventInformation-addElement").click(
function() {
ajaxAddEventInformation();
$('.fieldset').is(':visible')
.next().show().end()
.hide();
}
);
This will show the first hidden fieldset element whose ID attribute starts with "fieldset-group"...
$("fieldset[id^='fieldset-group']:hidden:first").show();
How about to add (or only use) a class for that fields?
$(".fieldset").hide(); // hides every element with class fieldset
$("#createEventForm-eventInformation-addElement").click( function() {
ajaxAddEventInformation();
// I assume that all fieldset elements are in one container #parentdiv
// gets the first of all remaining hidden fieldsets and shows it
$('#parentdiv').find('.fieldsset:hidden:first').show();
});

Categories

Resources