I'm working on a JavaScript and CSS3 responsive HTML menu for mobile devices. The JS is quite simple. It gets the element ID of the body and if the menu is closed it changes class to menuOpen, and vice versa. The CSS handles all the transitions and displays of the actual menu.
<script type="text/javascript">
function menuButton() {
if( document.getElementById("htmlbody").className == "menuClosed" ) {
document.getElementById("htmlbody").className = "menuOpen";
} else {
document.getElementById("htmlbody").className = "menuClosed";
}
}
</script>
The problem I ran into is this. Instead of removing the entire class and replacing the entire class, how can I remove a single class and then add a single class?
Since an element will contain multiple classes I need to change the condition to see IF it contains the className, as opposed to being the actual className. I believe I can simply add the new class by doing the JS below but I don't know how to remove the old class.
document.getElementById("htmlbody").className += "menuOpen";
Hope I've provided enough details.
EDIT
Thanks for help. I've managed to get this to work great. Does just what I need to.
<script type="text/javascript">
function menuButton() {
var htmlBody = document.getElementById("htmlbody");
if( htmlBody.classList.contains("menuClosed") ) {
htmlBody.className = htmlBody.className.replace(/(?:^|\s)menuClosed(?=\s|$)/g, " menuOpen");
} else {
htmlBody.className = htmlBody.className.replace(/(?:^|\s)menuOpen(?=\s|$)/g, " menuClosed");
}
}
</script>
I use regular expressions for this. I have a macro that does the following
(node).className = (node).className.replace(/(?:^|\s)cl(?=\s|$)/g,"");
where cl is replaced with the actual class to be removed. That handles the remove. You can code your insert as you said but be aware your className can grow as you get duplicate class invocations.
Function Definition
Node.prototype.hasClass = function (className) {
if (this.classList) {
return this.classList.contains(className);
} else {
return (-1 < this.className.indexOf(className));
}
};
Node.prototype.addClass = function (className) {
if (this.classList) {
this.classList.add(className);
} else if (!this.hasClass(className)) {
var classes = this.className.split(" ");
classes.push(className);
this.className = classes.join(" ");
}
return this;
};
Node.prototype.removeClass = function (className) {
if (this.classList) {
this.classList.remove(className);
} else {
var classes = this.className.split(" ");
classes.splice(classes.indexOf(className), 1);
this.className = classes.join(" ");
}
return this;
};
Usage :
myElement = document.getElementById("htmlbody");
Now you can call myElement.removeClass('myClass')
or chain it: myElement.removeClass("oldClass").addClass("newClass");
This will remove a single class
function menuButton() {
var htmlBody = document.getElementById('htmlbody');
if( htmlBody.className.test(/menuClosed/)) {
htmlBody.className.replace('menuClosed', 'menuOpen');
} else if (htmlBody.className.test(/menuOpen/)){
htmlBody.className.replace('menuOpen', 'menuClosed');;
}
}
Use the String.replace function. And i sugest to use class names as -. Not camel cased as is not the standard, but it depends of your team.
Related
I am trying to convert a small script from javascript to jquery, but I don't know where I should be putting the [i] in jquery?. I am nearly there, I just need someone to point out where I have gone wrong.
This script expands a search input when focused, if the input contains any values, it retains it's expanded state, or else if the entry is removed and clicks elsewhere, it will snap back.
Here is the javascript:
const searchInput = document.querySelectorAll('.search');
for (i = 0; i < searchInput.length; ++i) {
searchInput[i].addEventListener("change", function() {
if(this.value == '') {
this.classList.remove('not-empty')
} else {
this.classList.add('not-empty')
}
});
}
and converting to jquery:
var $searchInput = $(".search");
for (i = 0; i < $searchInput.length; ++i) {
$searchInput.on("change", function () {
if ($(this).value == "") {
$(this).removeClass("not-empty");
} else {
$(this).addClass("not-empty");
}
});
}
Note the key benefit of jQuery that it works on collections of elements: methods such as .on automatically loop over the collection, so you don't need any more than this:
$('.search').on("change", function() {
this.classList.toggle('not-empty', this.value != "");
});
This adds a change event listener for each of the .search elements. I've used classList.toggle as it accepts a second argument telling it whether to add or remove the class, so the if statement isn't needed either.
I'm trying to write a function, to make a visual object come on and off, on and off, as the user clicks on it. Then add a click event listener in the class, called button btn-sauce.
So far my code doesn't work :
function renderWhiteSauce() {
if (element.classList) {
element.classList.toggle("btn-sauce.active");
} else {
var classes = element.className.split(" ");
var i = classes.indexOf("btn-sauce.active");
if (i >= 0)
classes.splice(i, 1);
else
classes.push("btn-sauce.active");
element.className = classes.join(" ");
}
document.querySelector('.btn-sauce.active').addEventListener('click', () => {
state.sauce = !state.sauce;
renderEverything();
});
You can just add and remove classes with methods classList.add('classname') and classList.remove('classname'). Define class which makes btn active and just add or remove it.
const elem = document.querySelector('.btn-sauce')
elem.addEventListener('click', () => {
if(elem.className.indexOf('className') < 0) {
elem.classList.add('className')
} else {
elem.classList.remove('className')
}
});
btn-sauce and active are two separate classes, but you are writing your code like they are one. Remove btn-sauce. (including the dot) from everything above the querySelector line and you will be able to toggle the active class on and off.
If the element is not "active" to begin with, you should also change document.querySelector('.btn-sauce.active') to document.querySelector('.btn-sauce').
One last note, you are calling renderEverything() in your click handler, which I assume is another function that calls renderWhiteSauce(), but I thought I'd mention it in case this was just a typo and they were meant to be the same function.
I want to implement conditions where if I have a group of html elements with text that if they have 4 text characters I want to add a class. Here is my code which does not work:
var attributeIcons = dojo.query(".attribute-icon");
Array.prototype.forEach.call(attributeIcons, function(el) {
if (el.innerText.length === 4) {
return domClass.add(attributeIcons, "new-class");
}
});
What am I doing wrong?
You need to add the class to el:
return domClass.add(el, "new-class");
You could also change your code slightly more:
var attributeIcons = dojo.query(".attribute-icon");
Array.prototype.forEach.call(attributeIcons, function(el) {
if (el.innerText.trim().length == 4) {
return el.addClass("new-class");
}
});
Instead of return just add a class using classList.add
var attributeIcons = dojo.query(".attribute-icon");
Array.prototype.forEach.call(attributeIcons, function(el) {
if (el.innerText.trim().length === 4) {
return el.classList.add("new-class");
}
});
I've written some code which enables and disables input and select fields on a series of forms. I have repeated a lot of code and I wanted to know how I would write this in a DRY, scalable way.
I created a Fiddle and and it repeats three times - edit, cancel and save.
$(edit).each(function(){
$(this).on('click', function(){ }); });
Here is my fiddle.
https://jsfiddle.net/tellmehow/5tcs6f82/9/
I will keep working on this, but if anyone has any pointers or a similar Fiddle, please let me know. Thanks.
You could reduce your repetition of hide/show/disable etc, by putting your form/button manipulation into a single function like this:
function setFormMode($form, mode){
switch(mode){
case 'view':
$form.find('.save-button, .cancel-button').hide();
$form.find('.edit-button').show();
$form.find("input, select").prop("disabled", true);
break;
case 'edit':
$form.find('.save-button, .cancel-button').show();
$form.find('.edit-button').hide();
$form.find("input, select").prop("disabled", false);
break;
}
}
Create three "onclick" functions (because, presumably you'll want to do other things as well):
function edit_onclick(){
setFormMode($(this).closest("form"), 'edit');
}
function cancel_onclick(){
setFormMode($(this).closest("form"), 'view');
//TODO: Undo changes?
}
function save_onclick(){
setFormMode($(this).closest("form"), 'view');
//TODO: Send data to server?
}
And then bind:
$('.save-button').on('click', save_onclick);
$('.cancel-button').on('click', cancel_onclick);
$('.edit-button').on('click', edit_onclick);
Fiddle: https://jsfiddle.net/5tcs6f82/10/
You can also do something like the following. It doesn't use jQuery but demonstrates an alternative approach that adds or removes a class called hidden to hide or show the buttons. It also uses event delegation to reduce the number of listeners.
.hidden {
display: none;
}
The following is the main function, it can be more concise if a toggle class function is used.
/* If click is from element with class edit-button, hide it and show
* buttons with class cancel-button or save-button.
* If click is from element with class cancel-button or save-button,
* hide them and show button with class edit-button
*/
function toggleButtons(event) {
var form = this;
var target = event.target;
// If the click came from a button with class edit-button, hide it and
// show the cancel and save buttons, otherwise show the edit button and
// hide the cancel and show buttons
if (hasClass(target, ['cancel-button','save-button','edit-button'])) {
var buttons = form.querySelectorAll('.cancel-button, .save-button, .edit-button');
Array.prototype.forEach.call(buttons, function(el) {
if (hasClass(el, 'hidden')) {
removeClass(el, 'hidden');
} else {
addClass(el, 'hidden');
}
});
}
}
Instead of adding a listener to each element, just add one listener to each form:
window.onload = function() {
for (var forms=document.forms, i=0, iLen=forms.length; i<iLen; i++) {
// Add listener to each form
forms[i].addEventListener('click', toggleButtons, false);
// Hide the cancel and save buttons
Array.prototype.forEach.call(forms[i].querySelectorAll('.cancel-button, .save-button'),
function(el){addClass(el, 'hidden')}
);
}
}
Some library functions that replace equivalent jQuery
// Return true if el has class className
// If className is an array, return true if el has any class in className
function hasClass(el, className) {
if (typeof className == 'string') {
className = [className];
}
var re = new RegExp('(^|\\s+)(' + className.join('|') + ')(\\s+|$)');
return re.test(el.className);
}
// Add class className to el
function addClass(el, className) {
var classes;
if (!hasClass(el, className)) {
classes = el.className.match(/\S+/g) || [];
classes.push(className);
el.className = classes.join(' ');
}
}
// Remove class className from el
function removeClass(el, className) {
var re;
if (hasClass(el, className)) {
var re = new RegExp('(^|\\s+)' + className + '(\\s+|$)','g');
classes = el.className.replace(re, ' ').match(/\S+/g);
el.className = classes.join(' ');
}
}
I have a class js-bootstrap3 that is generated by a cms. What I need to do is check if the containing element just has js-bootstrap3and .unwrap() the contents, but if the element has multiple classes which include js-bootstrap3 then I'm trying to just remove that class.
jsFiddle
$('.jsn-bootstrap3').each(function(){
if( $(this).attr('class') !== undefined && $(this).attr('class').match(/jsn-bootstrap3/) ) {
console.log("match");
$(this).contents().unwrap();
$(this).removeClass('jsn-bootstrap3');
}
});
This just seems to detect any element with js-bootstrap3as a class and unwraps it.
this.className is a string with all of the classes for the element (space delimited), so if it's not just "jsn-bootstrap3" you know it has more than one class:
$('.jsn-bootstrap3').each(function(){
if( $.trim(this.className) !== "jsn-bootstrap3") {
// Just jsn-bootstrap3
$(this).contents().unwrap();
} else {
// More than just jsn-bootstarp3
$(this).removeClass('jsn-bootstrap3');
}
});
Dependeing on the browsers you need to support element.classlist (IE10+) might or might not be what you need.
classList returns a token list of the class attribute of the element.
classList is a convenient alternative to accessing an element's list of classes as a space-delimited string via element.className. It contains the following methods:
Otherwise you're looking at splitting the className into an array like so to count the values:
var classes = element.className.split(" ");
Building on your example you could do something liket his:
$('.jsn-bootstrap3').each(function(i, el){
if( el.className.indexOf('jsn-bootstrap3') != -1 ) {
console.log("match");
if ( el.className.split(" ").length > 1 ) {
$(this).removeClass('jsn-bootstrap3');
} else {
$(this).contents().unwrap();
}
}
});
Try this code.
$(document).ready(function(){
$('.jsn-bootstrap3').each(function(){
var classes = $(this).attr('class');
var new_cls = classes.split(' ');
if( new_cls.length > 1 ){
$(this).removeClass('jsn-bootstrap3');
} else {
$(this).contents().unwrap();
}
});
});