remove elements from the jQuery object from within a plugin - javascript

I think I have mistaken some fundamentals here, because I think this should work. I am trying to to through the child p and div elements of the matched set, and remove those which fail to meet the required wordcount from the matched set.
I have tested the wordCount plugin, and the if statement it is being used it, and all seems to be working fine, but my element is not being removed from the matched set.
(function($){
$.fn.extend({
textBlocks: function(count){
var JQ_Object = $(this);
if(!count) count = 100;
return this.each(function(){
$(this).find("p, div").each(function(){
if($(this).wordCount()<count){
var x = $(this);
JQ_Object.not(x);
};
});
return JQ_Object;
});
}
});
})(jQuery);
Here is the wordCount plugin, just in case you wondered:
(function($){
$.fn.extend({
wordCount: function(){
return $(this).html().split(" ").length;
}
});
})(jQuery);

I made a few changes... see fiddle for working example and code for comments.
http://jsfiddle.net/8PXpt/
(function ($){
$.fn.extend({
wordCount: function (){
//Don't need $(this), this already refers to the jQuery object
//Always trim .html() and .text() when using .split()
//May want to use .text() instead of .html() - I leave that to you
return $.trim(this.html()).split(' ').length;
}
});
})(jQuery);
(function ($){
$.fn.extend({
textBlocks: function (count){
var collection = this;
//Check that a number was passed
//"50" would break your extension
if(typeof count !== 'number') {
count = 100;
}
//Removed $('div, p') - this should be part of your call
//See ready function below
this.each(function (){
if ($(this).wordCount() < count){
//This could be double assignment
//but better safe than sorry
collection = collection.not(this);
};
});
//Return what is left (what passed)
return collection ;
}
});
})(jQuery);
$(function() {
//Here is where you define your selector... in your case 'div, p'
$('div, p').textBlocks(2);
});

Have you tried $(this).remove() rather than JQ_Object.not(x);
I think .not() removes them from the selection rather than from the HTML... unless that's what you're trying to do

You're creating a new JQ_Object in the internal each, so I'm not sure if it would modify the original JQ_Object. I'm not 100% on that though. Try JQ_Object.not(this).
This assumes, however, that .each is synchronous, which I'd hope it isn't. If that's the case, you'd need to make use of jQuery's while function.
This should give you the desired result, but I'd be wary each being asynchronous.
return $(this).find("p, div").each(function(){
if($(this).wordCount()<count){
JQ_Object.not(this);
};
});
EDIT:
I'm not to sure about the above code. What I'd do is use a callback. This assumes a callback is passed in to your plugin.
$(this).find("p, div").each(function(){
if($(this).wordCount()<count){
JQ_Object.not(this);
};
}).when(function () {
callback(JQ_Object);
});

Related

Make jQuery function run individually

I create function to get specific height of some different div that has same class 'parallax', the problem is that it works but it can only get height of only first div and apply it to others.here my code, please suggest me what to do, I'm still a beginner.
jQuery.fn.do_para = function() {
var section_effect = jQuery(this);
var height = section_effect.height();
alert(height);
//do something with height
}
if(jQuery('.page_section').hasClass('parallax')) {
jQuery(this).do_para();
}
html looks like this
<div class="page_section parallax" style="height:500px"></div>
<div class="page_section"></div>
<div class="page_section parallax" style="height:700px"></div>
The problem would be because this in the outer part of a jQuery plugin refers to the jQuery object/collection itself, not the individual elements of that collection. So, instead of:
jQuery.fn.do_para = function() {
var section_effect = jQuery(this);
var height = section_effect.height();
alert(height);
//do something with height
}
You should instead use:
jQuery.fn.do_para = function() {
// if you don't want to return the collection
// omit the 'return' (but this will break
// the traditional jQuery chaining if you do):
return this.each(function(){
// within the each() method 'this' refers
// to each individual element of the
// collection passed to the plugin:
var section_effect = jQuery(this);
var height = section_effect.height();
alert(height);
//do something with height
});
}
The reason the problem was referring to the whole collection is that when a jQuery method is used as a getter (using the method without passing an argument) it will refer to the first element in the collection, and return the value from that first element.
If you'd wanted, for some reason, to retrieve an array of values instead you could have used:
jQuery.fn.do_para = function() {
var section_effect = jQuery(this);
// here we use the map() method to iterate over
// the collection and, from each element in the
// collection, return the height:
var height = section_effect.map(function(){
$(this).height();
// here we use get() to turn the jQuery map into
// an Array:
}).get();
alert(height);
//do something with height
}
Incidentally if, within the plugin's code, you want to use an alias for jQuery – to save typing, if nothing else – then you can instead compose your plugin in the following way, using an Immedately-Invoked Function Expression:
// the argument to the function is the
// alias with which you refer to
// jQuery:
(function ($) {
$.fn.do_para = function(){
return this.each(function(){
var section_effect = $(this);
// rest of your code..
});
});
// passing jQuery into the function:
})(jQuery);
Bibliography:
"How to Create Basic Plugin."
"Writing Your Own jQuery Plugins."

jQuery: apply function to elements at the same time

With jQuery.each(), I can iterate through members of the array:
// This is NOT what I am looking for.
$('.example a').each(function() {
// do a lot of things here
$(this).css('color', 'red');
});
However, I need to apply a function to the jQuery array itself, and not to its members. So I wrote a little plugin to do this:
$.fn.all = function( callback ) { return callback.call( this ); }
$('.example a').all(function() {
// do a lot of things here
$(this).css('color', 'red');
});
Please notice that in the function above, this will be set to the collection of the elements - which is what I require.
Now I'm sure that jQuery should have an elegant way to do this, but I haven't found anything in the documentation, or by Googling.
Is it possible, and if it is, how do I achieve this without using custom plugins?
UPDATE: I can not do $('.example a').css('color', 'red'); directly. I have a dozens of calculations in the function, so I have to use something similar to the plugin I wrote.
I am asking for a callback. The correct answer must provide a callback similar to the custom function.
You don't need a plugin, you can use call directly:
(function() {
// do a lot of things here
this.css('color', 'red');
}).call($('.example a'));
Or consider passing the array-like object as an argument
(function(arrayLike) {
// do a lot of things here
arrayLike.css('color', 'red');
})($('.example a'));
Or if you prefer a jQuery way
$.each([$('.example a')], function(){
// do a lot of things here
this.css('color', 'red');
});
Shouldn't
$('.example a').css('color', 'red');
be enough?
That would be the straight-forward (jQuery-esque) way to do what you want:
var $collection = $('.example a');
$collection.each(function() {
// do a lot of things here
$collection.css('color', 'red');
});
//And another reason I dislike jQuery's implementation of each().
//but most of all, wrong argument-order and the abuse of this to pass the current value/node
Here my prefered implementation. Mostly like Array.prototype.forEach(), and the nice parts of $.each().
$.fn.forEach = function(fn, scope){
if(scope != null) fn = fn.bind(scope);
for(var i = 0, len = this.length; i < len; ++i)
if(false === fn(this[i], i, this)) break;
return this;
}
and your code would be like:
$('.example a').forEach(function(node, index, $collection){
//and don't use `$(this)`, since `this` most of the time references `window`
//unless you've bound the function to a scope, or passed one to forEach
var $node = $(node);
$collection.css('color', 'red');
});
this
$('.example a')
returns collection of matched elements. And this
$('.example a').css('color', 'red');
sets color red to all elements in the collection.
That's how jQuery works.
I think that is not possible getting all the calculations at the same time, you should use threads for that, or a webWorker. You can get more info about this in mdn.
You can cache selector, utilize $.queue()
var elems = $(".example a");
$.queue(elems, "q", function() {
$(this).css("color", "red");
console.log(this)
});
$.dequeue(elems, "q")
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<div class="example">
a
b
c
</div>

create jquery extension. problems with scope

I create a simple jQuery extension(it's my first).
(function($){
var MyClass = function(opt){
//..
};
//one of the methods of my extension
$.fn.myExtension = function(opt){
this._ext = new MyClass(opt);
return this;
};
$.fn.myExtensionOtherMethod = function(){
if(this._ext){
//do something ..
}
return this;
};
}(jQuery));
//using ..
$(document).ready(function(){
$('#selector').myExtension({
//options ..
});
$('#selector').myExtensionOtherMethod();
});
when i invoke method $('#selector').myExtensionOtherMethod();, this does not contains this._ext variable. I know that this is other scope, but i know that there is some way to access that variable in both methods.how can i do it?
This isn't really a scope issue. This is because the jQuery prototype $.fn gives you a jquery object as this. Even though you are selecting the same element each time its a new jQuery object set as the context so that property is gone. You can put that property on the DOM element and achieve the outcome you want.
(function($) {
var MyClass = function(opt) {};
//one of the methods of my extension
$.fn.myExtension = function(opt) {
this[0]._ext = new MyClass(opt);
return this;
};
$.fn.myExtensionOtherMethod = function() {
if (this[0]._ext) {
//do something ..
}
return this;
};
}(jQuery));
//using ..
$(document).ready(function() {
$('#selector').myExtension({
//options ..
});
$('#selector').myExtensionOtherMethod();
});
This is just a quick example above. If your selector finds more than one element you should loop though them. But I only grabbed the first index since you were selecting by ID.
Fiddle: https://jsfiddle.net/AtheistP3ace/gd1ehk0d/
As mentioned above by #charlietfl, I agree with that comment. Happy to explain why what you did didn't work but there may be better ways to achieve what you are looking for.

jQuery .hasClass() method fails for SVG elements

I have a set of SVG elements with the classes node and link. My program should detect whether an element has the node class or the link class upon hovering over any of the SVG elements. However, for some reason, the .hasClass() doesn't seem to work:
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
So the element I hovered on has the class node, and jQuery detects that too, as shown by console.log($(this).attr("class"));, but for some reason the actual .hasClass() fails. Why is this? Is it failing because of the SVG?
The class attribute for HTML element doesn't have the same meaning in SVG.
$("<b></b>").addClass($(this).attr("class")).hasClass("node")
Or
/(^|\s)node(\s|$)/.test($(this).attr("class"))
for SVG elements.
EDIT .hasClass seems to work just fine (at least in IE9 and FF) http://jsfiddle.net/X6BPX/1/
So the problem could be any combination of the following: a syntax error, using an outdated browser, using an outdated version of jQuery.
As Bergi pointed out in comments, jQuery silently fails on SVG elements on account of className returning an SVGAnimatedString object instead of a normal DOMString.
See this JSFiddle for a comparison.
I was tempted to submit a pull request on this, but did a quick project search, and apparently the jQuery project stance on SVG issues is wontfix: https://github.com/jquery/jquery/pull/1511
If you're using D3, you could use d3.select(this).classed('node'). Note that D3 correctly returns for both HTML elements and SVG elements.
This is not the fastest option ever, but it is a possible solution. Instead of using jQuery's hasClass you could instead obtain the class attribute as a string and use indexOf to search through it. There are probably use cases where this will fail, so I wouldn't recommend this except for super simple projects.
Working example:
var s = $(this).attr('class');
if( s.indexOf('node')!==-1 ){
// do something
}
Remember: indexOf returns -1 when it can't find anything, not 0. 0 is returned when the substring starts at index 0.
This is a hack for addClass, removeClass, hasClass jquery methods for before jquery 3.x.x versions.
$.fn.extend({
addSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) === -1) {
classListArr.push(cls);
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
} else {
$(this).attr('class', cls);
}
});
},
removeSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
delete classListArr[classListArr.indexOf(cls)];
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
}
});
},
hasSVGClass: function (cls) {
var el = this[0];
var classList = $(el).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
return true;
} else {
return false;
}
}
return false;
}
});
usage :
$('.svg-element').addSVGClass('selected');
Works. But be sure to close the function
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
http://jsfiddle.net/X6BPX/

jQuery - why can't I bind events to elements in a loop?

Here is my code:
var b = $(slipStream.conf.mainVis).find('p#prev');
b.click(function() {
slipStream.slideLeft();
return false;
});
b = $(slipStream.conf.mainVis).find('p#next');
b.click(function() {
slipStream.slideRight();
return false;
});
b = $(slipStream.conf.controls).find('li img');
console.log(b);
for (var l in b) {
l.click(function() {
var visIndex = l.index();
console.log(visIndex);
});
};
The first two bindings go through, no problem. But I can't loop through a collection and bind something to each member? (the console is telling me that "l.click is not a function.") Is this a limitation of jQuery or is my code off? This seems like it would be the way to do it, though...
When you enumerate over a jQuery object, the values being enumerated are actual DOM nodes and not jQuery wrappers. Therefore, they don't have a click method but you can wrap them again to get all the usual functionality.
Of course this is not necessary because you can simply attach a wrapper directly from your initial jQuery instance:
$(slipStream.conf.controls).find('li img').click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
This is the classic "loop variables don't work properly in callbacks" bug.
Your variable l no longer has the originally supplied value by the time the callback is invoked - it has whatever final value was assigned in the last pass through the loop.
[FWIW, l isn't actually a jQuery object, so you have to wrap it - $(l) to use it with jQuery]
The usual fix to the loop bug is to create an additional closure that returns a function bound to the current value:
for (var l in b) { // NB: don't use `for ... in ...` on array-like objects!
var make_cb = function(n) {
return function() {
var visIndex = $(n).index();
console.log(visIndex);
}
}
$(l).click(make_cb(l));
};
Fortunately, you don't need a loop at all - you can have jQuery automatically add the callback to every element by itself:
b = $(slipStream.conf.controls).find('li img');
b.click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
Could it be that the problem is the forloop. .click is part of the jQuery, so you must be sure that it's called on element that is wrapper with jQuery.
$.each(b, function (index, element) {
$(element).click(function() {
});
};
With each() you can iterate through a set of jQuery objects:
$(slipStream.conf.controls).find('li img').each(function(){
$(this).click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
});
$(this) will match the currently indexed object from the collection.

Categories

Resources