Need help in my jquery plugin - javascript

Last week a made a function for ellipsing the text inside some selector.
I was calling the function like this:
ellipsiText('.class',50) passing the selector and the max length of the text that i wanted to. This works fine, but im trying to make it a plugin, to call like this: $('.class').ellipsiText(50).
So, i was reading the tutorial in jquery website, and i understood how to do it. But i think i'm having an issue with the "this" seletor. Here is my original function:
function ellipsiText(selector,maxLength){
var array = $(selector).map(function(){
return $(this).text();
}).get();
var i;
var teste = [];
for (i=0;i<array.length;i++){
if (array[i].length > maxLength){
teste.push(array[i].substr(0,maxLength) + "...");
} else {
teste.push(array[i]);
}
}
for (var i=0;i<teste.length;i++){
$(selector).each(function(i){
$(this).text(teste[i]);
});
}
}
and here is my tentative of making a jquery plugin:
(function ($) {
$.fn.ellipsiText = function(length){
var array = $(this).map(function(){
return $(this).text();
}).get();
var i;
var teste = [];
for (i = 0; i < array.length; i++){
if (array[i] > length){
teste.push(array[i].substr(0,length) + "...");
} else {
teste.push(array[i]);
}
}
$(this).each(function(i){
$(this).text(teste[i]);
});
};
}(jQuery));
What am i doing wrong?

Well first thing is not a problem, but instead of $(this) in the first function scope, you can use this.map/this.each.
The problem is, in the second code you do
if (array[i] > length)
instead of
if (array[i].length > length)
Nothing to do with the jQuery plugin!
http://jsfiddle.net/UY88r/

This is untested, but the basic structure is something like this. Also you have so much looping in your code when one loop is needed.
$.fn.ellipsiText= function(options) {
var settings = $.extend({ //nice way to give to give defaults
length : 50,
ellipsi : "..."
}, options );
return this.each(function() { //the return is needed for chaining
var elem = $(this);
var txt = elem.text();
if (txt.length>settings.length) {
elem.text(txt.substr(0,settings.length) + settings.ellipsi );
}
});
};
and call it
$( "div.defaults" ).ellipsiText();
$( "div.foo" ).ellipsiText({
length : 10
});
$( "div.more" ).ellipsiText({
length : 10,
ellipsi : "<more>"
});

You already have a working function, just use it.
$.ellipsiText = ellipsiText;
$.fn.ellipsiText = function (count) {
ellipsiText(this, count);
}
now you can use it like any of the following:
ellipsiText('.class',50);
$.ellipsiText('.class',50);
$('.class').ellipsiText(50);
There's no sense in rewriting the function you already have working when you can just use it.

Related

Javascript replace local variable with function return

many people said that looping function inside each() is bad practices.
it will cause terrible perfomance...
just like version one...
Version one :
$(".next").each(function() {
var el = $(this),
setNo = 0,
onNo = function() {
setNo = 1;
};
onNo(); // setNo will become 1
)};
so, i have to move function outside each(). then i'm confused on replacing local variable at each()
Version two :
var onNo = function() {
var setNo = 1;
return setNo; // replace it
};
$(".next").each(function() {
var el = $(this),
setNo = 0;
onNo(); // it's not replacing setNo local variable. how to fix this?
)};
how to fix this?
do you guys have more efficient design pattern, i'm really confused at javascript design pattern.
thanks...
Are you looking for something like this?
function onNo ($el) {
// do some calculation with $el
var setNo = 1;
return setNo;
}
$(".next").each(function() {
var $el = $(this)
var setNo = 0
setNo = onNo($el)
)}
I agree with Juhana's comment that the performance issues are not significant enough to worry about. However, to answer your question:
You can't set a local variable in another function like that. However, you can pass variables to that function.
var onNo = function(setNo) {
return setNo; // returns 0
};
$(".next").each(function() {
var el = $(this),
setNo = 0;
onNo(setNo);
)};

jQuery Not Defined within a function

Alright, here's a puzzler. I've got a jQuery function to display a PHP generated list of announcements for a website via .fadeIn/.fadeOut; the very first thing loaded on the page is jQuery 1.11.xx from a CDN. I'm running Bootstrap, fullCalendar, SmartMenus, etc., and jQuery is most definitely loading.
Except within the setInterval() to update the announcement. This is rough-code, some functionality isn't present, but to my mind it should be doing an animation.
var announcementArray = [];
var announcementSource = "../announcements.php";
var totalAnnc;
$.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
totalAnnc = announcementArray.length;
});
var count = 0;
var fadeAnnc = function() {
$('#announcementArea').text(announcementArray[count].announceText);
$('#announcementArea').fadeIn('slow',function() {
$('#announcementArea').css('display','block');
}).delay(2000).fadeOut('slow',function() {
count = (count + 1) % totalAnnc;
});
};
setInterval(function() {
fadeAnnc();
}, 3000);
Instead, when I run the page, I get a "function not defined" error for any jQuery function that's called within the setInterval(). If I call using document.getElementById('announcementArea').innerHTML = etc., it works, but doing the fade in/out via DOM manipulation seems to be more work than is needed when jQuery is available and working everywhere else on the page.
I've tried a few scope adjustments and have been working on what should be simple code for the last 5 hours. So, where's my glaring error? ;)
Not sure what kind of scope issue you are having (looks like it's the result of unposted code, as everything in your question looks OK), but if you want a fairly foolproof way of passing along the jQuery object, you could always pass it as a parameter:
var fadeAnnc = function($) {
$('#announcementArea').text(announcementArray[count].announceText);
$('#announcementArea').fadeIn('slow',function() {
$('#announcementArea').css('display','block');
}).delay(2000).fadeOut('slow',function() {
count = (count + 1) % totalAnnc;
});
};
setInterval(function() {
fadeAnnc($);
}, 3000);
Based on your updated answer, here's another possible solution:
(function($){
var announcementArray = [];
var announcementSource = "../announcements.php";
var announcementSpace = "#announcementArea";
$.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
if (announcementArray.length === 0) {
$('#anncRow').css('display','none');
}
});
var count = 0;
var masterCount = 0;
var totalAnnc = announcementArray.length;
var timer;
var fadeAnnc = function() {
if (announcementArray.length > 0) {
$(announcementSpace).html(announcementArray[count].announceText);
$(announcementSpace).fadeIn(750, function() {
$(announcementSpace).css('display','block');
}).delay(4500).fadeOut(750, function() {
$(announcementSpace).css('display','hidden');
});
}
count += 1;
if ((count % announcementArray.length) == 0) {count = 0}
};
setInterval(fadeAnnc, 6000);
}(jQuery));
$ is defined as a function parameter and thus overrides the globally scoped $ within the function body, protecting it's definition for your code. This is actually exactly what jQuery recommends when creating an extension.
My previous answer - scratch that:
The issue was more interesting - somewhere between the SmartMenu plugin and the LibraryThing book display widget there is a jQuery conflict created. This explains why - depending on the load order - different parts would break, but always the setInterval(), which always loaded after SmartMenu and LibraryThing.
So, my somewhat messy solution is to release the $ at the beginning of the script and reclaim it at the end so on other pages jQuery has access to it, like so:
jq = jQuery.noConflict();
var announcementArray = [];
var announcementSource = "../announcements.php";
var announcementSpace = "#announcementArea";
jq.getJSON(announcementSource, function(data) {
announcementArray = data.concat();
if (announcementArray.length === 0) {
jq('#anncRow').css('display','none');
}
});
var count = 0;
var masterCount = 0;
var totalAnnc = announcementArray.length;
var timer;
var fadeAnnc = function() {
if (announcementArray.length > 0) {
jq(announcementSpace).html(announcementArray[count].announceText);
jq(announcementSpace).fadeIn(750, function() {
jq(announcementSpace).css('display','block');
}).delay(4500).fadeOut(750, function() {
jq(announcementSpace).css('display','hidden');
});
}
count += 1;
if ((count % announcementArray.length) == 0) {count = 0}
};
setInterval(fadeAnnc, 6000);
$ = jQuery.noConflict();
Use closures (which is considered good practice anyways):
(function($) {
var your_function = function() {
$(...);
};
setTimeout(function() {
your_function();
});
}(jQuery));
Using closures creates a sort of 'sandbox' for your code, so you don't have to worry about overwriting any variables declared in a parent scope (such as the dollar-sign $ used by jQuery).

Run function when inputs are not empty

Let's start with the fiddle: http://jsfiddle.net/Gytrn/
Here I am mixing angular and jquery like a bad person, I know, but the underlying question is this:
How can I more elegantly execute the javascript to fade in the paragraph once all the inputs are filled in?
Currently the code is as such:
$('input').focusout(function(){
var allInput = $('input')
var inArr = []
for (var i = 0; i < allInput.length; i++) {
inArr.push($('input:eq('+i+')').val().length)
}
console.log(inArr);
if(inArr.sort()[0]===0){
} else {
$('p').fadeIn(750)
}
});
It feels very heavy handed and I would like to know a better way. Feel free to ignore the fact that I'm using Angular and that there is an Angular way to do this. I would like to know a vanilla js or js and jquery solution to this problem that is more elegant.
Proposed improvements:
1) Inputs cached once, not every time your handler fires
2) focusout handler returns when first empty field found
3) Quick native check of input for emptiness
$(function () {
var allInput = $('input');
$('input').focusout(function () {
for (var i = 0; i < allInput.length; i++) {
if (allInput[i].value.length === 0) {
return;
}
}
$('p').fadeIn(750);
});
});
JSFiddle
There are a bunch of ways to do it
var inputs = $("input");
inputs.on("change", function () {
var allFilled = true;
inputs.each(
function(){
if(this.value.length===0){
allFilled = false;
return false; //break loop
}
}
);
$("#myParagraph").toggle(allFilled);
});

Variable in nested function returns undefined

The variable my_sound is declared in the first, outer function. So, I should be able to use it in the nested function. However the mouseout event produces no result. What am I doing wrong? Thanks for any help.
$(document).ready(function () {
var starting_pics = ["CN.gif", "EN.gif", "GN.gif"];
var starting_sounds = ["CN.mp3", "EN.mp3", "GN.mp3"];
var i = 0;
for (i = 0; i < starting_pics.length; i++) {
$("<img/>").attr("src", "images/" + starting_pics[i]).appendTo("#main").addClass("pics");
}
$("#main").on("click", ".pics", function () {
var i = $(this).index();
var my_sound =($("<audio/>").attr("src", "audio/" + starting_sounds[i])).load().get(0).play();
$("#main").on("mouseout", ".pics", function () {
$("my_sound").animate({ volume: 0 }, 1000);
});
});
});
The problem is probably that .play() doesn't return a jQuery object (or anything, for that matter, hence undefined).
Additionally, as the other comments have said, you don't want $('my_sound').whatever but rather just my_sound.whatever if it were a jQuery object, which it is not. So maybe you could try
var $my_sound = $("<audio />").attr("suchandsuch","etc");
$my_sound.load().get(0).play();
$my_sound.whatever();

Not allowing to push duplicate items into array

Basically I'm pushing containers into an array, and once one has been pushed, I don't want to allow that same one to be pushed again.
Here is my JSfiddle: http://jsfiddle.net/9Dmcg/3/
Javascript:
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').on('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
I need to find a way to work around that.
Unless there's more to that click event, you can use the .one() method in place of .on to get this functionality.
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').one('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
http://jsfiddle.net/9Dmcg/4/
Even if there were more to it, you could still use .one():
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').one('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
$(this).on("click",function(){
alert("Favorite Added!");
}).triggerHandler("click");
});
});
http://jsfiddle.net/9Dmcg/5/
Try to check for element id's I would say. Something like this:
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').bind('click', function(){
var isAdded = false;
for (var f = 0; f < favorites.length; f++) {
console.log(favorites[f].id + "|" + this.id);
if (favorites[f].id === this.id)
isAdded = true;
}
if (!isAdded)
favorites.push($(this).clone()[0])
console.log(favorites);
$('.favorite').append(favorites);
});
});
And here's working example -> http://jsfiddle.net/9Dmcg/7/
mz
This can be done using the JS Proxy (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
Following is the code to create an array which will not accept duplication entry
var arr = new Proxy([], {
get: function(target, key) {
return target[key];
},
set: function(target, key, value){
if(target.indexOf(value)>=0) return false;
target[key] = value;
return true
}
});
arr.push(100);
arr.push(100); // this will throw error
In the above code, we are modifying the default behaviour of array as the set method will be called on any modification done on the array.
You can check if the element already exists:
$(document).ready(function(){
var favorites = [];
$('.containers').on('click', function(){
var found = false;
for(var i = 0; i < favorites.length; i++)
{
if (favorites[i] == $(this)) found = true;
}
if (!found) favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
Add a class to items that have been pushed, and then do a basic check:
Instead of:
favorites.push($(this).clone())
you can do:
if( !$(this).hasClass("pushed") ) {
favorites.push( $(this).addClass("pushed").clone() );
}
Just push the element itself instead of cloning it:
favorites.push($(this))
The added benefit is that it gives the user a clue that the item has already be added.
Live demo: http://jsfiddle.net/9Dmcg/6/
Seems like your looking for Array.indexOf. It returns the index of the value on an array. Returns -1 if not found.
It is a new array method, so you will need a polyfill for it to work on old browsers.
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').on('click', function(){
var clone = $(this).clone(); // caching element
if (favorites.indexOf(clone) !== -1) {
favorites.push(clone);
}
$('.favorite').append(favorites);
});
});

Categories