How to minimize/colocate similar DOM selector strings in jQuery code? - javascript

Code style advice, please:
I want to prevent the rampant distribution of selector strings, especially similar bases, throughout my code.
function fn1() {
$("#formId ul.sectionClass li.statusFlag").doSomething();
$("#formId ul.sectionClass li.otherStatusFlag").doSomeOtherThing();
doSomethingToGroup("#formId ul.sectionClass");
doSomethingToOtherGroup("#formId ul.otherSectionClass");
}
function doSomethingToGroup(selector) {
$("#formId>.statusBar").html(summarize(selector));
$(selector).doMore();
}
function classesLikeIds() {
$("#formId .item1").doOneThing();
$("#formId .item2").doAnotherThing();
}
...etc.
Functionally, I'm comfortable my code is fairly DRY. Divisions of responsibility are healthy, etc. But I still have selector strings scattered throughout my code that are difficult to maintain and causing defects.
Possible solution:
I've thought about something as simple as a named array of selectors:
AppName.Selectors = {
form: "#formId",
statusBar: "#formId .statusBar",
activeItems: "#formId ul.sectionClass li.statusFlag",
inactiveItems: "#formId ul.sectionClass li.otherStatusFlag"
}
That seems more maintainable, and a javascript compiler could alert me to many more problems. I still feel like it's pretty weak, though. If you do this, but have an object model that makes it more intuitive or supports child relations, please post it as a solution.
Maybe my style is part of the problem:
Maybe it is bad or controversial, but I try to minimize unique IDs in my HTML, even sometimes using classes like IDs (beneath top-level element IDs). For example:
//I'll use
$("#appName form .header")
//Rather than
$("#appNameHeader")
Why? If an app has 100 IDs in it, bad stuff happens in my experience. Two quick examples: 1) mashing-up apps becomes fraught with name collision danger, 2) it is harder to intuit the impact of style changes on child elements.
What do you do?
Thanks,
Shannon

I'd suggest that you store the result of the selector in order to be more efficient.
Elements = {
form: $("#formId"),
statusBar: Elements.form.find(".statusBar"),
sectionClass: Elements.form.find("ul.sectionClass"),
activeItems: Elements.sectionClass.find("li.statusFlag"),
inactiveItems: Elements.sectionClass.find("li.otherStatusFlag")
};
thus you'll reuse the selector results, which results in better performance. But this may not work if you've elements that are being added in the DOM later and match these selectors.
If you go with the above approach, you might've to change your methods/functions to expect an array of elements rather than a string.
For eg.,
function doSomethingToGroup(elems) {
Elements.statusBar.html(summarize(elems.selector));
elems.doMore();
}
You can always get the selector string from the cached results using the selector method.
Elements.statusBar.selector returns the selector #formId .statusBar

Related

How to stay DRY in jQuery?

In jQuery is there a way of saving commonly-used function chains for referencing later, to maintain a DRY coding style?
For example in the following code I'd like to reference the chain .closest('.row').closest('div').remove();:
$('#search-results .row .unsafe').closest('.row').closest('div').remove();
$('#search-results .warnings').closest('.row').closest('div').remove();
$('#search-results .textresults1:contains("Endpoint offline!")').closest('.row').closest('div').remove();
// lots more distinct selectors
Something like this:
var bye = ".closest('.row').closest('div').remove()";
$('#search-results .row .unsafe').bye();
$('#search-results .warnings').bye();
$('#search-results .textresults1:contains("Endpoint offline!")').bye();
// more...
My question solely pertains to reusing chains of functions in this manner - i.e. whether jQuery has a way of executing a function-chain stored as a string in a variable. I appreciate that any example of code could be rewritten to avoid the need for this, but that's a different question. I think there could be quite a few use cases for storing commonly-used function chains like this - e.g. for complex styling/animation routines perhaps.
[I know that the first selector could be modified to encompass all of the subsequent ones, but it is done this way for performance. #search-results contains tens of thousands of <div>'s, each with a dozen elements inside of them. This way the fuzzy searches are done last (i.e. loose text matching with :contains), after the previous selectors have already killed off a few thousand rows. Also, this particular code is taken from a Greasemonkey userscript I'm writing, so I do not have the option of removing these spurious results straight at the source, server-side - it's not my server/website]
Basic encapsulation will do it perfectly:
function removeStuff(selector) {
return $(selector)
.closest('.row')
.closest('div')
.remove();
}
exported from #Heretic Monkey's now deleted comment.
By Using a simple plugin you can use it like .bye()
(function ( $ ) {
$.fn.bye = function() {
$(this).remove();
//$(this).closest('.row').closest('div').remove();
}
}( jQuery));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>click</button>
<script>
$(document).ready(function(){
$('button').bye();
});
</script>

jQuery performance: Chain appends or append multiple arguments?

I was wondering which of those is more performant, and more importantly why?
$('a').append(
$('<b></b>').text('1'),
$('<b></b>').text('2'),
$('<b></b>').text('3'));
and
$('a')
.append($('<b></b>').text('1'))
.append($('<b></b>').text('2'))
.append($('<b></b>').text('3'));
where a and <b> are an arbitrary selector and tag. As far as I was able to tell from trying them out, they both function in exactly the same manner.
Relevant question: What is the best way to add options to a select from as a JS object with jQuery?
Edit:
The reason I was asking this question was to know whether I should structure appends as:
texts = ['1','2','3','4','5'];
$a = $('a');
$a.append(...texts.map(function(o){ return $('<b></b>').text(o); }));
or as:
texts = ['1','2','3','4','5'];
$a = $('a');
for (o in texts) {
$a.append($('<b></b>').text(o));
}
The latter one is more intuitive, and I believe most programmers would choose to write it, when the earlier one performs better.
You can use a benchmarking tool to test this. I used JSPerf: https://jsperf.com/so-question
The results show that the first case is much faster. I believe it is because that jQuery does a lot of initialization, checking, and compatibility work every time you call a function that works with an element, that is, .append().
The first case only calls .append() once, whereas the second case calls .append() three times. In general, the less function calls you make, the faster your code will be.

jQuery methods Vs jQuery selectors

I was recently assigned a very small but complex task in jQuery, the requirement was quite simple, given the following HTML :
<div>
<span id="myid2151511" class="myclass23462362">....foobar....</span>
<span id="myid2151512" class="myclass23462362">....YoLO....</span>
<span id="myid2151513" class="myclass23462362">....lalal....</span>
<span id="myid2151514" class="myclass23462362">....foobar....</span>
</div>
What i have to do i recursively go through all the span under div, With a certain id and check if the values contained in the spans is foobar, So i can up with the following jQuery code:
$(function(){
$('div [id^="myid"]:contains("foobar"):last').css({'background' : 'rgb(227, 216, 22)' })
});
FIDDLE HERE
Its quite a complex bit of code by itself, but the jQuery documentation made it a cakewalk for me as for as understanding the code is concerned.
By now i am comfortable writing code like so in jQuery:
$('some-Element').somemethod().anothermethod().yetanothermethod();
Every function returns a value in the above jQuery statement, so chain ability becomes a reality.
but when i see code like so.
$('div [id^="myid"]:contains("foobar"):last').css({'background' : 'rgb(227, 216, 22)' });
I am thrown a bit off the hook(although i managed to write the above line myself), notice how alot of the filtering is done by a selector :last and :contains, to me they appear to be working much like some kind of a jQuery method. So my question is, how do these selectors in jQuery work in comparison to jQuery methods ?
If anybody could explain or give me a vague idea, it would be Fantastic.
EDIT ::
well to clarify my question in one line, to me $(".someClass").eq('10'); makes sense, but somehow $(".someClass:eq(10)") does't , i mean it works, but how on earth is it implemented internally ?(I wrote this edit after reading the answers below, and well this question has been thoroughly answered by now, but this edit is just to clarify my question.).
That's an interesting question. The short answer is they both accomplish the same thing. Of course though, there's always more to the story. In general:
$('div [id^="myid"]:contains("foobar"):last').css({'background' : 'rgb(227, 216, 22)' });
Is equivalent to:
$("div").find("[id^='myid']").filter(":contains('foobar')").last().css({'background' : 'rgb(227, 216, 22)' });
Most of the time when you call $(), jQuery is calling document.querySelectorAll(). This is a browser implemented function that grabs elements based on a selector. That complex string you create is passed to this method and the elements are returned.
Naturally, things implemented by the browser are faster than JavaScript so the less JavaScript and more C++, the better. As a result, your example passing everything as a selector is likely to be faster as it just sends it all to the browser as one call and tells it "do it." Calling $(), contains(), last() on the other hand is going to call querySelectorAll multiple times and therefore it will likely be slower since we're doing more JavaScript as opposed to letting the browser do the heavy lifting in one shot. There are exceptions though. JQuery generally calls querySelectorAll. However, there are times when it doesn't. This is because jQuery extends what querySelectorAll is capable of.
For example, if you do something like $(".someClass:eq(10)") per the jQuery documentation:
jQuery has extended the CSS3 selectors with the following selectors. Because these selectors are jQuery extension and not part of the CSS specification, queries using them cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using these selectors, first select some elements using a pure CSS selector, then use .filter().
So in that case, while $(".someClass:eq(10)") might seem to be faster, in reality $(".someClass").eq(10) or $(".someClass").filter(":eq(10)") is going to be faster since the first call will be executed as JavaScript code. The latter two will first call querySelectorAll to select by class, then only use JavaScript to find the 10th element. When jQuery has to do the selection in pure JavaScript, it does it using the Sizzle engine which is fast, very fast, but not faster than native code in the browser. So again, the short answer is, they're the same thing, the long answer is, it depends. If you're interested in all the extensions that fall into that category, the link to the jQuery documentation I included lists them.
First of all, yes nikhil was right. ID is unique identifier and can be only used once. If you are willing to apply same styles to several elements, or you to use it to select several elements together use class attribute. But however, i couldn't understand your question. But maybe this could help
there is function in javascript which is widely supported by almost all major browsers
document.querySelectorAll("div [id^=myId]");
in fact you could write your own library (well not as advanced one like jquery but)
var $ = function(selector){
return document.querySelectorAll(selector);
}
// and then you could use it like this
var elementsWithMyId = $("div [id^=myId]");
// where elementsWithMyId will contain array of all divs which's id start with myId
so as i understood your question, No. there is no magic happening behind jQuery selections it's just browser built in function which is kinda shortened by jquery. of course they added tons of new features, which would work like this:
var $ = function(selector){
var elementsArray = document.querySelectorAll(selector);
elementsArray.makeBlue = function(){
for(var i = 0; i < elementsArray.length; i++){
elementsArray[i].style.backgroundColor = "blue";
}
// so elementsArray will now have function to make all of its
// div blues. but if you want to have chain like that, we have to return this array not just make all of it blue
return elementsArray;
}
elementsArray.makeRed = function(){
for(var i = 0; i < elementsArray.length; i++){
elementsArray[i].style.backgroundColor = "red";
}
return elementsArray;
}
return elementsArray;
}
// so now you can use it like this
// this returns array which has options make blue, and make red so lets use make blue first
// makeBlue then returns itself, meaning it returns array which has again options of making itself red and blue so we can use makeRed now
$("div [id^=myId]").makeBlue().makeRed();
and thats it!

How should I use Variables and jQuery Dom navigation?

I was just wondering which is the correct or most efficient way of navigating through the Dom using variables.
For example, can I concatenate selectors
var $container = '.my-container';
$($container).addClass('hidden');
$($container + ' .button').on('click', function(){
//something here
});
or should I use the jQuery traversal functions
var $container = $('.my-container');
$container.addClass('hidden');
$container.children('.button').on('click', function(){
//something here
});
Is there a different approach, is one best, or can you use them at different times?
The $ is usually used only when working with an actual jquery object. You generally shouldn't prefix anything with that unless it's really something from jquery.
Beyond that little bit though, performance-wise, your second bit of code is going to be faster. I made an example jsperf here: http://jsperf.com/test-jquery-select
The reason the second bit of code is faster is because (if I remember correctly) jquery caches the selection, and then any actions performed on that selection are scoped. When you use .find (which is really what you meant in your code, not .children), instead of trying to find elements through the entire document, it only tries to find them within the scope of whatever my-container is.
The time when you wouldn't want to use the second pattern is when you expect the dom to change frequently. Using a previous selection of items, while efficient, is potentially a problem if more buttons are added or removed. Granted, this isn't a problem if you're simply chaining up a few actions on an item, then discarding the selection anyway.
Besides all of that, who really wants to continuously type $(...). It's awkward.

Refactoring jQuery to use $(this) and add/remove classes from another element

so I have this idea I'm working on over on Codepen. I've got it working as it is but before I go and add more clickable areas I've realized a massive need to refactor and DRY things up. So far it works but it's ugly as hell and would involve a massive amount of repeated code.
So I'm trying to replace the many $(.class).click(function() { ... }); functions with a switch statement that uses $(this) to populate a single .click function instead. But I'm lost.
You can see everything here and edit it also: http://codepen.io/lukewatts/pen/ubtmI
I feel like I'm close but I've hit a wall. The top commented out part is the DRY attempt while what is uncommented for now is the working version. Click the min, off, max words or the LEDs to see it work.
Thank you very much in advance for any advise on this. PHP is my main language to be honest.
P.S. I had leds.click(function() { ... }) and I replaced it with leds.on(function() { ... }) but still nothing.
I understand what you are trying to do, but that not how the jQuery object works. In order to check for the object to match a selector, you will have to use .is().
As such, you will not be able to use a switch, but you will have to use a serie of chained ifs to achieve the goal the way you are trying, such as
if ( $this.is('.led[data-level="one"]') )
var led = $('p.min a');
var level = "one";
I have updated your CodePen example to work in this way: Codepen
As I mentioned in my comment to the question, though, I am not making any code review here, just fixing what didn't work for you. I am not sure this is actually a better approach than your messy original one, to be entirely honest.
The refactored version looks good for me. If you don't like to use addClass and removeClass you may directly change the class property of the element:
indicator.attr("class", "one on");
The reason your switch statement doesn't work is because every time you create a jQuery object, it gets an Id, so when the switch tries to compare $this to a selector like $(p.min a), they won't be equal. However, if you used multiple if statements with $.is, you could compare:
$this = $(this)
if($this.is('p.min a')) {
// do work
} else if($this.is('p.max a')) {
// do work
}
I wouldn't, however, recommend this approach. For more complex pages, I'd recommend a binding framework like Knockout.js. For something small, you're adding a lot of complexity. For clarity: If this becomes part of a larger control set or system, a binding framework would be useful. For the control as-is, both a binding framework and the OP's current approach are overkill.
You may want to look at event delegation, I find it very helpful to keeping things DRY. Clicks will bubble up the DOM tree to higher elements, and you can register your handler on an ancestral element. This is actually ideal, as you only bind a single handler to a single element, instead of binding to multiple elements and thus you realize a performance benefit in addition to cleaner code.
First thing, wrap all your .led elements in a <div id="leds">:
<div id="leds">
</div>
Now create your handler:
$('#leds').bind('click', function(e){
var target = e.target;
var $target = $(target);
//do interesting stuff
if (target.nodeName === 'A') {
var level = $target.data('level');
if(level = 'one'){
//do more interesting stuff
}
}
}
});

Categories

Resources