Javascript/Jquery Load Callback and Writing a Base Function - javascript

This might be a possible duplicate question but I do not know how to phrase the search for it. I'm not that familiar with JS properties.
CASE:
Lets say I create some element types such as Text,Image,Audio,Video etc. and suppose I give them the same abilities such as drag, rotate, resize etc. For simplicity, lets consider this two elements: Text and Image.
PROBLEM:
Lets say I need to get the sizes of those two elements and set those to their wrapper div.
Text element is simple, i can just get the size immediately and set the size of wrapper div.
Image element waits for the load of the image and its size can be set only after the load happens i.e. in its load callback.
SIMPLE EXAMPLE
function createText(){
var aDiv = createWrapperDiv(); // <div class="element"></div>
var anElem = $('<textarea></textarea>')
aDiv.append(anElem)
// can set immediately
aDiv.attr('width', anElem.width());
aDiv.attr('height', anElem.height());
// Some other size-DEPENDENT and size-INDEPENDENT stuff can both be done here.
sizeDependentStuff();
sizeIndependentStuff();
}
function createImage(){
var aDiv = createWrapperDiv(); // <div class="element"></div>
var anElem = $('<img>')
aDiv.append(anElem)
// need to wait for element to load.
anElem.attr('src',imageUrl)
anElem.load(function(e) {
aDiv.attr('width', e.currentTarget.width());
aDiv.attr('height', e.currentTarget.height());
// Some other size-DEPENDENT stuff.
sizeDependentStuff();
});
// Some other size-INDEPENDENT stuff.
sizeIndependentStuff();
}
WHAT I WANT:
I want to define a createSimpleElement function ( something similar to a Factory) that takes type and does both sizeDependentStuff and sizeIndependentStuff so I can reuse the same code. Note that i have more than 2 types and number can increase. I can not find a suitable way to tackle the load process.
I hope it is clear and please consider that I am still in the learning process.
Thanks.

What about something like this?
function base(parentElement, element){
parentElement.append(element);
parentElement.attr('width', element.width());
parentElement.attr('width', element.width());
/*other common properties...*/
}
function createText(){
var aDiv = createWrapperDiv();
var anElem = $('<textarea></textarea>')
base(aDiv, anElem);
}
function createImage(){
var aDiv = createWrapperDiv();
var anElem = $('<img>')
anElem.attr('src',imageUrl)
anElem.load(function(e) {
base(aDiv, anElem);
});
// Some other size-INDEPENDENT stuff.
}
}

Related

Returning a value from within a class loading method (images/audio)

For my current game-development project, I'm using pure JS and HTML5 Canvas, no jQuery, nothing of the sort.
I have X images to load at the beginning of a level. So far, I've been using the load handler on them to increment a counter (which counts how many images are loaded) until the counter value is X.
Once it's done, it begins to draw things on the Canvas.
Now, I've reached the stage where I'm trying to pass as much stuff into it's own specific class as possible. My issue is that if I put all the loaders into the classes, I'm not sure how I can check if that counter is at the correct value.
My idea was to create something like this:
In GameLevel.js:
function init(player,level,actors,enemies)
var loaded_components = 0;
if (player.initializeImages==true)
loaded_components++;
if (level.initializeImages==true)
loaded_components++;
(...)
if (loaded_components==5)
initiateDrawing();
In, for example, Player.js:
initializeImages(){
loaded=0;
maxload=10;
this.idle.addEventListener("load",loadHandler);
this.idle.id = "idle";
this.idle.src = "path/idle.png";
(...)
loadHandler(ev){
if (ev.id == "idle")
loaded++;
(...)
if (loaded==maxLoad){
return true;
}
}
}
So, the issue here is: I'm not sure how to make the code stop at that test for the return value of the initializeImage() method. I want the loading of all things to be placed within the specific class of the thing being loaded, if that makes sense.
I also know that you can load things without checking, and I've been doing that with the audio to test, but it's unsafe, and I really want to change it! I'm rather new to it, but if anyone has a solution that works, I'd love to hear about it.
Thanks in advance!
Edit: Tried it, but I'm failing!
I have a class that receives a few Canvasses, draws to them, and only once these are drawn should the code move forward.
I tried making a promise within the constructor, so it would be:
constructor(layer1,layer2,layer3,layer4){
this.layer1 = layer1;
(...)
var promise = new Promise(function(resolve,reject){
**(load everything here)**
**(once everything is loaded, draw to the
multiple canvasses referenced above)**
**(all done, resolve)**
});
}
This didn't work. I tried making the promise outside, in GameLevel.js, in the following way:
(in gameLevel):
var promise = new Promise(..){
scene = new Scene(layer1, layer2 (...))
resolve("done");
promise.then(function(){
beginGame();
}
This also does not work, the promise isn't... Waiting, for the scene to finish it's full creation and initialization. What am I doing wrong?
Here is a example with loading and rendering loop.
function loadimage(src){
return new Promise(resolve=>{
let img = new Image();
img.onload = ()=>resolve(img)
img.src=src
})
}
function stageinit(){
let imagesPromise = [
loadimage('https://via.placeholder.com/150'),
loadimage('https://via.placeholder.com/300')
]
return Promise.all(imagesPromise)
}
stageinit().then(images=>{
//images are loaded, do whatever you want
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
let x = 0
function gameloop(){
requestAnimationFrame(gameloop)
ctx.fillRect(0,0,500,500)
ctx.drawImage(images[0],x,0)
ctx.drawImage(images[1],x,200)
x+=1
}
gameloop()
}
)
canvas{width:80vmin;height:80vmin;border:3px solid yellow}
<canvas width=500 height=500></canvas>

How do I call this function in other parts of my code?

Pardon me if this is a very silly question. I'm brand new to JS and I was wondering how I can use this function in other parts of my code. I looked at tutorials and other websites, but the way they define functions seems to be different than what I have here. Could anyone please nudge me in the right direction?
$('.message_div').each(function message_function()
{
console.log("One liner");
var th = $(this);
var ih = $(this).outerHeight(); // outer height
var oh = $(this).find('.message').outerHeight();
console.log("Oh", oh);
var txt = $(this).find('.message').html();
console.log("Ih", ih);
if (oh > ih)
{
th.html('');
th.html('<marquee class="message" direction="up" scrollamount="1" scrolldelay="0">' + txt + '</marquee>')
}
});
//message_function(); -----> Is this the right way?
There are several intricacies here with regards to what jQuery does. The simple way of referencing this function later on would be to store it in a variable:
function message_function()
{
console.log("One liner");
var th = $(this);
//... (rest of function omitted for brevity)
}
$('.message_div').each(message_function);//note that the function handle is used here,
//and not the result of the function which would
//have used the () to call it
///and then later on
message_function();
However, the problem here is with this. jQuery will bind this behind the scenes (which means it works fine in each), however in order to properly call the message function separately, you would need to have an element ready to bind. For example,
var element = document.querySelector("div");
message_function.call(element);
alternatively:
var element = document.querySelector("div");
var elementMessage = message_function.bind(element);
elementMessage();
Here is a broader explanation of what this is, and how jQuery interacts with it: https://stackoverflow.com/a/28443915/1026459
Inside the same file :
Move that code inside a function
Call the function
Outside of that file :
Move the function (you just created) to a .js file
Include the .js file in the desired document
Make sure the DOM elements properties match what's in the script

How to make variable accessible within jQuery .each() function?

This is and example of a frequent dilemma: how to make markup accessible inide this .each()?
I'm more interested in learning how to access outer variables from within a closure than I am in this specific issue. I could fix this problem by assigning markup from inside the each function, but I'd rather learn a more elegant way to handle this kind of problem.
// hide form & display markup
function assessmentResults(){
// get assessment responses
var markup = parseForm();
// show assessment results to user
$('#cps-assess-form fieldset').each( function() {
var q = $(this).find('.fieldset-wrapper');
var i = 0;
// hide form questions
q.slideUp();
// insert markup
$('<div>'+markup[i]+'</div>').insertAfter(q);
i++;
});
}
Read the docs, it already has an index!
.each( function(index, Element) )
No need for i
$('#cps-assess-form fieldset').each( function(index) {
var q = $(this).find('.fieldset-wrapper').slideUp();
$('<div/>').html(markup[index]).insertAfter(q);
});
The reason why yours is failing is the i is inside of the function so it is reset every iteration. You would need to move it outside of the function for it to work.

Iterate over cue method with popcorn.js

I am attempting to set multiple cue points in a video. Instead of writing each cue out, I would like to iterate over an object that has the data I need, such as the time of the cue and some info about what to do with the call back.
The problem is when I try to iterate over the object it overwrites all the cues except for the last one.
var products = myVideo.products;
var video = Popcorn('#mainVideo');
for (product in products){
var obj = products[product],
start = obj.start,
img = obj.image,
$targetDiv = $("#"+obj.targetDiv);
video.cue( start, function(){
$('<img>', {
class : "isImage",
src : 'images/' + img
}).prependTo( $targetDiv );
})
}
Any help would be greatly appreciated.
In this code, every cue has its own callback function, but every function refers to the same variable img and the same $targetDiv. By the time they run, those variables will be set to their respective values for the last item in products.
If you've ever run code through jslint and seen the warning, don't make functions in a loop, this is why. A good way to do what you're trying to do is to put those variables inside of a function that gets called immediately and returns another function, which is your callback. Like this:
function makeCallback(obj) {
return function() {
$('<img>', {
class : "isImage",
src : 'images/' + obj.img
}).prependTo( $("#"+obj.targetDiv) );
};
}
for (var product in products) {
var obj = products[product];
video.cue( obj.start, makeCallback( obj ) );
}
Alternatively, you can use forEach, which does the same thing under the hood. (Popcorn provides its own version, which handles both arrays and objects.)
Popcorn.forEach(products, function(obj) {
video.cue( start, function(){
$('<img>', {
class : "isImage",
src : 'images/' + obj.img
}).prependTo( $("#"+obj.targetDiv) );
});
});
I should note that you have another problem in this code, which is that you have Popcorn creating a new image every time you pass through the cue point. So, if the user ever skips back to replay some of the video, the images will start to pile up. Also, the images don't start loading until right when they become visible, so if there's a slow-ish network connection, the images may not show up until it's too late.
The best way to handle these is usually to create your images ahead of time but make them invisible with CSS, and then have the Popcorn events make them visible at the right time. You might consider using the image plugin, which will do most of your heavy lifting for you.

Why does onclick's handling function not work as expected?

I have the following little piece of code:
var instance = this;
window.onload = function () {
for (var i = 0; i < array.length; ++i) {
var currentDivId= array[i];
var currentDiv = document.getElementById(currentDivId);
try {
if (!currentDiv) {
throw 'Div id not found: ' + currentDivId;
}
var image = document.createElement('img');
image.src = 'img.jpg';
image.onclick = function() {
instance.doSomething(currentDivId);
};
currentDiv.appendChild(image);
}
catch(e) {
console.warn('oops');
}
}
};
This code is passed an array of id of divs. What it does is that, it renders an image at each of those divs and set their onclick property.
Say I have an array of strings: ['abc', 'xyz']
I want the code to place an image inside <div id="abc"></div> and another image inside <div id="xyz"></div>.
When you click the first image, instance.doSomething function should be called with parameter 'abc' and vice versa.
But the code does not work as expected. It always calls instance.doSomething with the last parameter in the array, in this case, 'xyz'.
I'm new to JS and still don't have a solid grasp of its inner workings. What's wrong here and how can I fix it?
Any help appreciated.
image.onclick = function() {
instance.doSomething(this.parentNode.id);
};
That should do it. Since we know that the image is inside the div we want to get at, just go one dom element up and get its id.
Welcome to the wonderful world of Javascript scoping issues. As it stands now, JS is treating your onclick code as something like "when this object is clicked, fetch the value stored in the currentDivID variable AT THE TIME THE CLICK occurs and pass it to the doSomething function".
What you should do is base the argument on the image object itself. Every DOM object knows where it is in the DOM tree, so at the time it's clicked, the onclick code should use DOM traversal operations to figure out which div it's inside of and dynamically retrieve its ID. That way you don't have to worry about binding variables and scoping issues... just figure out which div contains your image and get the ID at run time.
Try:
image.onclick = (function() {
var currentD = currentDivId;
return function() {
instance.doSomething(currentD);
}
})();
Hope it helps

Categories