jquery plugin, settimeout and div not being append, simple weird case - javascript

I'm writing a jquery plugin the code below is not working (I mean the setTimeout is working but nothing is append)
var self = this;
for (var i=0; i<=10; i++) {
setTimeout(function() {
self.append(bubble);
}, 1000);
}
And the code below is working:
for (var i=0; i<=10; i++) {
this.append(bubble);
}
this is a jquery selection. I really don't get what's going on. It can't be scope issue .. can it be ? I don't get it. Thanks in advance for you help
Edit: bubble is a simple div (" ")
Below the whole plugin code:
(function($) {
'use strict';
$.fn.randomBubble = function(options) {
var self = this;
var settings = $.extend({
color: 'blue',
backgroundColor: 'white',
maxBubbleSize: 100
}, options);
var frame = {
height: this.height(),
width: this.width(),
}
var bubble = "<div class='randomBubble'> </div>";
this.getLeft = function(width) {
var left = Math.random() * frame.width;
if (left > (frame.width / 2)) {
left -= width;
} else {
left += width;
}
return left
}
this.getTop = function(height) {
var top = Math.random() * frame.height;
if (top > (frame.height / 2)) {
top -= height;
} else {
top += height;
}
return top
}
this.removeBubbles = function() {
var currentBubbles = this.find('.randomBubble');
if (currentBubbles.length) {
currentBubbles.remove();
}
}
window.oh = this;
for (var i = 0; i <= 10; i++) {
var timer = Math.random() * 1000;
setTimeout(function() {
window.uh = self;
self.append(bubble);
console.log("oh");
}, 1000);
}
this.randomize = function() {
//self.removeBubbles();
var allBubbles = this.find('.randomBubble');
allBubbles.each(function(i, el) {
var height = Math.random() * settings.maxBubbleSize;
var width = height;
$(el).css({
color: settings.color,
backgroundColor: settings.backgroundColor,
zIndex: 1000,
position: 'absolute',
borderRadius: '50%',
top: self.getTop(height),
left: self.getLeft(width),
height: height,
width: width
});
});
}
this.randomize();
//var run = setInterval(self.randomize, 4000);
return this.find('.randomBubble');
}
})(jQuery);

Because the bubbles are appended later due to the setTimeout(), this selector in your randomize() function comes up empty:
var allBubbles = this.find('.randomBubble');
That is why appending them in a simple for loop works fine.
If you really want to use the setTimout() to append your bubbles, one option is to style them when you add them:
setTimeout(function() {
var height = Math.random() * settings.maxBubbleSize;
var width = height;
var b = $(bubble).css({
color: settings.color,
backgroundColor: settings.backgroundColor,
zIndex: 1000,
position: 'absolute',
borderRadius: '50%',
top: self.getTop(height),
left: self.getLeft(width) ,
height: height,
width: width
});
self.append(b);
}, 1000);
Fiddle

Is it because you still call randomize() right away, even when you postpone the creation for one second?
You will also return an empty selection in that case, for the same reason.
Also, you probably want to use the timer variable in setTimeout() instead of hardcoding all to 1000 ms?

this is a javascript selection, the selector in jquery is $(this)
$.fn.randomBubble = function(options) {
var self = $(this);
};

Related

How to call a function only for the first time - JavaScript

Note: I need to achieve this with pure javascript, I know there is a .one() method in jquery to do this, but I need the same output in pure javascript.
Scenario: I am trying to call a function when a user scrolls and reaches to the 3/4 part or more of the page, but the problem rises when user reaches that part, We all know they can't be pixel perfect so, after the condition is met, the function gets executed per pixel scroll.
I want that to execute only once the condition is met, then add a section at the bottom of the page, and then again user should reach the bottom and the function should get executed only once and so on...
Snippet:
var colors = ['skyblue', 'powderblue', 'lightgreen', 'indigo', 'coral'];
var addPage = function() {
var page = document.createElement('div');
page.classList.add('page');
page.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
document.body.append(page);
console.log('added a new page');
}
var scrollAndAdd = function() {
if (window.scrollY < (window.innerHeight * (3 / 4))) {
// execute addPage only once for each attempt; it's creating infinite pages
addPage();
}
}
window.addEventListener('scroll', scrollAndAdd);
* {
margin: 0;
padding: 0;
}
.page {
height: 100vh;
}
<div class='page' style='background-color: lightgreen'></div>
<div class='page' style='background-color: skyblue'></div>
You don't really need logic to run the function just once; instead, use a different expression to determine whether to add the page. Once the page is added that same expression should no longer evaluate to true until more scrolling is done.
NB: I also changed a bit the random pick logic.
var colors = ['powderblue', 'lightgreen', 'indigo', 'coral', 'skyblue'];
var addPage = function() {
var page = document.createElement('div');
page.classList.add('page');
// Make sure the same color is not selected twice in sequence:
var colorIndex = Math.floor(Math.random() * (colors.length-1));
var color = colors.splice(colorIndex,1)[0];
colors.push(color);
page.style.backgroundColor = color;
document.body.append(page);
}
var scrollAndAdd = function() {
if (window.scrollY > document.body.clientHeight - window.innerHeight - 10) {
addPage();
}
}
window.addEventListener('scroll', scrollAndAdd);
* {
margin: 0;
padding: 0;
}
.page {
height: 100vh;
}
<div class='page' style='background-color: lightgreen'></div>
<div class='page' style='background-color: skyblue'></div>
I hope it will help you:
var colors = ['skyblue', 'powderblue', 'lightgreen', 'indigo', 'coral'];
var addPage = function() {
var page = document.createElement('div');
page.classList.add('page');
page.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
document.body.append(page);
console.log('added a new page');
}
var scrollAndAdd = function() {
var a = document.body.clientHeight - window.innerHeight * (5 / 4)
if (window.scrollY > a) {
addPage();
}
}
window.addEventListener('scroll', scrollAndAdd);
* {
margin: 0;
padding: 0;
}
.page {
height: 100vh;
}
<div class='page' style='background-color: lightgreen'></div>
<div class='page' style='background-color: skyblue'></div>
Well how about curry it up with local flag.
var colors = ['skyblue', 'powderblue', 'lightgreen', 'indigo', 'coral'];
const localCurry = function(func, immediateAction) {
var flag;
return function() {
var context = this, args = arguments;
var callNow = immediateAction && !flag;
flag = true;
if (callNow) func.apply(context, args);
}
}
var addPage = localCurry(function() {
var page = document.createElement('div');
page.classList.add('page');
page.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
document.body.append(page);
console.log('added a new page');
}, true);
var scrollAndAdd = function() {
var a = document.body.clientHeight - window.innerHeight * (5 / 4)
if (window.scrollY > a) {
addPage();
}
}
window.addEventListener('scroll', scrollAndAdd);
Now you do have option to reset the flag based on timer or custom logic.

wookmark plugin including grid as javascript

I have a question about the topic.
I use stackover at the first time.
I use JS wookmark plugin. by the way I make web pages.
One of pages with sidebar with open and close.
When I close the left sidebar, It makes a space left of article.
Usually It resizes width on no space. The open sidebar size is 260px. The close size is 70.
I use these but no action.
$(document).ready(function() {
var article_list = $("#grid-container article").get();
var outer = $('<div class="grid-outer"></div>').get(0);
var nowbox = false;
for ( var i = 0; i < article_list.length; i++ ) {
if($(article_list[i]).hasClass('area')){
nowbox = false;
$(outer).append(article_list[i]);
}
if($(article_list[i]).hasClass('area-box')) {
if(!nowbox) {
nowbox = $('<div class="gridbox"></div>').get(0);
$(outer).append(nowbox);
}
$(nowbox).append(article_list[i]);
}
}
$("#grid-container").empty();
$("#grid-container").append(outer);
var options = {
offset: 20, // Optional, the distance between grid items
autoResize: true,
fillEmptySpace: true,
};
var resizeTimer = null;
var gridarr = [];
var gridboxlist = $('.gridbox').get();
for ( var i = 0; i < gridboxlist.length; i++ ) {
gridarr[i] = new Wookmark( gridboxlist[i], options);
}
resize();
resizeTimer = setTimeout(function() {
resize();
}, 0);
$(window).resize(function(){
clearTimeout(resizeTimer);
resize();
resizeTimer = setTimeout(function() {
resize();
}, 0);
})
function resize(){
var gridboxlist = $('.gridbox').get();
$(gridboxlist).each(function(){
var window_w = window.outerWidth;
var outer_w = $(this).width();
if( window_w < 1400) {
var w = Math.floor((( outer_w - ( 20 * 1 )) / 2));
$(this).find('.area-box').css({
'width': w+'px'
});
} else {
var w = Math.floor((( outer_w - ( 20 * 2 )) / 3));
$(this).find('.area-box').css({
'width': w+'px'
});
}
});
if( 0 < gridarr.length ){
refresh();
}
}
function refresh(){
for ( var i = 0; i < gridarr.length; i++ ) {
gridarr[i].layout(true);
}
}
});

How to do something everytime .animate repetition ends

var track = Ti.UI.createView({
width: 100, height: 30,
backgroundColor: '#e8e8e8',
top: '30%'
});
var progress = Ti.UI.createView({
left: 0,
width: 1, height: 30,
backgroundColor: '#00c36a'
});
track.add(progress);
Ti.UI.currentWindow.add(track);
Ti.UI.currentWindow.addEventListener('open', function () {
progress.animate({
width: 100,
duration: 10000,
repeat: 6
});
I have made a custom progress bar using two views and .animate function. How do I do implement some functionality everytime a repetition of progress.animate() is completed
Here is an example of JQuery animation Complete callback.
var cbFunction = function(){
//Animation Complete Callback function
alert('animation completed!');
}
//Test buntton click event
$(".testbutton").on('click',function(){
//Launch animation
$(".test").animate({width:100},1000,cbFunction);
});
.test{
background-color:#ff0000;
width:200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class="testbutton">TEST</button>
<div class="test">-</div>
Try this:
var repeat = 6;
var rep = 0;
var autoProgress = function() {
createAnimation(progress, function() {
alert('complete nÂȘ-> ' + rep);
rep++;
if (rep < repeat) {
autoProgress();
}
});
};
/**
* create animation to view
* #param Ti.UI.View view
* #param {Function} callback
*/
var createAnimation = function(view, callback) {
callback = _.isFunction(callback) ? callback : function() {
};
//create animate
var animate = Titanium.UI.createAnimation({
width : 100,
duration : 10000,
});
/**
* Fired when the animation complete.
*/
animate.addEventListener('complete', function() {
view.width = 0;
callback();
});
view.animate(animate);
};
Ti.UI.currentWindow.addEventListener('open', autoProgress);

Hook jQuery validation message changes

I want to display my jQuery validation messages in a tooltip. In order to accomplish this, I started out by adding the following CSS rules to my stylesheet:
fieldset .field-validation-error {
display: none;
}
fieldset .field-validation-error.tooltip-icon {
background-image: url('/content/images/icons.png');
background-position: -32px -192px;
width: 16px;
height: 16px;
display: inline-block;
}
and a very small piece of JS code:
; (function ($) {
$(function() {
var fields = $("fieldset .field-validation-valid, fieldset .field-validation-error");
fields.each(function() {
var self = $(this);
self.addClass("tooltip-icon");
self.attr("rel", "tooltip");
self.attr("title", self.text());
self.text("");
self.tooltip();
});
});
})(jQuery);
The issue is that I now need to capture any event when the validation message changes, I've been looking at the source for jquery.validate.unobtrusive.js, and the method I'd need to hook to is the function onError(error, inputElement) method.
My tooltip plugin works as long as I've an updated title attribute, the issue comes when the field is revalidated, and the validation message is regenerated, I would need to hook into that and prevent the message from being put out there and place it in the title attribute instead.
I want to figure out a way to do this without modifying the actual jquery.validate.unobtrusive.js file.
On a second note, how could I improve this in order to leave the functionality unaltered in case javascript is disabled?
Ok I went with this, just in case anyone runs into this again:
; (function ($) {
$(function() {
function convertValidationMessagesToTooltips(form) {
var fields = $("fieldset .field-validation-valid, fieldset .field-validation-error", form);
fields.each(function() {
var self = $(this);
self.addClass("tooltip-icon");
self.attr("rel", "tooltip");
self.attr("title", self.text());
var span = self.find("span");
if (span.length) {
span.text("");
} else {
self.text("");
}
self.tooltip();
});
}
$("form").each(function() {
var form = $(this);
var settings = form.data("validator").settings;
var old_error_placement = settings.errorPlacement;
var new_error_placement = function() {
old_error_placement.apply(settings, arguments);
convertValidationMessagesToTooltips(form);
};
settings.errorPlacement = new_error_placement;
convertValidationMessagesToTooltips(form); // initialize in case of model-drawn validation messages at page render time.
});
});
})(jQuery);
and styles:
fieldset .field-validation-error { /* noscript */
display: block;
margin-bottom: 20px;
}
fieldset .field-validation-error.tooltip-icon { /* javascript enabled */
display: inline-block;
margin-bottom: 0px;
background-image: url('/content/images/icons.png');
background-position: -32px -192px;
width: 16px;
height: 16px;
vertical-align: middle;
}
I'll just include the tooltip script I have, since it's kind of custom-made (though I based it off someone else's).
; (function ($, window) {
$.fn.tooltip = function (){
var classes = {
tooltip: "tooltip",
top: "tooltip-top",
left: "tooltip-left",
right: "tooltip-right"
};
function init(self, tooltip) {
if ($(window).width() < tooltip.outerWidth() * 1.5) {
tooltip.css("max-width", $(window).width() / 2);
} else {
tooltip.css("max-width", 340);
}
var pos = {
x: self.offset().left + (self.outerWidth() / 2) - (tooltip.outerWidth() / 2),
y: self.offset().top - tooltip.outerHeight() - 20
};
if (pos.x < 0) {
pos.x = self.offset().left + self.outerWidth() / 2 - 20;
tooltip.addClass(classes.left);
} else {
tooltip.removeClass(classes.left);
}
if (pos.x + tooltip.outerWidth() > $(window).width()) {
pos.x = self.offset().left - tooltip.outerWidth() + self.outerWidth() / 2 + 20;
tooltip.addClass(classes.right);
} else {
tooltip.removeClass(classes.right);
}
if (pos.y < 0) {
pos.y = self.offset().top + self.outerHeight();
tooltip.addClass(classes.top);
} else {
tooltip.removeClass(classes.top);
}
tooltip.css({
left: pos.x,
top: pos.y
}).animate({
top: "+=10",
opacity: 1
}, 50);
};
function activate() {
var self = $(this);
var message = self.attr("title");
var tooltip = $("<div class='{0}'></div>".format(classes.tooltip));
if (!message) {
return;
}
self.removeAttr("title");
tooltip.css("opacity", 0).html(message).appendTo("body");
var reload = function() { // respec tooltip's size and position.
init(self, tooltip);
};
reload();
$(window).resize(reload);
var remove = function () {
tooltip.animate({
top: "-=10",
opacity: 0
}, 50, function() {
$(this).remove();
});
self.attr("title", message);
};
self.bind("mouseleave", remove);
tooltip.bind("click", remove);
};
return this.each(function () {
var self = $(this);
self.bind("mouseenter", activate);
});
};
$.tooltip = function() {
return $("[rel~=tooltip]").tooltip();
};
})(jQuery, window);

Javascript slider pause function on hover

Looking for a little help with this slider that came with a Magento Template I purchased.
I'm trying to add a pause on hover and resume on mouse out but am very new to getting my hands this dirty with the JavaScript.
Hoping for a push in the right direction
Here is the code I'm working with
function decorateSlideshow() {
var $$li = $$('#slideshow ul li');
if ($$li.length > 0) {
// reset UL's width
var ul = $$('#slideshow ul')[0];
var w = 0;
$$li.each(function(li) {
w += li.getWidth();
});
ul.setStyle({'width':w+'px'});
// private variables
var previous = $$('#slideshow a.previous')[0];
var next = $$('#slideshow a.next')[0];
var num = 1;
var width = ul.down().getWidth() * num;
var slidePeriod = 3; // seconds
var manualSliding = false;
// next slide
function nextSlide() {
new Effect.Move(ul, {
x: -width,
mode: 'relative',
queue: 'end',
duration: 1.0,
//transition: Effect.Transitions.sinoidal,
afterFinish: function() {
for (var i = 0; i < num; i++)
ul.insert({ bottom: ul.down() });
ul.setStyle('left:0');
}
});
}
// previous slide
function previousSlide() {
new Effect.Move(ul, {
x: width,
mode: 'relative',
queue: 'end',
duration: 1.0,
//transition: Effect.Transitions.sinoidal,
beforeSetup: function() {
for (var i = 0; i < num; i++)
ul.insert({ top: ul.down('li:last-child') });
ul.setStyle({'position': 'relative', 'left': -width+'px'});
}
});
}
function startSliding() {
sliding = true;
}
function stopSliding() {
sliding = false;
}
// bind next button's onlick event
next.observe('click', function(event) {
Event.stop(event);
manualSliding = true;
nextSlide();
});
// bind previous button's onclick event
previous.observe('click', function(event) {
Event.stop(event);
manualSliding = true;
previousSlide();
});
// auto run slideshow
new PeriodicalExecuter(function() {
if (!manualSliding) nextSlide();
manualSliding = false;
}, slidePeriod);
}
Now I'm guessing the best way would be to manipulate a hover or mouseover observer similar to the next and previous ones to stop and start but I'm just not sure on how to set this up.
Would appreciate a push in the right direction!
Edit ....
So I'm getting much closer but I seem to have a problem yet hopefully someone who know about prototype can help.
I got it to work by adding this variable
var stopSliding = false;
and then adding an if like so
function nextSlide() {
if (!stopSliding) {
new Effect.Move(ul, {
x: -width,
mode: 'relative',
queue: 'end',
duration: 1.0,
//transition: Effect.Transitions.sinoidal,
afterFinish: function() {
for (var i = 0; i < num; i++)
ul.insert({ bottom: ul.down() });
ul.setStyle('left:0');
}
});
}
}
// previous slide
function previousSlide() {
if (!stopSliding) {
new Effect.Move(ul, {
x: width,
mode: 'relative',
queue: 'end',
duration: 1.0,
//transition: Effect.Transitions.sinoidal,
beforeSetup: function() {
for (var i = 0; i < num; i++)
ul.insert({ top: ul.down('li:last-child') });
ul.setStyle({'position': 'relative', 'left': -width+'px'});
}
});
}
}
then
ul.observe('mouseover', function(event) {
stopSliding = true;
});
ul.observe('mouseout', function(event) {
stopSliding = false;
});
This method works but only Safari will auto start my slideshow now and firefox needs interaction to trigger a start.
However I did find that switching the var to true at the start and switching around the order of the mouseovers it then auto starts fine in Firefox and not in Safari.
Had enough for this evening.
So I managed to get it to work in both Firefox, Safari, IE etc using:
Event.observe( window, 'load', function() { stopSliding = false; });
This ensures that my variable "stopSliding" is set.

Categories