How to pass nested function parametrs to main function parametrs? - javascript

I have function, that makes animation on DOM elements using animate.css when user scrolling the page on certain coordinates. It works perfectly.
$(window).scroll(function() {
$('.dev-courses__item').each(function(){
var imagePos = $(this).offset().top;
var topOfWindow = $(window).scrollTop();
if (imagePos < topOfWindow + 591) {
function slideAnimate(name,method){
if (method === undefined || method == 'default') {
method = "slideInLeft";
}
$(name).addClass(method);
};
slideAnimate('.dev-courses__item-first');
slideAnimate('.dev-courses__item-second');
slideAnimate('.dev-courses__item-third');
slideAnimate('.services-list');
};
});
});
The goal is to make function, that could be used universally with diferent selectors etc. I tried this:
function elemAnimate(selector,position,name,method){
$(window).scroll(function() {
$(selector).each(function(){
var imagePos = $(this).offset().top;
var topOfWindow = $(window).scrollTop();
if (imagePos < topOfWindow + position) {
function slideAnimate(name,method){
if (method === undefined || method == 'default') {
method = "slideInLeft";
}
$(name).addClass(method);
};
};
});
});
}
elemAnimate('.dev-courses__item', 591, '.dev-courses__item-first');
Somehow after function slideAnimate starts to work, nothing happens in console. So i figured out, that i can't pass parametrs from nested function in this way. How to do this correctly?

I think your answer is here:
JavaScript Nested Functions
All functions have access to the global scope.
In fact, in JavaScript, all functions have access to the scope "above" them.
JavaScript supports nested functions.
Nested functions have access to the scope "above" them.
https://www.w3schools.com/js/js_function_closures.asp
Your slideAnimate function is nested in an each function which is nested in the scroll function. Try un-nesting this function.
function elemAnimate(selector,position,name,method){
$(window).scroll(function() {
$(selector).each(function() {
if ($(this).offset().top < $(window).scrollTop() + position) {
slideAnimate(name,method);
}
});
});
}
function slideAnimate(name,method){
$(name).addClass(method === undefined || method === 'default' ? 'slideInLeft' : method);
}
Or, if you're only using it to add a class, just get rid of it all together...
function elemAnimate(selector,position,name,method){
$(window).scroll(function() {
$(selector).each(function() {
if ($(this).offset().top < $(window).scrollTop() + position) {
$(name).addClass(method === undefined || method === 'default' ? 'slideInLeft' : method);
}
});
});
}
Also, since you're already using jQuery, have you thought about using jQuery UI? It makes animating things pretty easy.
https://api.jqueryui.com/slide-effect/

Related

Is there any way to change the value of an object property dynamically? JavaScript

I am pretty sure I am doing something wrong, but still is there any way I can set a value according to the window size?
Here is a sample code:
var setNewValue;
$(window).resize(function() {
if($(window).width() < 768) {
setNewValue = "less";
} else {
setNewValue = "more";
}
});
var createObject = function(property) {
this.property = property;
this.showProperty = function() {
document.write(this.property);
};
};
var example = new createObject(setNewValue);
example.showProperty();
// undefined
var createObject = function(property) {
return {
property: property,
getProperty: function() {
return this.property;
}
};
};
var example = createObject();
$(window).resize(function() {
example.property = $(window).width() < 768 ? "less" : "more";
document.write(example.getProperty());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Looks like you have a race condition here. When the script is run, what's happening is that:
setNewValue variable is defined (it's value is undefined at the moment)
resize event handler is defined
createObject() is called with current setNewValue value which is still undefined
Unless there is no resize event triggered between steps 2. and 3., you'll always end up with setNewValue = undefined when calling createObject().
I'm not sure what's the big picture here as you've posted only a snippet of your code, but you need to make sure that setNewValue variable is already set when accessing it -- for example by explicitly setting it somewhere at the beginning of the script.

add functions to javascript / JQuery object

$(".box").each(function(){
$(this).distance_left = function() {
return $(this).offset().left - $(this).parent().offset().left;
}
$(this).distance_top = function() {
return $(this).offset().top - $(this).parent().offset().top;
}
});
When I call distance_left or distance_top on a .box object, I simply get a box.distance_left is not a function. Why?
You will need to extend the prototype:
$.fn.distance_left = function() { return -1; };
and then you can use it on all jQuery objects:
$('#myId').distance_left(); // -1
Anyway for your particually case you can use
$(this).position().left;
$(this).position().top;
Because each time you create a jQuery wrapper a new object is returned, so even though you assign the properties to a wrapper instance it won't be available in another one.
One easy way to test it is compare $(this) == $(this) which will return false.
Demo: Fiddle
The solution here is to use a plugin model as given below.
$.fn.distance_left = function () {
return $(this).offset().left - $(this).parent().offset().left;
}
$.fn.distance_top = function () {
return $(this).offset().top - $(this).parent().offset().top;
}
You could do
var that = $(this);
Because this frequently changes when changing scope by using a new function, you cannot simply access the original value by using it. Aliasing it to that allows you still to access the original value of this.
So your code would be
$(".box").each(function(){
var that=$(this);
that.distance_left = function() {
return that.offset().left - that.parent().offset().left;
}
that.distance_top = function() {
return that.offset().top - that.parent().offset().top;
}
});

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).

Is $(window).scrollTop() the correct statement?

I'm trying to build a function like this one:
var t =$('#top');
var q1=$('#fe1');
var q2=$('#fe2');
var q3=$('#fe3');
var q4=$('#fe4');
var q5=$('#fe5');
var win = $(window);
var doc=$(document);
var wins = win.scrollTop();
var docs = doc.scrollTop();
function next (){
if (wins == docs) {
q1.ScrollTo();
}
else if (wins == q1.scrollTop()) {
q2.ScrollTo();
}
else if (wins == q2.scrollTop()) {
q3.ScrollTo();
}
else if (wins == q3.scrollTop()) {
q4.ScrollTo();
}
else if (wins == q4.scrollTop()) {
q5.ScrollTo();
}
}
I want to go scrolling to the next section. To do so, the code checks in which section I am so it knows which section to scroll to. But I think $(window).scrollTop() is not what I am looking for.
I want a statement that returns the distance between the top of the page and the top of what I am displaying. Maybe i have to do a more complex operation. Do you know how can i get this?
Thanks.
This problem is quite simple to solve even without jQuery or similar in plain JavaScript. Here it is for your example:
var next = (function (sections) {
function getTop(node) {
return node ? node.offsetTop + getTop(node.offsetParent) : 0;
}
return function () {
var i, nodeTop, top = window.pageYOffset;
for (i = 0; i < sections.length; i += 1) {
nodeTop = getTop(document.getElementById(sections[i]));
if (nodeTop > top) {
window.scrollTo(window.pageXOffset, nodeTop);
return;
}
}
};
}(['top', 'fe1', 'fe2', 'fe3', 'fe4', 'fe5']));
The code is quite general, so you can pass any section ids you want (they just need to appear in the correct order).
We use two standard DOM properties/functions here window.pageYOffset and window.scrollTo() to get and set vertical offset of the window (window.pageXOffset is used to keep horizontal offset the same). To get the vertical offset of the section start I defined getTop function using simple recursion (jQuery uses similar code IMHO).
Resulting function next() is defined in a self-invoking closure to hide the implementation and helper function. To use it after this code is run, you simply call
next();
I tested this code, so I am quite confident it works :).

Abstracting a global variable to a property?

I'm reading DOM Scripting and have a beginner question about the abstraction below. The original code didn't include "clearTimeout", and "movement" was declared as a global variable. While the code ran fine, it wasn't a smooth animation which is why clearTimeout was added. My question however, is why I can't just test for "movement" and when it fails (on the first mouseover call to moveElement) continue on through the rest of the function? If I keep "movement" as a global variable, rather than make it a property, the code doesn't run at all?
If it helps to see the other JS and HTML, I've plugged the remaining code into jsFiddle.
function moveElement(elementID,final_x,final_y,interval) {
if (!document.getElementById) return false;
if (!document.getElementById(elementID)) return false;
var elem = document.getElementById(elementID);
if (elem.movement) { //Why can't I use "movement"?
clearTimeout(elem.movement);
}
var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);
if (xpos == final_x && ypos == final_y) {
return true;
}
if (xpos < final_x) {
xpos++;
}
if (xpos > final_x) {
xpos--;
}
if (ypos < final_y) {
ypos++;
}
if (ypos > final_y) {
ypos--;
}
elem.style.left = xpos + "px";
elem.style.top = ypos + "px";
var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
elem.movement = setTimeout(repeat,interval); //Originally global property
}​
A property can never be undefined, only have a undefined as a value. In contrast, a variable may be undefined (not defined) or have undefined as a value.
When you say if (movement), in the case that movement is not defined, it will throw an exception.
When you say if (elem.movement), in teh case that elem.movement is not defined, it will evaluate to false and fail the condition without exceptions.
If you want to use movement as a global variable, you must first declare it before attempting to read from it via:
var movement;
function moveElement(elementID,final_x,final_y,interval) {
...
}
Alternatively, you could try to read the global movement as a property, since all global variables are just properties of the window object (in a browser):
if (window.movement) { // this will never throw an exception
clearTimeout(movement);
}
And finally, you could switch your if statement to one that protects itself against these exceptions via the typeof operator:
if (typeof movement !== 'undefined') {
clearTimeout(movement);
}

Categories