Performing the same operation on multiple selectors, elegantly? - javascript

On the project I'm working on, I've been writing a little JavaScript object. One of its behaviors involve removing any children in a series of classes, as such:
function Foo () {
var $a = $('.a'),
$b = $('.b'),
// ...etc...
$n = $('.n');
$a.remove();
$b.remove();
// ...etc again...
$n.remove();
}
While there are some ways to revise this to be more easily maintainable (putting the selectors into a single array springs instantly to mind), I'm wondering if there's any elegant way to perform the .remove() operation on a series of selectors, as such:
function FooPrime() {
var selectors = [
'.a',
'.b',
// ...
'.n'
];
// Perform remove on all given selectors?
$(selectors).remove();
}
Thus, Question: What are some ways I could perform a single jQuery operation on a number of selectors?
EDIT: here is a JSFiddle to show a cut-down version of the problem context.

You can separate the selectors with commas:
$(selectors.join(',')).remove();
The comma has that purpose in straight ordinary CSS selector syntax.

Thanks for showing your DOM, you should avoid making big lists of classes to select when you can add multiple classes to elements and create a specific class for the elements you want to target... or target via association to other elements. This would be a more clean and efficient way to do it.
By association
Basically for the selector I just have this:
$("#test-callout").find("div").not(".callout-main").remove();
Fiddle
This assumes that you do not have any other div's besides .callout-main and the target div in test-callout. If you do you can modify the selector chain a bit to compensate.
By adding another class
Your arrow creation code was like this:
function calculateArrow() {
var arrowClass;
if(pub.isLeft) {
arrowClass = 'callout-arrow-left';
} else {
arrowClass = 'callout-arrow-right';
}
pub.$callout.append('<div class="' + arrowClass + '"></div>');
}
Modify it to be like this:
function calculateArrow() {
$arrow = $("<div>")
.addClass("callout-arrow")
.addClass(pub.isLeft ? "callout-arrow-left" : "callout-arrow-right");
pub.$callout.append($arrow);
}
Then your selector can be simply:
$("#test-callout").find(".callout-arrow").remove();
Fiddle
If you are interested in a complete refactor - I reduced your CalloutObject from 53 to 17 lines and it still works the same.
Fiddle

Related

Concisely removing a DOM element if it exists?

I'm currently writing a browser script (for use in Tampermonkey/Greasemonkey) (and learning JS in the process!), and I have a lot of occurrences like this:
let btn = document.querySelector("#unwantedButton");
if (btn) {
btn.parentNode.removeChild(btn);
}
That is, I want a certain element to be removed, if it exists.
Is there any way to write this more concisely and elegantly? That would save me a lot of lines in this project, and most importantly, teach me some JS best practice. :-)
Thank you for helping a JS noob!
Rather than rewrite the code in several places, you can put it in a function (and use remove():
function removeIfExists (selector) {
var x = document.querySelector(selector)
if (x) x.remove()
}
Then you can call it all over the place:
removeIfExists("#some-unwanted-element")
You can also use it if you have a list of known elements you don't want:
var badElements = [ ".foo", "#bar", "#etc" ]
badElements.forEach(removeIfExists)
You can make it as a function for multiple elements
const removeElements = refs => document.querySelectorAll(refs)
.forEach(el=>{if(!!el) el.remove()})
usage :
removeElements('#unwantedButton')
or
removeElements('.foo, #bar, #etc')

jQuery: Remove and Re-Add Classes

I've searched quite a bit before asking this question, but I may be searching / asking the wrong question:
I want to select the last two classes of an element (that has multiple classes and an unknown amount of classes) and store that in a variable. I then want to remove those two classes and add them back at a later point (like toggleClass). The first class is known, while the second class is unknown.
For instance:
<div class="c1 c2 c3 c-unknown"></div>
I would like to select c3 and c-unknown
I've tried split() and it seems like the solution, but I couldn't quite get it to work.
I appreciate any help / guidance you can offer.
You could store them on the element itself allowing to isolate multiple instances
Following solution doesn't need classes to be in any specific order or matter how many classes are on element.
$('.c1').each(function () {
/* array of classes on elemnt*/
var classes = $(this).attr('class').split(' ');
/* remove the targeted selector from array */
classes.splice(classes.indexOf('c1'), 1);
/* remove the extra classes from element and store */
$(this).removeClass(classes.join(' ')).data('classes', classes);
});
For a specific element could also use attr(function)
$('.c1.Specific').attr('class', function(_, existingClasses){
var classes = existingClasses.split(' ');
classes.splice(classes.indexOf('c1', 1));
$(this).data('classes', classes);
return 'c1';
});
Then to re-use the stored classes
var $el = $('.c1:first');
$el.addClass( $el.data('classes').join(' '))
DEMO
Probably the easiest way to get the last two classes:
var lastTwoClasses = $('.c1').attr('class').split(/\s+/).slice(-2).join(' ');
Toggle (for example with button id="btn"):
$('#btn').click(function(){
$('.c1').toggleClass(lastTwoClasses);
});
JSFiddle
EDIT.
And yet another way:
$('.c1').each(function(){
var classes = $(this).attr('class').split(/\s+/).slice(-2).join(' ');
$(this).click(function(){
$(this).toggleClass(classes);
});
});
JSFiddle
I believe this is what you're trying to do.
var ele = document.getElementsByClassName('c1'),
classes = ele[0].className,
classArr = classes.split(' '),
spliceIndex = classArr.length - 2,
last2Classes = classArr.splice(spliceIndex, 2);
Here's a working fiddle
If you're trying to remove the classes you can use jquery or you could just use the dom element's className property and set it to whichever array has the classes you want to use. You would use the array method .toString() on either array and it will give you a string representation of the classes.
The answer here could help you to get part of the functionality you need:
Get first and last class with jQuery
Starting with that, you could use split, and removeClass like this
(sample text and css added for demo purposes):
function removeTheClasses(el) {
var classes = el.attr("class").split(/\s/);
var second_to_last = classes[classes.length - 2]; //second to last class
var last = classes[classes.length -1]; //last class
el.removeClass(second_to_last, last);
}
$('button').click(function() {
removeTheClasses($('.c1'));
});
.c-unknown {font-weight:bold}
.c3 {color:pink}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div class="c1 c2 c3 c-unknown">
ABC
</div>
<br/>
<button>Remove the last two classes</button>
In order to add these same classes back in (toggling), you'd have to keep a record of the recently removed classes. This would require:
pushing the values of the first & last variables to a cookie or web storage (if handling multiple elements at a time), OR
a single javascript variable above the scope of the 'removeTheClasses' function (if you're just handling one element at a time).

Can I make a compound selector including this in jquery?

I have a function that uses each to go over each element in a set and renumber them after one is removed from the DOM.
Right now that function looks like this:
renumber_items = function(){
$(".item_set").each(function(index){
$(this).find('legend').html("Item " + (index+1));
});
};
I remember reading somewhere that find is a really inefficient operation, so I was wondering if there's a way to combine the 'legend' selector into a compound selector with this.
If there is only one legend per .item_set this will abbreviate things a bit:
renumber_items = function(){
$(".item_set legend").html(function(index){
return "Item " + (index+1);
});
};
.html can take a function and the result is stored.
If there is more than one legend per .item_set you will need to retain an outer each to keep the numbers sequential for each set.
Generally if you have speed issues, on a function called many times, and the jQuery selector result is on a fixed set of elements, you just archive the search to a variable once at page load and reuse that:
var $legends = $(".item_set legend");
renumber_items = function(){
$legends.html(function(index){
return "Item " + (index+1);
});
};
Maybe you can try with .filter(). As others say, it shouldn't be such a performance issue as long as you're not using it all the time. Consider labeling all the items you want to find/filter, so that you can get them all in one JQuery selector at once, and you don't have to go filtering everything after. Else you can use (as commented out by #Regent):
renumber_items = function(){
$(".item_set legend").each(function(index){
$(this).html("Item " + (index+1));
});
};
You can replace:
$(this).find('legend')
with:
$('legend', this)
The second argument sets the context in which jQuery searches for 'legend'.
If it is omitted, the context defaults to be document.

How can I make it so that more than one ID will trigger using jQuery?

I have the following:
$('#editMenu', '#createContent', '#editContent')
.click(function () {
var $link = $(this);
if ($link.attr('data-disabled') === 'no') {
$link.attr('data-disabled', 'yes');
adminDialog($link);
}
return false;
});
However it seems like clicking on any of these does not work. Am I setting it up correctly?
What you are trying is multiple selector which should be written as a single string with comma separated selector. See below,
Change
$('#editMenu', '#createContent', '#editContent')
to
$('#editMenu, #createContent, #editContent')
jQuery allows for multiple selectors like so:
jQuery('selector1, selector2, selectorN')
so you would need:
$('#editMenu, #createContent, #editContent')
You can specify any number of selectors to combine into a single
result. This multiple expression combinator is an efficient way to
select disparate elements. The order of the DOM elements in the
returned jQuery object may not be identical, as they will be in
document order. An alternative to this combinator is the .add()
method.

Check if objects within a variable are also contained in another variable (jQuery)

I hope the title somewhat sums up what I am trying to achieve.
Let's say I have two variables:
var one = $('div.foo, div.faa, div.fee, span, a, .all-sorts-of-objects'),
two = $('div.fii, div.foo, span, .all-sorts-of-objects-two');
And now, I want to check if objects contained within two are also contained within one. In other words, if there are any objects within both variables.
I need this so that I can set-up a non-overriding hover function (i.e. because I'm adding inline color-styles, I need to target my objects wisely). This is the logic I came up with: (note the if(one == two) which is essentially my question).
one.hover(function() {
if(one == two) { // if there is one or more objects in both variables..
$(this).css('color', 'red');
} else {
$(this).css('color', 'blue');
}
}, function() {
// ...
});
I hope I have been clear enough. If not, please let me know and I will do my best to explain this better.
Thank you very much!
Here is a quick and dirty way to do it:
var one = $('div.foo, div.faa, div.fee, span, a, .all-sorts-of-objects').addClass('one');
var two = $('div.fii, div.foo, span, .all-sorts-of-objects-two').addClass('two');
one.hover(function() {
if(one.hasClass('two')) {
$(this.css('color', 'red');
}
});
In my book the cleanest way (apart from refactoring to avoid these duplicate selectors if possible) would be to use the merge and unique jQuery utilities, something like:
var $merged = $.merge(one, two);
var $unique = $.unique($merged.get());
The $unique now has the unique, or, if you like, distinct elements.

Categories

Resources