Do something when all dynamically created images have loaded - javascript

What I'm trying to do is create a dynamic wall of images.
What I'm doing is this:
Call an API to get some response. Create an array of objects based on response
Based on the array, make HTML elements for each object where there's an img in it too.
When all of these HTML elements are created, attach it to the DOM, and call a final function.
This is what I have so far (truncated to get the point across):
EDIT: code has changed a bit. scroll to bottom of question for link to current code.
// based on one post, construct the html and return it
function getOneHtml(post, w) {
console.log("getting one html");
var outerDiv = $("<div>", {class: "brick"});
outerDiv.width(w);
var img = $("<img />");
img.attr("src", post.img_src);
img.on('load', function() {
console.log("img loaded");
var ratio = this.width / w;
h = this.height / ratio;
$(this).css({'height': h});
// ...
// ...
// create the element
// an alternative I'm using for now is directly append
// the created HTML onto the page, but that results
// in a kinda messy interface.
return outerDiv[0].outerHTML;
});
}
// queries an api and then calls callback after everything is done
function requestData(subreddit, callback) {
// array of objects with link to image, post title, link to reddit
posts = [];
var w = $(window).innerWidth() / 3,
html = ''; // holds all of the inner HTML for all elements
$.get("url here", function(data) {
var arr = data.data.children;
arr.forEach(function(res_post) {
console.log("looping in requestData");
// prepare a post object
// ...
// ...
html += getOneHtml(post, w); // get the HTML for this post
});
// this should happen after everything else is done
console.log("calling callback");
callback(html);
});
}
// complete the DOM
function makeWall(html) {
console.log("making wall");
// do stuff
}
Now the trace of the program in console is this:
looping in requestData
getting one html
looping in requestData
getting one html
... // bunch of times
calling callback
making wall
(20) img loaded
So now the problem is that the HTML isn't prepared until each image is loaded, and so it doesn't actually get attached to the DOM.
How can I make sure that things happen in order in which I want them to? I tried refactoring code into more of an async style but that didn't work (not my strongest point).
I also tried looking at $.Deferred but I don't understand it, and how to integrate it into my code.
Any help is appreciated.
EDIT:
I think it might help to see what I'm doing: http://karan.github.io/griddit/
When you load, I want the images to load first, and then fade in. Currently, they show up, then hide and then fade in. Here's the source: https://github.com/karan/griddit/blob/gh-pages/js/main.js.
Also, if you scroll down one or two pages, then scroll back up, some images show up behind others.

You can use .done(), as explained in the jQuery API documentation on .done() specifically explains how to use .done() with $.get().
It's as simple as:
$.get( "test.php" ).done(function() {
alert( "$.get succeeded" );
});
Concretely...
As the API documentation link provided above indicates, you can daisy chain .done() calls together.
$.get(url,handler).done(function(){console.log('calling callback')},callback);

Note, Not certain about functionality of included plugins, i.e.g., #grid layout,
css, etc. image width, height and #grid layout not addressed, save for existing pieces re-composed in attempt at flow clarity.
Piece below solely to fulfill // Do something when all dynamically created images have loaded requirement. See console at jsfiddle
Note also, piece at jsfiddle format issue. In order to test piece, drew in 2 plugins from links at original post. Tried jsfiddle's TidyUp feature, which inserted linebreaks.
Piece may need some re-formatting; though current jsfiddle does provide callback functionality, as per original post. Again, see console. Thanks for sharing.
updated
$(function() {
// the name of the last added post
var last_added = '';
// to control the flow of loading during scroll var scrollLoad = true;
var q = 'cats';
var callbacks = $.Callbacks();
// callback,
// Do something when all dynamically created images have loaded
var callback = function (cb) {
return console.log( cb||$.now() )
};
callbacks.add(callback);
function getOneHtml(post, w, count){
var img = $("<img>", {
"src" : post.img_src,
"width" : w
});
img.on('load', function(e) {
var ratio = e.target.width / w;
h = e.target.height / ratio;
$(e.target).css('height',h)
});
var link = $("<a>", {
"href" : post.permalink,
"target" : "_blank",
"html" : img
});
var outerDiv = $("<div>", {
"class" : "brick",
"style" : "width:" + w
});
$.when($(outerDiv).appendTo("#grid"),
$(link),
count)
.then(function(div, _link, _count) {
// `image` `fadeIn`; adjustable
$(_link).appendTo($(div)).hide(0).fadeIn(2000);
return _count
})
.always(function(_count){
callbacks.fireWith(window, [_count + " images appended to grid at " + $.now()])
});
};
function requestData(subreddit,callback) {
//array of objects with link to image, post title,link to reddit
posts=[];
var w = $(window).innerWidth() / 3;
html = '';
$.ajax({
type : 'get',
url : "http://api.reddit.com/r/" + subreddit + "/hot.json?&after=" + last_added,
beforeSend : function () {
$("#searchterm").addClass("loadinggif");
},
complete : function () {
$("#searchterm").removeClass("loadinggif");
},
success : function (data) {
var arr = data.data.children;
var count = null;
arr.forEach(function(res_post) {
if(!res_post.data.is_self&&(/\.(gif|jpg|jpeg|tiff|png)$/i).test(res_post.data.url)) {
// `images` count
++count;
var post = {
'title' : res_post.data.title,
'img_src': res_post.data.url,
'name' : res_post.data.name,
'permalink': 'http://reddit.com' + res_post.data.permalink
};
getOneHtml(post, w, count);
}
last_added = res_post.data.name;
});
scrollLoad = true;
// callback,
// Do something when all dynamically created images have loaded
// see `console`; adjustable
callbacks.fireWith( window, [$(".brick img").size() + " appended to grid, callback at " + $.now()]);
}});
}
// function makeWall() {}
})
jsfiddle http://jsfiddle.net/guest271314/ggsY9/

Related

jQuery get width of ajax loaded elements

I am trying to add a css class to ajax loaded html element. My HTML look like as below:
<h4 class="overflow-text"><span>This is the response from the ajax call.</span></h4>
This h4 is a fixed width element and span inside h4 may be overflowing. So I need to add a CSS class for h4 if span text is overflowing. Here I am trying this with getting width of these two element and comparing it as below.
var el = $('h4.overflow-text');
el.each(function() {
var span = $(this).find('span')
if (span.width() > $(this).width()) {
$(this).addClass('text-marquee')
console.log('overflow')
} else {
console.log('not overflow')
}
})
This works for me, if html not load from ajax. But not working on ajax responce. So I tried it on ajax sussess as below. But it also doesn't work for me.
success: function(res) {
var $response = $("<div/>").html(res);
var wp = $response.find('.overflow-text').width();
var wc = $response.find('.overflow-text').children('span').width();
console.log(wp,wc)
$container.fadeOut( 50 , function() {
jQuery(this).html( res);
}).fadeIn( 1000 );
},
Doesn't works mean, wp and wc always giving 0.
Any help would be greatly appreciated.
Checking the width of the element after the content has been added to the DOM should work.
Edit: I saw that the DOM is not updated immediately however waiting for even a millisecond is enough and it gives the desired result.
$.get("spantext", function(data, status){
$('.container').fadeOut( 50 , function() {
jQuery(this).html(data);
$container = jQuery(this);
setTimeout(function(){
var wp = $container.find('.overflow-text').width();
var wc = $container.find('.overflow-text').children('span').width();
console.log(wp,wc)
},1);
}).fadeIn( 1000 );
});

Load content in div from a href tag in jQuery

I want to load all images before displaying them in a slideshow. I have been searching a lot but nothing seems to work. This is what i have so far and it doesn't work. I am loading images from the <a> tag from another div.
$('.slideshow').load(function(){
if (loaded<length){
first = $(settings.thumb).eq(loaded).find('a').attr("href");
$('<img src="'+first1+'"/>').appendTo('.slideshow');
}
else{ $('.slideshow').show(); }
loaded++;
});
Add an event listener to each image to respond to when the browser has finished loading the image, then append it to your slideshow.
var $images = $("#div_containing_images img");
var numImages = $images.length;
var numLoaded = 0;
var $slideshow = $(".slideshow");
$images.each(function() {
var $thisImg = $(this);
$thisImg.on("load", function() {
$thisImg.detach().appendTo($slideshow);
numLoaded++;
if (numLoaded == numImages) {
$slideshow.show();
}
});
});
It's a good idea to also listen for the error event as well, in case the image fails to load. That way you can increase numLoaded to account for broken image. Otherwise, your slideshow will never be shown in the event the image is broken.
Also note, that by calling detach() followed by appendTo() I am am moving the image in the DOM. If instead, you want to copy the image, use clone() instead of detach().
* EDIT TO MODIFY USER'S EXACT USE CASE *
var $images = $("li.one_photo a");
var numImages = $images.length;
var numLoaded = 0;
$images.each(function() {
$('<img />',
{ src: $(this).attr("href") })
.appendTo('.slideshow')
.on("load error", function() {
numLoaded++;
if(numLoaded == numImages) {
$('.slideshow').show();
}
});
});
* EDIT #2 *
Just realized you were putting everything in the $(".slideshow").load() function. Since $(".slideshow") represents a DIV, it will never raise a load event, and the corresponding function will never execute. Edited above accordingly.

jQuery appending to a dynamically created div

Trying to add a save/load feature using JSON to a diagram that uses jsPlumb. The save feature works like a charm however the load feature is not able to replicate full initial saved state. That is, the problem occurs when jQuery is trying to append to a freshly/dynamically created div.
The jsfiddle has the following functionality:
I can add projects which are div containers. Inside these I can add tasks by clicking on the green projects.
http://jsfiddle.net/9yej6/1/
My saving code plugins everything into an array which then becomes a string (using JSON).
$('#saveall').click(function(e) {
// Saving all of the projects' parameters
var projects = []
$(".project").each(function (idx, elem) {
var $elem = $(elem);
projects.push({
blockId: $elem.attr('id'),
positionX: parseInt($elem.css("left"), 10),
positionY: parseInt($elem.css("top"), 10)
});
});
// Saving all of the tasks' parameters
var tasks = []
$(".task").each(function (idx, elem) {
var $elem = $(elem);
tasks.push({
blockId: $elem.attr('id'),
parentId: $elem.parent().attr('id')
});
});
// Convert into string and copy to textarea
var flowChart = {};
flowChart.projects = projects;
flowChart.tasks = tasks;
var flowChartJson = JSON.stringify(flowChart);
$('#jsonOutput').val(flowChartJson);
});
The load code does the same in reverse.
$('#loadall').click(function(e) {
// Delete everything from the container
$('#container').text("");
// Convert textarea string into JSON object
var flowChartJson = $('#jsonOutput').val();
var flowChart = JSON.parse(flowChartJson);
// Load all projects
var projects = flowChart.projects;
$.each(projects, function( index, elem ) {
addProject(elem.blockId);
repositionElement(elem.blockId, elem.positionX, elem.positionY)
});
// Try to load all tasks
var tasks = flowChart.tasks;
$.each(tasks, function( index, elem ) {
//Problem occurs here, I am unable to reference the created project
$(elem.parentId).text('This is a test');
addTask(elem.parentId, 0);
});
});
Basically, what's not working is the $(parentId).append(newState); line 75 in the jsFiddle, I can't seem to reference that div because it was just created using jquery ?
edit:
More specifically, I make use of these functions to create actual project and task divs
function addProject(id) {
var newProject = $('<div>').attr('id', id).addClass('project').text(id);
$('#container').append(newProject);
jsPlumb.draggable(newProject, {
containment: 'parent' });
}
function addTask(parentId, index) {
var newState = $('<div>').attr('id', 'state' + index).addClass('task').text('task ' + index);
$(parentId).append(newState);
}
It should be:
$('#' + parentId).append(newState);
To search for an ID in a jQuery selector, you need the # prefix.

Loading an image but onload/onerror not working as expected

I have a div
<div id='cards'>
Which I want to fill with images based on some logic. But only when images are first loaded into memory. Otherwise, through onerror I wanna fill in some text..
function pasteCard(card, to){
if (typeof(card) == 'string')
card = [card];
var image = [];
for (var i = 0; i < card.length; i++) {
image[i] = new Image();
image[i].src = '/sprites/open/' + card[i] + '.png';
image[i].onload = function() {
pasteImage(to, image[i]);
}
image[i].onerror = function() {
pasteText(to, card[i]);
}
// alert(card[i]) #1
}
function pasteImage(to, image) {
to.append(image);
}
function pasteText(to, text) {
// alert(card[i]) #2
to.append(text);
}
}
pasteCard(['ABC123', 'DEF456', 'GHI789'], $('#cards'));
But this isn't working.
Problem/weirdness: If only #2 alert is active it returns nothing. But strangely if #1 alert is also active it does kinda work... (but still doesn't load my images, and mostly fails too when other code is involved)
Question: Why is it not working without #1 alert (at least in that jsfiddle)
suggestions?: what should I do?
Onload and onerror events are fired (executed) outside the scope of your function so your variables will be undefined. In the event method you have access to this which is the image object. You can set a data attribute to each image and access that in your error event.
Here is an example:
http://jsfiddle.net/7CfEu/4/
The callbacks are not in the same scope as your image array is - therefor you need to declare a variable then will "connect the scopes" and use it inside the callbacks
also the i variable probably changes until the callback is fired - so by using it inside the callback you will get undefined behavior
for (var i = 0; i < card.length; i++) {
var current_card = card[i];
var current_image = new Image();
current_image.onload = function() {
pasteImage(to, current_image);
}
current_image.onerror = function() {
pasteText(to, current_card);
}
current_image.src = '/sprites/open/' + current_card + '.png';
image[i] = current_image;
}
Fiddle: http://jsfiddle.net/7CfEu/6/
(Also - closing the div tag is never a bad idea)
Just in case anyone ends up here for same reason I did.
Was going crazy because onload and onerror were not firing in the page I was building. Tried copy pasting
var myimage = new Image();
myimage.onload = function() { alert("Success"); };
myimage.onerror = function() { alert("Fail"); };
myimage.src = "mog.gif" //Doesn't exist.
Which was working within codepen and random otherwise blank pages.
Turns out the problem I was having was that I was doing AJAX requests earlier in the page. This involved authorization which in turn involved a call to
setRequestHeader();
This was resulting in a net::ERR_FILE_NOT_FOUND error instead of the expected GET mog.gif 404 (Not Found)
This seemed to prevent proper triggering of events.
Revert with
xhr.setRequestHeader("Authorization", "");

how to add an auto expand to ace editor

im using the ace editor and im unable to modify it to autoexpand when the user input is longer than the current size:
here is how i have it currently (it has a handler for shift+enter), and it does not work.
Typist.prototype.heightUpdateFunction = function() {
var newHeight =
ac.getSession().getScreenLength()
* ac.renderer.lineHeight
+ ac.renderer.scrollBar.getWidth();
$(settings.id).height(newHeight.toString() + "px");
ac.resize();
};
Typist.prototype.createinput = function(settings,handler) {
var that = this;
var $typepad = $("<div/>" ,{
id : settings.id,
}).css({position:'relative', height: '40px'}) ;
$(that.place).append($typepad);
var ac = ace.edit(settings.id);
ac.commands.addCommand({
name : 'catchKeys',
bindKey : { win : 'Shift-Enter', mac : 'Shift-Enter' },
exec : function (ac) {
if (typeof handler === "function") {
handler(ac);
}
},
readOnly : false
});
that.heightUpdateFunction();
ac.getSession().on('change', that.heightUpdateFunction);
return true;
};
how would i get it to work? this current code does not.
How would i access the object that called the height update? (or the "id" of the div containing the ace editor, since i have several, each with an id reachable by
a.inpid
given
a = new Typist()
my attempt comes from reading this similar kind of problem i dont want to go that way because i will have several ace editors on the page, and i need to know the id of the one to apply the height adjustment to.
turns out i missed something simple
Typist.prototype.heightUpdateFunction = function() {
var newHeight =
ac.getSession().getScreenLength()
* ac.renderer.lineHeight
+ ac.renderer.scrollBar.getWidth();
$("#"+settings.id).height(newHeight.toString() + "px"); // i forgot $() needs '#'
ac.resize();
};
my bad. This omission kept me awake for hours.
EDIT:
see comment in the code to find my correction

Categories

Resources