There are quite a number of jQuery methods that accept functions instead of values as parameters. .append() is an example of that. Instead of writing:
something.append( '<div></div>' );
one might write:
something.append( () => '<div></div>' );
That's... nice. But I'm wrecking my mind trying to come up with a use case for this. Why would I want to do that? Does this enable something that would not otherwise be possible? Or does it at least drastically shorten or beautify certain bits of code?
Just to quickly add the purpose of this question: I'm writing a JS library that doesn't operate on HTML but still might as well have an API that's similar to jQuery's. So now I'm trying to figure out what to copy and what not to.
EDIT:
One use case is to index elements based on their position in the matched set. (Thanks to #Satpal and #JasonSmith!)
A second use case is to conditionally add content - as long as there's no condition that requires not to add content. (Thanks again to #JasonSmith)
Are there other practical use-cases? Does this get used often?
In .append(fn) method. With in the function, this refers to the current element in the set. which lets us to manipulate the content to be appended.
Here is an example.
$('p').append(function(){
return $(this).index();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p></p>
<p></p>
<p></p>
<p></p>
OR
$('p').append(function(){
return $(this).next('a');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p></p>1
<p></p>2
<p></p>3
<p></p>4
#Satpal provided an excellent example of one use case using the append function in jQuery. In more general terms, however, I look at it this way: if you are creating an API method capable of operating on a set of objects, then accepting a function as an argument for that API method allows the user to vary the behavior of the API according to the unique properties of each object in the set. For example, suppose we have a collection of elements like this:
<li>100</li>
<li>1000</li>
<li>150</li>
Suppose also that we have a hypothetical API method called myAPI.colorize(). If the colorize function accepts only a string, then all items in the set will be made the same color
mySet.colorize('red');
If, on the other hand the colorize method also accepts a function as an argument, then the developer can dynamically colorize without being required to break the set into constituent parts, like this:
mySet.colorize(function(currentElement) {
return currentElement.text == '1000' ? 'green' : 'red';
});
Or, if our hypothetical API binds the this reference the way jQuery does, then we could make our code even simpler:
mySet.colorize(function() {
return this.text == '1000' ? 'green' : 'red';
});
Of course this is a somewhat contrived example, but I believe it illustrates the design point in question without getting too stuck on a specific feature of jQuery.
Related
I'm trying to figure out how to write an if statement that works something like this:
if (element contains class 1 && class 2) {
// do this
}
How do I write that if statement to check for multiple classes?
Is there a way to make the .contains method check for more than one class?
What I've found so far:
How to check class of multiple class elements in javascript, without jquery
Problem is, it seems to be returning an array of all the classes that the element contains, which is not what I want. I need the function to check if the element contains the classes that I am supplying it with.
One of the solutions looks to me like it's asking us to create our own function to do this, but is there a native method on JS which will do the trick?
I'm quite new to this stuff and I really appreciate the patience you guys have shown in answering my questions.
You can use the .matches() API on the element, and it's pretty well-supported now:
if (element.matches(".class1.class2")) ...
It's like a built-in version of jQuery .is().
Documentation link.
Put the class names you want to test for into an array, then use Array.prototype.every() to check if all members of your array exist in the element's classList:
console.log(['a', 'b'].every(e=>div.classList.contains(e)))
console.log(['a', 'b', 'c'].every(e=>div.classList.contains(e)))
<div class="a b" id="div"></div>
Unfortunately Element.classList is read-only, so you cannot add anything to its prototype. You can however do that on the Array.prototype:
Array.prototype.contains = function(...args) {
return [...args].every(c=>this.includes(c))
}
console.log([...div.classList].contains('a', 'b'))
console.log([...div.classList].contains('a', 'c'))
console.log([...div.classList].contains('a', 'c.d'))
<div class="a b c.d" id="div"></div>
Important: Please note that this violates the basic OOP rule Do not modify objects you don't own. As such, it would be better to create a named function taking the element and an array with a list of classes to test for.
There is a native API for the browser that lets you play with the DOM. jQuery is not always needed. https://www.w3schools.com/jsref/prop_element_classlist.asp.
You can use the 'classList.contains' method
document.getElementById({id}).classList.contains({class}) -- returns true or flase
I like to keep my code clean as possible and I am just wondering what would be the best way to go around this problem when it comes to best practices.
I have the following function:
$('.car-hub-header-help, #assistance-overlay').click(function(){
$('#new-car-hub, #new-car-offer').toggleClass('assistance-active');
$('#pulman-assistance').toggleClass('pulman-assistance-active').css("top", fixedPositionCalculator);
$('#assistance-overlay').toggleClass('assistance-overlay-active');
$('#new-car').toggleClass('assistance-active-body');
$('#new-car-offer-cta').toggleClass('assistance-active-cta');
});
Now as you can see this function is very simple it just toggles classes based on a click event. One issue that I am having is that the element new-car-offer-cta is only on specific pages and it seems like this is bad practice to run that part of the function if the element isn't on some of my pages.
So I am just wondering if this would be better practice:
$('.car-hub-header-help, #assistance-overlay').click(function(){
$('#new-car-hub, #new-car-offer').toggleClass('assistance-active');
$('#pulman-assistance').toggleClass('pulman-assistance-active').css("top", fixedPositionCalculator);
$('#assistance-overlay').toggleClass('assistance-overlay-active');
$('#new-car').toggleClass('assistance-active-body');
var carOfferCta = $('#new-car-offer-cta');
if (carOfferCta.length) {
carOfferCta.toggleClass('assistance-active-cta');
};
});
So that part of the function wont run unless the element is on the page. I am just wondering what is classed as the best practice. Thanks
I would advise against doing the check at all. The beauty of jQuery is that you usually don't have to know whether your selector selects 0, 1 or more elements, the methods will just work (even if working is doing nothing at all).
If you start adding these checks everywhere, you're just coupling different parts of your logic more tightly together.
(That's also why I usually prefer not to use id selectors, but select based on classes instead. If then your html changes and e.g. your jQuery code needs to act on more elements, you don't need to change anything in the structure of your page, just apply the right classes.)
jQuery already does that check for you, if the selector inside $() doesn't match any elements, the functions you chain to it won't do anything (not even produce an error). So there's really no need to check explicitly. With these exceptions:
if you want it to be absolutely obvious to anybody reading your code, that the element you're trying to create won't exist on every page that uses your script or
if you want to do a bunch of different things in your if statement,
then it makes sense to explicitly write if ($element.length).
It's better to ask if the element's val is !=undefined, rather than asking for it's length, since that way you're assuming it does exist on the page. You can add the length check right after asking for the element's existence, I usually do as follows:
if($('#new-car-offer-cta').val() != undefined && $('#new-car-offer').val().length > 0){
//do something
}
I maintain a custom library consisting of many dijit widgets at the company I work at.
Many of the defects/bugs I have had to deal with were the result of this.inherited(arguments) calls missing from overriden methods such as destroy startup and postCreate.
Some of these go unnoticed easily and are not always discovered until much later.
I suspect I can use dojo\aspect.after to hook onto the 'base' implementation, but I am not sure how to acquire a handle to the _widgetBase method itself.
Merely using .after on the method of my own widget would be pointless, since that wouldn't check whether this.inherited(..) was inded called.
How can I write a generic test function that can be passed any dijit/_WidgetBase instance and checks whether the _widgetBase's methods mentioned above are called from the widget when the same method is called on the subclassing widget itself?
Bottom-line is how do I acquire a reference to the base-implementation of the functions mentioned above?
After reading through dojo's documentation, declare.js code, debugging, googling, debugging and hacking I end up with this piece of code to acquire a handle to a base method of the last inherited class/mix-in, but I am not entirely happy with the hackiness involved in calling getInherited:
Edit 2 I substituted the second param of getInherited with an empty array. While I actually get a reference to the method of the baseclass using aspect doesn't work. It appears this approach is a bust.
require(['dijit/registry','dojo/_base/declare','mycompany/widgets/widgetToTest'],
function(registry,declare,widgetToTest)
{
var widget = registry.byId('widgetToTestId');
var baseStartup = getBaseMethod(widget,'startup');
function getBaseMethod(widget,methodName){
return widget.getInherited(methodName,[]);
}
//This is the method body I want to use .after on to see if it was called, it returns the last overriden class in the array of inherited classes. (a mixin in this case, good enough for me!)
alert(baseStartup);
});
I have given up trying to use dojo/aspect.
I have instead opted to modify the code of our custom base widget to incorporate snippets such as the one below. They are automatically removed when creating a release-build in which console-calls and their content are removed:
console.log(
function(){
(this._debugInfo = this._debugInfo|| {}).postCreate=true;
}.call(this)
);
A simple method in boilerplate code I added near the unittests is available so that I can call it on all mycompany.widgets.basewidget instances in their respective unittests.
Libraries I've seen have DOM wrappers that inclusively handle only the first element of the list in some case, like:
return this[0].innerHTML
and use the whole list in some other like:
for( var i=0, l=this.length; ++i<l; ) this[i].className = cls;
return this
Why is this approach accepted?
I think singling out the first element defeats the purpose of having methods that apply the same thing on the rest of the list. Isn't it bad to have dubious functions? I know it suits many people..but it feels inconsistent and I'm interested in why this is accepted so widely.
EDIT as an example:
jQuery.html()
If the selector expression matches more than one element, only the
first match will have its HTML content returned.
why not all?
the hide() method in bonzo, from Dustin Diaz
//...
hide: function () {
return this.each(function (el) {
el.style.display = 'none'
})
}
why not only the first?
The accessor methods in jQuery return single values because it's simpler and more generally useful. If the .html() API were to return the value if innerHTML for all elements, that'd mean it'd have to return an array. That, in turn, would mean that in the most common case of wanting the contents of a single element, you'd have to add the array access. There's also the problem of knowing exactly which returned value goes with which selected element. In other words, if .html() returned an array of element contents:
var contentList = $('.someClass, span, .hidden .container').html();
If "contentList" were just a simple array, what use would it be? How would the code know for each element which DOM node it came from? Of course there are solutions to this, but again the simple case is made complicated in order to support a rare general case.
It's possible of course to get the list yourself with .map(). I think this is just an issue of smart, practical, pragmatic API design.
function eegetdropdownvalue_str(ctl){return ctl.selectedIndex>=0&&ctl[ctl.selectedIndex]?ctl[ctl.selectedIndex].value:''}
The above function is called with
co.p1A10=eegetdropdownvalue_str(document.formc.p1A10);
I want to switch the call over to jQuery to drop the document.form reference however doing this
co.p1A10=eegetdropdownvalue_str($('p1A10'));
Does not reference the control correctly - How should I do this?
There's two things wrong with your code.
First, $('p1A10') references nothing.
jQuery selectors work almost identically (if not completely identically) to the way css works.
So, just ask yourself how you would reference the object(s) in question in CSS and you're half way there.
I'm assuming that p1A10 is the name or id of an object. Since we're using CSS/jQuery syntax, this should be an id, although you can select by other attributes such as $("select[name='p1A10']") .
To reference an object by ID we use the # character (again, just like in CSS). So we can select your node via $('#p1A10').
The second problem is that your function is expecting a DOM object not a jQuery object. To keep your code intact, we need to say $('#p1A10')[0] where 0 is the first element within the collection of jQuery elements.
I've provided two examples to explain this a little better. One uses your existing infrastructure and one replaces it.
http://jsfiddle.net/TD6Uu/5/
Hope it helps.
Given a form with id formc and a select with name p1A10 you could e.g. use:
o.p1A10 = eegetdropdownvalue_str($('#formc select[name="p1A10"]').get(0));
If this doesn't do it, please provide use with the exact HTML structure