Calling function in a callback before defining it in javascript? - javascript

I am having some trouble wrapping my head around this. I have a web application that is almost entirely built with javascript. It starts out with a basic template, then starts adding content to it as the user interacts. I am trying to use Greensock as the animation library which has the ability to use a progress slider to show how far you are in the animation, see the second box here: https://greensock.com/timelinemax
The issue is that it uses a callback onUpdate that is supposed to run that function on each frame. Then I can use it to make the slider track with the animation.
var mainTL = new TimelineLite({onUpdate:updateSlider});
function updateSlider() {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
This would work - except that the slider object doesn't exist yet. I don't know why, this is some of the last code to be included in the file, but I get a couple errors in the console log just loading the page `ReferenceError: sliderTimeline is not defined' but then everything works.
To try getting away from those errors, I tried to do it like this:
var mainTL = new TimelineLite({onUpdate:updateSlider});
$( document ).ready(function() {
function updateSlider() {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
});
except now it fails because the updateSlider' function hasn't been defined, and it fails to start at all. I could put them both in a$( document ).ready(function()`, but then they become local functions / variables and the 5 other javascript files I am working with don't have access to them.
Do I have to live with the errors, or is there something I am not thinking of?

You can check whether sliderTimeline exists before trying to call it. For example change function updateSlider() to:
function updateSlider() {
if (typeof sliderTimeline !== 'undefined') {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
}
Or if you know that sliderTimeline is declared, but not assigned yet:
function updateSlider() {
if (sliderTimeline) {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
}
Note that this works because onUpdate is called frequently, so it will eventually be called when sliderTimeline is eventually defined.
Edit:
Additionally, you can assign global variables inside $( document ).ready() as long as you declare them outside of the function.
For example:
var mainTL;
var updateSlider;
$( document ).ready(function() {
updateSlider = function () {
sliderTimeline.noUiSlider.set( mainTL.progress());
};
mainTL = new TimelineLite({onUpdate: updateSlider});
});

If you look at their codepen page http://codepen.io/GreenSock/pen/FnsqC/ they have:
var tl = new TimelineMax({delay:0.5, repeat:3,
repeatDelay:2, onUpdate:updateStats,
onRepeat:updateReps, onComplete:restart});
function updateReps() {
reps++;
repeatCount.innerHTML = reps;
}
function updateStats() {
time.innerHTML = tl.time().toFixed(2);
totalTime.innerHTML = tl.totalTime().toFixed(2);
progress.innerHTML = tl.progress().toFixed(2);
totalProgress.innerHTML = tl.totalProgress().toFixed(2);
}
Meaning that you need to define the callback function of onUpdate.

Related

Javascript TypeError: Cannot read property when nested inside a window.onload function

SOLVED: Will move to close post but for future reference here is what happened. The children div in the parent div I was referencing were dependent on an AJAX call. So I set an interval to keep checking if the element existed with...
var checkExist = setInterval(function() {
if ($('parent').length) {
// do work on child elements
clearInterval(checkExist);
}
}, 100);
ORIGINAL POST BELOW
I am getting into writing some scripts to make testing tasks simple. I need to wait to run some of these until the page is fully loaded. Naturally I nest my code in ...
window.onload = function () { code in here }
So I am trying to store an element in a var that I know exists and I know works without being nested in onload. The var is written like this.
var element = document.getElementById('userPanel').getElementsByTagName('div')[0];
I can run this in console and it returns the element I need. When I run my script nested in the onload function. It throws a type error. Is there some fundamental principle of javascript I am overlooking here? Thanks!
For sake of clarity here is the whole code section glued together
window.onload = function () {
var element = document.getElementById('userPanel').getElementsByTagName('div')[0];
}
SOLVED: Will move to close post but for future reference here is what happened. The children div in the parent div I was referencing were dependent on an AJAX call. So I set an interval to keep checking if the element existed with...
var checkExist = setInterval(function() {
if ($('parent').length) {
// do work on child elements
clearInterval(checkExist);
}
}, 100);

SCOPE - Get elements by classname and call a Jquery prototype function

I'm trying to adapt a videoplayer to meet my needs (resizing).
At this moment almost everything is ok, but I have a problem when 2 or more players are showed in the same page (the jquery reference is always pointing to the last player).
To fix it, I'm trying to use jquery selector:
$('className').each(function() { /* call resize function */ } );
Now I have a problem because I can call resize function using MyObject1.resize(); but the selector only returns div tags (id="obj1", id="obj2") and I don't now how can I get MyObject1, MyObject2 and so on, so I could call resize function for every object.
So, how can I call a function defined like Object.prototype.resize = function() {...} from the jquery selector each loop?
UPDATE ****************
Hi, thanks for all your replies, Im not an js expert, sorry :)
Im trying to adapt leanback HTML5 Video Player which js file start as :
"use strict"
;(function(window, undefined)
{
var document = window.document, navigator = window.navigator, location = window.location, ua$ = navigator.userAgent;
var LBP = function(o, v, h5o) { ... };
LBP.prototype.initializeKeys = function() { ... }
...
LBP.prototype.initializeAPlayer = function() {
LBP.prototype.initializeVPlayer = function() {
LBP.prototype.resize = function() { .. }
I have 3 players in html (classname = "vPlayer") so I want to call the resize function from the three player, not only from the last.
Jquery Selectors returns something like div tags so I don't know how to call functions from the LBP objects

WinJS DOM-Loading

I've programmed an WinJS-App (Metro-App) for Windows 8. The problem is, that I get "nullpointer exception" as soon as I try to access a DOM(HTML)-Element like a div.
The problem code is this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
document.getElementById("inhalt").innerHTML = "test"; // causes NullpointerException
}
});
But when I do this instead, there is no problem. But I don't want to wait 3 seconds each time.
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
window.setTimeout(function() { document.getElementById("inhalt").innerHTML = "test"; }, 3000);
}
});
Why is NullPointerException thrown and how can I fix it?
This is probably because ready() is called before your page is parented into the DOM, so document.getElementById can't find it. You are being passed the root element in the ready function, so you can instead do:
element.querySelector('#inhalt').innerHTML = "test";
And that should work. It's a best practice for pages to not use ids inside of pages though, so just change it to a class class="inhalt" and make it element.querySelector('.inhalt').

See whether object is undefined before executing script

I'm currently loading a custom.js file on my site and it calls various functions. However, to keep the size down I only load the libraries needed on certain pages.
Because of this, since the custom.js file is loaded on every page and it calls functions that the particular page may not have, I get undefined is not a function errors on my site on certain pages.
What I would like to be able to do is determine if something is defined before executing the code to keep the errors from popping up.
For an example, I'm using Jarallax (http://www.jarallax.com/) on my front page only with the following:
var jarallax = new Jarallax();
jarallax.addAnimation('div#bigSlider',[{progress:'0%',marginTop:'0px'},{progress:'100%', marginTop:'-200px'}]);
Since Jarallax is only loaded on the homepage and no others I get the undefined function error on all pages but the hompeage. How could I first confirm Jarallax is loaded before attempting to execute the code?
Since referring to undefined variables raises a ReferenceError exception, you could use a try/catch block to handle the exception.
try {
var jarallax = new Jarallax();
}
catch (e) {
// desired behavior for this situation.
}
More on try/catch blocks.
However, to keep the size down I only load the libraries needed on
certain pages. Because of this I get "undefined is not a function"
errors on my site on certain pages.
So this means you're not doing it properly on every page?
You could solve this by using a wrapper object or class:
(function($){
var wrapper = {
init: function(){
var jarallax;
if (typeof Jarallax == 'function'){
jarallax = new Jarallax();
jarallax.addAnimation('div#bigSlider',[{progress:'0%',marginTop:'0px'},{progress:'100%', marginTop:'-200px'}]);
}
}
};
// once the DOM is read
$(function(){
wrapper.init();
});
}(window.jQuery));
By stalling the init function on the DOM ready, you can be certain the script is loaded if you make sure the script tag for Jarallax is added before the wrapper in the HTML. In any other case the init function won't do a thing.
if (typeof jarallax === "undefined") {
var jarallax = {
obj: {},
return {
obj;
};

What is the preferred pattern for re-binding jQuery-style UI interfaces after AJAX load?

This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here

Categories

Resources