Continuing for loop only when image loads - javascript

function readXML(){
var xmlDoc = loadXML();
var x = xmlDoc.getElementsByTagName("image");
for (i = 0; i < x.length; i++) {
if (i == 600){
break;
}
var path = x[i].getElementsByTagName("path")[0].childNodes[0].nodeValue;
var title = x[i].getAttribute("class");
this.imageArray.push(new InfoImage(path,title));
while(this.imageArray[i].isImageLoaded() == false); //something like this
console.log(this.imageArray[i].getMaxPixels());
}
}
function InfoImage(path,title){
this.path = path;
this.title = title;
this.color = undefined;
this.maxPixels = undefined;
this.imageLoaded = false;
this.init = function(){
var canvas = document.querySelector("canvas");
var img_Color = new Image_Processing_Color(canvas);
var img = new Image();
var info_image = this;
img.onload = function () {
img_Color.init(img);
info_image.color = img_Color.getDominantColor();
info_image.maxPixels = img_Color.getDominantColorPixels();
info_image.imageLoaded = true;
};
img.src = path;
};
this.isImageLoaded = function(){
return this.imageLoaded;
}
this.getPath = function(){
return this.path;
};
this.getTitle = function(){
return this.title;
};
this.getColor = function(){
return this.color;
};
this.getMaxPixels = function(){
return this.maxPixels;
};
this.init();
}
I want my for loop to only continue to the next iteration when img.onLoad completes. The while i'm using blocks the code and doesn't let img.onLoad complete. Is there a way to do this? Without changing the code structure.

You can check the .complete property of an img:
var x = document.getElementById("myImg").complete;
the result can be true or false

If I understood you correctly, I believe your best option is to use async.parallel (if you want it to be serial, they have a function for it - series).
This function can perform a series of tasks for you and issue a callback on complete (in which you get informed of errors).

Related

How can I do this dynamically?

I am working on writing my code this way, but I want to do it in a loop. I want to be dynamic. How can I do it?
$("#sound-player").attr("src",secilen[0]);
var x = document.getElementById("sound-player");
x.play();
x.onended = function() {
$("#sound-player").attr("src",secilen[1]);
var x = document.getElementById("sound-player");
x.play();
x.onended = function() {
$("#sound-player").attr("src",secilen[2]);
var x = document.getElementById("sound-player");
x.play();
x.onended = function() {
$("#sound-player").attr("src",secilen[3]);
var x = document.getElementById("sound-player");
x.play();
x.onended = function() {
alert("bitti");
}
}
}
}
You can use recursion:
/* Cache the player. */
var x = document.getElementById("sound-player");
/* Create an IIFE and execute it recursively. */
(function playRecursively (current, last) {
x.src = secilen[current];
x.play();
x.onended = function() {
/* Play the next, if the current is not the last. */
if (current < last) playRecursively(++current, last);
else alert("bitti");
};
})(0, secilen.length - 1);
Reuse your variable x, instead o redefining it.
var player = document.getElementById('sound-player');
var i = 0;
function playSource(src){
// change the src attribute and play.
if (secilen[scr] !== undefined) {
player.setAttribute('src', secilen[src]);
player.play();
}
else {
alert('bitti')
}
}
player.onended = function(){
i++;
playSource(i);
}
playSource(i); //start playing the first sound

JQuery and XHR Request asyncronious issues

I'm trying to manage to apply an generated image of an audio graph for every div that a music url is found with a Google Chrome Extension.
However, the process of downloading the music from the url and processing the image, takes enough time that all of the images keep applying to the last div.
I'm trying to apply the images to each div as throughout the JQuery's each request. All the div's have the /renderload.gif gif playing, but only the last div flashes as the images finished processing one by one.
Example being that the src is being set to /renderload.gif for all 1,2,3,4,5
but once the sound blob was downloaded and image was generated, only 4-5 gets the images and it continues on loading the queue, repeating the issue.
Here's an example of what I'm trying to deal with.
Here's my latest attempts to add queueing to avoid lag by loading all the audios at once, but it seems the issue still persists.
// context.js
function Queue(){
var queue = [];
var offset = 0;
this.getLength = function(){
return (queue.length - offset);
}
this.isEmpty = function(){
return (queue.length == 0);
}
this.setEmpty = function(){
queue = [];
return true;
}
this.enqueue = function(item){
queue.push(item);
}
this.dequeue = function(){
if (queue.length == 0) return undefined;
var item = queue[offset];
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
return item;
}
this.peek = function(){
return (queue.length > 0 ? queue[offset] : undefined);
}
}
var audioqueue=new Queue();
var init=0;
var current=0;
var finished=0;
function RunGraphs(x) {
if (x==init) {
if (audioqueue.isEmpty()==false) {
current++;
var das=audioqueue.dequeue();
var divparent=das.find(".original-image");
var songurl=das.find(".Mpcs").find('span').attr("data-url");
console.log("is song url "+songurl);
console.log("is data here "+divparent.attr("title"));
divparent.css('width','110px');
divparent.attr('src','https://i.pinimg.com/originals/a4/f2/cb/a4f2cb80ff2ae2772e80bf30e9d78d4c.gif');
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET",songurl,true);
xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
xhr.onload = function() {
blob = xhr.response;//xhr.response is now a blob object
console.log(blob);
SCWFRobloxAudioTool.generate(blob, {
canvas_width: 110,
canvas_height: 110,
bar_width: 1,
bar_gap : .2,
wave_color: "#ecb440",
download: false,
onComplete: function(png, pixels) {
if (init == x) {
divparent.attr('src',png);
finished++;
}
}
});
}
xhr.send();
OnHold(x);
}
}
}
function OnHold(x) {
if (x==init) {
if (current > finished+7) {
setTimeout(function(){
OnHold(x)
},150)
} else {
RunGraphs(x)
}
}
}
if (window.location.href.includes("/lib?Ct=DevOnly")){
functionlist=[];
current=0;
finished=0;
init++;
audioqueue.setEmpty();
$(".CATinner").each(function(index) {
(function(x){
audioqueue.enqueue(x);
}($(this)));
});
RunGraphs(init);
};
The SCWFAudioTool is from this github repository.
Soundcloud Waveform Generator
The Queue.js from a search request, slightly modified to have setEmpty support.Queue.js
Please read the edit part of the post
I mad a usable minimal example of your code in order to check your Queue and defer method. there seems to be no error that i can find (i don't have the html file and cant check for missing files. Please do that yourself by adding the if (this.status >= 200 && this.status < 400) check to the onload callback):
// context.js
function Queue(){
var queue = [];
var offset = 0;
this.getLength = function(){
return (queue.length - offset);
}
this.isEmpty = function(){
return (queue.length == 0);
}
this.setEmpty = function(){
queue = [];
return true;
}
this.enqueue = function(item){
queue.push(item);
}
this.dequeue = function(){
if (queue.length == 0) return undefined;
var item = queue[offset];
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
return item;
}
this.peek = function(){
return (queue.length > 0 ? queue[offset] : undefined);
}
}
var audioqueue=new Queue();
var init=0;
var current=0;
var finished=0;
function RunGraphs(x) {
if (x==init) {
if (audioqueue.isEmpty()==false) {
current++;
var songurl = audioqueue.dequeue();
console.log("is song url "+songurl);
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET",songurl,true);
xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
xhr.onload = function() {
if (this.status >= 200 && this.status < 400) {
blob = xhr.response;//xhr.response is now a blob object
console.log('OK');
finished++;
} else {
console.log('FAIL');
}
}
xhr.send();
OnHold(x);
}
}
}
function OnHold(x) {
if (x==init) {
if (current > finished+7) {
setTimeout(function(){
OnHold(x)
},150)
} else {
RunGraphs(x)
}
}
}
var demoObject = new Blob(["0".repeat(1024*1024*2)]); // 2MB Blob
var demoObjectURL = URL.createObjectURL(demoObject);
if (true){
functionlist=[];
current=0;
finished=0;
init++;
audioqueue.setEmpty();
for(var i = 0; i < 20; i++)
audioqueue.enqueue(demoObjectURL);
RunGraphs(init);
};
Therefore if there are no errors left concerning missing files the only errors i can think off is related to the SCWFRobloxAudioTool.generate method.
Please check if the callback gets triggered correctly and that no errors accrue during conversion.
If you provide additional additional info, data or code i can look into this problem.
EDIT:
I looked into the 'SoundCloudWaveform' program and i think i see the problem:
The module is not made to handle multiple queries at once (there is only one global setting object. So every attempt to add another query to the api will override the callback of the previous one, and since the fileReader is a async call only the latest added callback will be executed.)
Please consider using an oop attempt of this api:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
Array.prototype.max = function() {
return Math.max.apply(null, this);
};
function SoundCloudWaveform (){
this.settings = {
canvas_width: 453,
canvas_height: 66,
bar_width: 3,
bar_gap : 0.2,
wave_color: "#666",
download: false,
onComplete: function(png, pixels) {}
}
this.generate = function(file, options) {
// preparing canvas
this.settings.canvas = document.createElement('canvas');
this.settings.context = this.settings.canvas.getContext('2d');
this.settings.canvas.width = (options.canvas_width !== undefined) ? parseInt(options.canvas_width) : this.settings.canvas_width;
this.settings.canvas.height = (options.canvas_height !== undefined) ? parseInt(options.canvas_height) : this.settings.canvas_height;
// setting fill color
this.settings.wave_color = (options.wave_color !== undefined) ? options.wave_color : this.settings.wave_color;
// setting bars width and gap
this.settings.bar_width = (options.bar_width !== undefined) ? parseInt(options.bar_width) : this.settings.bar_width;
this.settings.bar_gap = (options.bar_gap !== undefined) ? parseFloat(options.bar_gap) : this.settings.bar_gap;
this.settings.download = (options.download !== undefined) ? options.download : this.settings.download;
this.settings.onComplete = (options.onComplete !== undefined) ? options.onComplete : this.settings.onComplete;
// read file buffer
var reader = new FileReader();
var _this = this;
reader.onload = function(event) {
var audioContext = new AudioContext()
audioContext.decodeAudioData(event.target.result, function(buffer) {
audioContext.close();
_this.extractBuffer(buffer);
});
};
reader.readAsArrayBuffer(file);
}
this.extractBuffer = function(buffer) {
buffer = buffer.getChannelData(0);
var sections = this.settings.canvas.width;
var len = Math.floor(buffer.length / sections);
var maxHeight = this.settings.canvas.height;
var vals = [];
for (var i = 0; i < sections; i += this.settings.bar_width) {
vals.push(this.bufferMeasure(i * len, len, buffer) * 10000);
}
for (var j = 0; j < sections; j += this.settings.bar_width) {
var scale = maxHeight / vals.max();
var val = this.bufferMeasure(j * len, len, buffer) * 10000;
val *= scale;
val += 1;
this.drawBar(j, val);
}
if (this.settings.download) {
this.generateImage();
}
this.settings.onComplete(this.settings.canvas.toDataURL('image/png'), this.settings.context.getImageData(0, 0, this.settings.canvas.width, this.settings.canvas.height));
// clear canvas for redrawing
this.settings.context.clearRect(0, 0, this.settings.canvas.width, this.settings.canvas.height);
},
this.bufferMeasure = function(position, length, data) {
var sum = 0.0;
for (var i = position; i <= (position + length) - 1; i++) {
sum += Math.pow(data[i], 2);
}
return Math.sqrt(sum / data.length);
},
this.drawBar = function(i, h) {
this.settings.context.fillStyle = this.settings.wave_color;
var w = this.settings.bar_width;
if (this.settings.bar_gap !== 0) {
w *= Math.abs(1 - this.settings.bar_gap);
}
var x = i + (w / 2),
y = this.settings.canvas.height - h;
this.settings.context.fillRect(x, y, w, h);
},
this.generateImage = function() {
var image = this.settings.canvas.toDataURL('image/png');
var link = document.createElement('a');
link.href = image;
link.setAttribute('download', '');
link.click();
}
}
console.log(new SoundCloudWaveform());
Also consider simply using an array for the queue:
function Queue(){
var queue = [];
var offset = 0;
this.getLength = function(){
return (queue.length - offset);
}
this.isEmpty = function(){
return (queue.length == 0);
}
this.setEmpty = function(){
queue = [];
return true;
}
this.enqueue = function(item){
queue.push(item);
}
this.dequeue = function(){
if (queue.length == 0) return undefined;
var item = queue[offset];
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
return item;
}
this.peek = function(){
return (queue.length > 0 ? queue[offset] : undefined);
}
}
var q = new Queue();
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
console.log(q.dequeue());
console.log(q.dequeue());
console.log(q.dequeue());
console.log(q.dequeue());
var q2 = [];
q2.push(1)
q2.push(2)
q2.push(3)
console.log(q2.shift());
console.log(q2.shift());
console.log(q2.shift());
console.log(q2.shift());
It prevents confusion ant the speedup of it is minimal in your application.
On your open method on the xhr object, set the parameter to true, also... try using the onload() instead of onloadend(). Good Luck!
var xmlhttp = new XMLHttpRequest(),
method = 'GET',
url = 'https://developer.mozilla.org/';
xmlhttp.open(method, url, true);
xmlhttp.onload = function () {
// Do something with the retrieved data
};
xmlhttp.send();

How to remove EventListener in anonymous function?

I am trying to use removeEventListener but in line with event.target.removeEventListener('click', myName, false); is mistake. I tried different ways but none works.
document.querySelector('#imagesContainer').addEventListener('click', function myName (event) {
var images = this.formData.getAll('images[]');
var picName = event.target.parentNode.dataset.name;
var id = event.target.parentNode.getAttribute('id');
var xxx = document.getElementById(id);
xxx.outerHTML = "";
delete xxx;
for (var n = 0; n < images.length; n++) {
if (images[n].name == picName) {
var removedObject = images.splice(n, 1);
removedObject = null;
break;
}
}
this.formData.delete('images[]');
[].forEach.call(images, function(image){
this.formData.append('images[]', image);
}.bind(this));
event.target.removeEventListener('click', myName, false);
}.bind(this), false);
because you .bind(this) on function myName - bind creates a new function, so the function that is passed to click handler isn't myName, it's the myName bound to this - but you can't simply add .bind(this) to the remove code, because that would be a different myName bound to this
One solution is to use an IIFE to "capture" this as _this, then use _this instead of this in your code: e.g.
document.querySelector('#imagesContainer').addEventListener('click', (function(_this) {
return function myName (event) {
// replace this with _this
var images = _this.formData.getAll('images[]');
var picName = event.target.parentNode.dataset.name;
var id = event.target.parentNode.getAttribute('id');
var xxx = document.getElementById(id);
xxx.outerHTML = "";
delete xxx;
for (var n = 0; n < images.length; n++) {
if (images[n].name == picName) {
var removedObject = images.splice(n, 1);
removedObject = null;
break;
}
}
_this.formData.delete('images[]');
[].forEach.call(images, function(image){
_this.formData.append('images[]', image);
}.bind(_this));
// here you can use this, because this will be the element that fired the click event
this.removeEventListener('click', myName, false);
};
}(this)), false);
if you save a reference to the bound function first you can remove it that way.
const fooBound = foo.bind({hello: 'world'});
btn.addEventListener('click', fooBound);
function foo(event) {
console.log(this);
btn.removeEventListener('click', fooBound);
}
<button id="btn">clicker</button>

How to pass unspecified number of parameters to setInterval

I need to create an interval wrapper to track if it has been cleared.
The number of parameters to pass to the interval callback should be variable. So this is the code (not working) I implemented to test it:
function MyInterval() {
var id = setInterval.apply(this, arguments); // NOT VALID!!
this.cleared = false;
this.clear = function() {
this.cleared = true;
clearInterval(id);
};
}
var x = 2;
var y = 3;
var fn = function() {
x = x + y;
console.log(x);
};
var interval = new MyInterval(fn, 5000, x, y);
Within the call to setInterval, this must refer to the global object, so instead of this, you want window in your constructor:
var id = setInterval.apply(window, arguments);
// here -------------------^
(or in loose mode you could use undefined or null.)
Then it works, at least on browsers where setInterval is a real JavaScript function and therefore has apply:
function MyInterval() {
var id = setInterval.apply(window, arguments);
this.cleared = false;
this.clear = function() {
this.cleared = true;
clearInterval(id);
};
}
var x = 2;
var y = 3;
var fn = function() {
x = x + y;
log(x);
};
var interval = new MyInterval(fn, 500, x, y);
setTimeout(function() {
interval.clear();
}, 3000);
function log(msg) {
var p = document.createElement('p');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
Note, though, that host-provided functions are only required to be callable, they are not required to inherit from Function.prototype and so they're not required/guaranteed to have apply. Modern browsers ensure they do, but earlier ones (IE8, for instance) did not. I can't speak to how well-supported apply is on setInterval.
If you need to support browsers that may not have it, just to use your own function:
function MyInterval(handler, interval) {
var args = Array.prototype.slice.call(arguments, 2);
var tick = function() {
handler.apply(undefined, args);
};
var id = setInterval(tick, interval);
this.cleared = false;
this.clear = function() {
this.cleared = true;
clearInterval(id);
};
}
This also has the advantage that it works even on browsers that don't support additional args on setInterval (fairly old ones).
Example:
function MyInterval(handler, interval) {
var args = Array.prototype.slice.call(arguments, 2);
var tick = function() {
handler.apply(undefined, args);
};
var id = setInterval(tick, interval);
this.cleared = false;
this.clear = function() {
this.cleared = true;
clearInterval(id);
};
}
var x = 2;
var y = 3;
var fn = function() {
x = x + y;
log(x);
};
var interval = new MyInterval(fn, 500, x, y);
setTimeout(function() {
interval.clear();
}, 3000);
function log(msg) {
var p = document.createElement('p');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
You might be tempted to use the new ES2015 spread operator:
var id = setInterval(...arguments);
...but note that if you transpile (and right now you'd have to), it ends up being an apply call, and so you have the issue of whether apply is supported.
I suggest that you pass an "options" parameter to your timeout.
var MyInterval = (function(window) {
return function(callbackFn, timeout, options) {
var id = setInterval.apply(window, arguments);
this.cleared = false;
this.clear = function() {
this.cleared = true;
clearInterval(id);
};
}
}(window));
var fn = function(opts) {
opts.x += opts.y;
console.log('x = ', opts.x);
};
var opts = {
x: 2,
y: 3
};
var ms = 5000;
var interval = new MyInterval(fn, ms, opts);
// Bootstrap a custom logger. :)
console.log = function() {
var logger = document.getElementById('logger');
var el = document.createElement('LI');
el.innerHTML = [].join.call(arguments, ' ');
logger.appendChild(el);
logger.scrollTop = logger.scrollHeight;
}
body{background:#7F7F7F;}h1{background:#D7D7D7;margin-bottom:0;padding:0.15em;border-bottom:thin solid #AAA;color:#444}#logger{height:120px;margin-top:0;margin-left:0;padding-left:0;overflow:scroll;max-width:100%!important;overflow-x:hidden!important;font-family:monospace;background:#CCC}#logger li{list-style:none;counter-increment:step-counter;padding:.1em;border-bottom:thin solid #E7E7E7;background:#FFF}#logger li:nth-child(odd){background:#F7F7F7}#logger li::before{content:counter(step-counter);display:inline-block;width:1.4em;margin-right:.5em;padding:.25em .75em;font-size:1em;text-align:right;background-color:#E7E7E7;color:#6A6A6A;font-weight:700}
<h1>Custom HTML Logger</h1><ol id="logger"></ol>
I created a utility function rather than a constructor to solve your issue.
function Wrapper(delay) {
var isCleared,
intervalId,
intervalDelay = delay || 5e3; // default delay of 5 sec
function clear() {
if (!isCleared) {
console.log('clearing interval');
isCleared = true;
clearInterval(intervalId);
}
}
function setUpInterval(callback){
var params = [].slice.call(arguments, 1);
if (!callback) {
throw new Error('Callback for interval expected');
}
params.unshift(intervalDelay);
params.unshift(callback);
intervalId = setInterval.apply(null, params);
}
return {
setUp : setUpInterval,
clear : clear
}
}
function intervalCallback() {
console.log([].slice.call(arguments).join(','));
}
var wrapper = Wrapper(1e3); // create wrapper with delay for interval
console.log('test case 1');
wrapper.setUp(intervalCallback, 'params', 'to', 'callback');
// call clear interval after 10sec
setTimeout(function() {
wrapper.clear();
}, 10e3);
Hope this helps.

Function to print not working

I'm working on creating a video game using Javascript and my knowledge is pretty basic. I'm trying to create a function that will be used to create all the sprites as well as print them to a canvas, but when I try to print them nothing works. I'm not entirely sure what the issue is or if I'm doing the whole thing wrong entirely. Below is my current code.
//Gets canvas and context
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
//Creates all attributes of sprite
function sprite(imageName, imageX, imageY, imageHeight, imageWidth){
this.imageName = imageName;
this.imageX = imageX;
this.imageY = imageY;
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
this.draw = function(){
character = new Image();
character.src = "../Images/" + this.imageName + ".png";
character.onload = function(){
context.drawImage(character, this.imageX, this.imageY, this.imageWidth, this.imageHeight);
}
};
this.getHeight = function(){
return this.imageHeight;
};
this.getWidth = function(){
return this.imageWidth;
};
this.getX = function(){
return this.imageX;
};
this.getY = function(){
return this.imageY;
};
this.moveUpX = function(e){
this.imageX = (this.imageX + e);
};
this.moveUpY = function(f){
this.imageY = (this.imageY + e);
};
this.moveBackX = function(e){
this.imageX = (this.imageX - e);
};
this.moveBackY = function(f){
this.imageY = (this.imageY - e);
};
this.changeImage = function(a){
this.imageName = a;
};
this.getImage = function(){
return imageName;
};
this.changeX = function(b){
this.imageX = b;
};
this.changeY = function(c){
this.imageY = c;
};
}
//Creates Sprite
sprites[0] = new sprite('mySprite', 0, 0, 36, 51);
sprites[0].draw();
Your sprites var is undefined. You need to define that first
var sprites = []; // add this line
sprites[0] = new sprite('mySprite', 0, 0, 36, 51);
sprites[0].draw();

Categories

Resources