In jQuery, you can run a selector where every element is run through a function you define, like this (totally contrived example):
jQuery.expr[':'].AllOrNothing= function(a,i,m){
// a is the thing to match, m[3] is the input
if(m[3] === "true"){
return true;
} else {
return false;
}
};
Then you can use it like:
$("div:AllOrNothing(" + true + ")"); //returns all divs
$("div:AllOrNothing(" + false + ")"); //returns nothing
Is it possible to pass an anonymous function instead of calling jQuery.expr[:].Name= ?
Edit
I'm envisioning something chainable like the following:
$("div").filterByFunction(function(a,i,m){ ... })
It sounds like you just want to use the built-in .filter() method and pass it a custom function that examines sibling elements to decide whether to return true or false and then hide the remaining elements.
$("section").filter(function() {
// examine child div and return true or false
}).hide();
For completeness, you could an implementation of filter yourself with by adding to $.fn
$.fn.customFilter = function(f) {
var filtered = $();
this.each(function() {
if(f.call(this)) {
filtered = filtered.add(this)
}
});
return filtered;
}
$("div").filterByFunction(function(){ return $(this).text() != "test"; })
Not that you should, in this case.
Related
How Jquery can chain and return multiple values?
I know you can chain:
const $ = {
a() {
return this;
}
b() {
return this;
}
}
$.a().b()
The example below will speaks by itself:
$('div').find('p').hide().css() // find an apply style
$('div').find('p') // return all the "p"
See my example
How jQuery return all the p and keep the plugin instance?
How can I achieve the same behavior?
How to know if there is another call after find()?
Thanks.
All of these API members are returning a jQuery object. It just so happens that the jQuery API has all of these members on it. That is how you are able to chain calls on the same object.
Taking a look at the docs:
In API calls that return jQuery, the value returned will be the original jQuery object unless otherwise documented by that AP
show() returns a jQuery type
hide() returns a jQuery type
find() returns a jQuery type
Note that css() doesn't return a jQuery type, so you can't chain off that.
jQuery follows an approach like this one:
function $(param) {
if(!(this instanceof $)) // if $ is called without using 'new'
return new $(param); // then return a new instance
this.param = param; // ...
}
$.prototype.a = function() { // the function a has to...
// a's logic
console.log("this is a");
return this; // return this so there will be chaining
}
$.prototype.b = function() { // the same goes for b
// b's logic
console.log("this is b");
return this;
}
$.c = function() {
// c's logic
console.log(c);
// not returning this as c can't be chained
}
$("some param").a().b().a();
In the example, a and b are methods (attached to the prototype) that return this so they can be chained (example of $(...).val(), $(...).addClass()). c however is attached as a property to the function object $ directly, thus it can't be chained (example of $.isArray() and $.each() not to be confused with $(...).each()).
A mini-jQuery example:
function $(selector) {
if(!(this instanceof $))
return new $(selector);
if(selector instanceof $) // if selector is already an instance of $ then just return it to be used as it is
return selector;
else if(typeof selector === "string") // otherwise, if selector is a string, then select the elements
this.elems = document.querySelectorAll(selector);
else if($.isArray(selector)) // otherwise, if the selector is an array, then the elements will be those in the array ( this is used by find )
this.elems = selector;
else if(!selector) // otherwise, selector is a DOM element
this.elems = [selector];
}
$.prototype.each = function(callback) { // take a function (callback) and call it on all elements
for(var i = 0; i < this.elems.length; i++) // for each element in this.elems
callback.call(this.elems[i], i, this.elems[i]); // call callback, setting its this to the current element and passing to it two parameters: the index and the element itself
return this;
}
$.prototype.css = function(prop, value) { // a function that take a CSS property and probably a value and either set or return the value of that CSS property
if(value === undefined) // if the value is not provided, then
return this.elems[0].style[prop]; // return the value of the first element in the selection (some checking if the element exist should be here)
return this.each(function() { // if a value is provided, then
this.style[prop] = value; // set the style of each element in the selection, and then return this (this.each returns this so just return this.each())
});
}
$.prototype.text = function(val) { // follows the same as css above
if(val === undefined)
return this.elems[0].textContent;
return this.each(function() {
this.textContent = val;
});
}
$.isArray = function(obj) { // is not a method (this is not the instance of the class)
return Object.prototype.toString.call(obj) === "[object Array]";
}
// FIND:
$.prototype.find = function(selector) {
var newElems = []; // the array of new elements
this.each(function() { // for each element in the current selection
var thisElems = this.querySelectorAll(selector); // select it descendants that match the selector
for(var i = 0; i < thisElems.length; i++) // add them to the result array
newElems.push(thisElems[i]);
});
// remove duplicates // this is important as elements could be selected twice (elements inside a parent inside a grand parent where both parent and grand parent are in the old selection), check jQuery's source code for how they remove duplicats using sort
return $(newElems); // return a new $ object using the new array of elements as the wrapped elements (check the constructor for more informations)
}
// usage:
$("div").css("background", "#fd8") // selects all divs and change their background
.find("span") // now find all spans inside those divs
.css("background", "#0ff"); // change their background
<div>a</div>
<div class="even"><span>b</span></div>
<div>c</div>
<div class="even">d</div>
<div><span>e</span></div>
<span>outer span</span>
I am working with angular and I am trying to create a "select all" button.
I have a list of items, each item has a toggle and what I am doing is, on change (everytime the toggle changes from true (selected) to false (not selected), I run a function to create an array with all the IDs of the selected elements.
This works almost perfectly, the problem is that I am facing some issues with the indexfOf method to check if the ID is already in the array.
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
return scope.selectFromList(element.iID, element.copyTo);
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
scope.selected.pop(id);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
the problem I've got is when the array (scope.selected) has multiple IDs.
Let's say, for example, that my scope.selected looks like this:
scope.selected = [2,3,4,7]
if I click on select all, nothing gets added (and this is correct)
Now, let's say I untick 4 and 7 for example, and my scope.selected now looks like this:
scope.selected = [2,3]
If I now click on select all, my result is the following: [2,4,7].
I lose the 3
I think this is due to the fact that my array doesn't have one single item?
thanks for any help. Here's also a quick codepen to explain the problem. If you check the console and play with the toggles you should be able to see straight away what I am referring to.
Thanks in advance
Thanks to Matthias and Christian Bonato for their suggestions.
At the end, I solved using both of their suggestions and the final result seems to work as expected.
Here's a codepen with the final version: http://codepen.io/NickHG/pen/KNXPBb
Basically, I changed
scope.selected.pop(id);
with
$scope.selected.splice( isInArray($scope.selected, id),1);
and in the selectAll event function, I always empty scope.selected[] before adding elements to the array
$scope.evtSelectAll = function() {
$scope.selected = []
angular.forEach($scope.list, function(element) {
element.copyTo = true;
return $scope.selectFromList(element.id, element.copyTo);
});
};
thank you for your help!
I think mostly your code contains a logical error. You are using the function selectFromList to de-select (when done individually) and for the select all (which you don't want to use to de-select).
As someone pointed out in a for some reason now deleted answer, the pop.() function shouldn't be called with any arguments (it is only for removing the last element), you should use splice like this:
$scope.selected.splice( isInArray($scope.selected, id),1);
Unless you really need the emitted functionality to run on a select all, you can try if this is the answer for you:
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
if (isInArray($scope.selected, element.id) === -1) {
$scope.selected.push(element.id);
}
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
$scope.selected.splice(isInArray($scope.selected, id), 1);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
Now the select all only adds to scope.selected if it doesn't find the id in the scope.selected list.
If I set a function under an object i can use it once only like
function handle(selector)
{
return{
elem:selector,
next:function(){
return (this.nextSibling.nodeType==1) ? this.nextSibling : this.nextSibling.nextSibling;
}
}
}
here i can say handle.next() this will work but if I want to say handle.next().next().next() my question is how I can use in this way as jquery does?
Speaking about your function you can modify it like this to make it work:
function handle(selector)
{
if (typeof selector === 'string') {
selector = document.querySelector(selector);
}
return{
elem:selector,
next:function(){
return handle(selector.nextElementSibling);
}
}
}
See jsfiddle.
UPD: Modified the code to support both elements and string selectors as a parameter.
UPD 2: Came out with an alternative variant. In this case we extend the native html element object and add new next method:
function handle(selector)
{
if (typeof selector === 'string') {
selector = document.querySelector(selector);
}
selector.next = function() {
return handle(selector.nextElementSibling);
};
return selector;
}
Fiddle is here.
//find value in array using function checkValue using underscoreJS _.each.
//return true, else false.
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function(arg) {
_.each(helloArr, function(helloArr, index) {
if (arg[index] === index) {
return true;
}
return false;
});
};
alert(checkValue("hola"));
The problem with your code is that, _.each will iterate through all the elements of the array and call the function you pass to it. You will not be able to come to a conclusion with that, since you are not getting any value returned from it (unless you maintain state outside _.each).
Note that the values returned from the function you pass to _.each will not be used anywhere and they will not affect the course of the program in any way.
But, instead, you can use _.some as an alternate, like this
var checkValue = function(arg) {
return _.some(helloArr, function(currentString) {
return arg === currentString;
});
};
But, a better solution would be, _.contains function for this purpose. You can use it like this
var checkValue = function(arg) {
return _.contains(helloArr, arg);
};
But, since you have only Strings in the Array, the best solution would be to use Array.prototype.indexOf, like this
var checkValue = function(arg) {
return helloArr.indexOf(arg) !== -1;
};
Try this:
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function(arr, val) {
_(arr).each(function(value) {
if (value == val)
{return console.log(true);}
else {return console.log(false);}
});
};
console.log(checkValue(helloArr,'hello'));
/* Output
false
true
false*/
I want to return value from the function which contains an anonymous function.
function getSingleCheckedItemId() {
return $(".data-table-chk-item").each(function() {
if ($(this).is(":checked")) {
var value = $(this).attr("value");
return value;
}
});
}
In this case it returns me the array of all checkboxes. If I remove the first return, it won't return a value but undefined.
So how do I return the value from getSingleCheckedItemId()?
.each always returns the jQuery object containing all elements that you iterated over so:
function getSingleCheckedItemId() {
var ret;
$(".data-table-chk-item").each(function() {
if ($(this).is(":checked")) {
ret = $(this).attr("value");
return false; //breaks out of .each
}
});
return ret;
}
Also, this.value is usually a better option than $(this).attr('value') in case you're dealing with form inputs - seems like you have radio/checkbox inputs due to their checked property. Also, this.checked returns a boolean so there's no need for $(this).is(':checked') either.
I believe your logic can be simplified to:
function getSingleCheckedItemId() {
return $(".data-table-chk-item:checked").val();
}
This way .val() will return the value of the first :checked item or undefined if no elements are matched by the selector, which does the same as the loop above.
You can do it:
function getSingelCheckedItemId() {
var elements = $(".data-table-chk-item:checked");
return (elements.length > 0) ? $(elements[0]).val() : undefined;
}
I would do it like this
function getSingleCheckedItemId() {
var ret;
$(".data-table-chk-item").each(function() {
if ($(this).is(":checked")) {
ret = $(this).attr("value");
}
});
return ret;
}