calling a javascript function without parenthesis - javascript

The following renderChat function is used to render a message and an image onto a chat board. Inside the function there is another function
var onComplete = function () {
which does all the work of creating the list element and appending it to the chat list. After the onComplete function, there is only this three lines of code
img.onload = onComplete;
img.onerror = onComplete;
img.src = c.chat.value.media;
Because the var onComplete is a function assigned to a variable, I assumed it had to be called with parenthesis. Thus, when I see this
img.onload = onComplete;
I understand that the function has been assigned to a new variable, but has never been called. Yet, when I use the application, the chat has been rendered by the time we reach img.src = c.chat.value.media;
Can you please explain how my understanding of JavaScript is mistaken and how this function is working?
var renderChat = function (c) {
debug("Rendering chat: key='%s' fingerprint='%s' message='%s' created='%s' imageMd5='%s'",
c.chat.key,
c.chat.value.fingerprint,
c.chat.value.message,
c.chat.value.created,
md5(c.chat.value.media));
var renderFP = c.chat.value.fingerprint;
if (!isMuted(renderFP)) {
var img = new Image();
var onComplete = function () {
// Don't want duplicates and don't want muted messages
if (body.find('li[data-key="' + c.chat.key + '"]').length === 0 &&
!isMuted(renderFP)) {
var li = document.createElement('li');
li.dataset.action = 'chat-message';
li.dataset.key = c.chat.key;
li.dataset.fingerprint = renderFP;
li.appendChild(img);
// This is likely your own fingerprint so you don't mute yourself. Unless you're weird.
if (userId.val() !== renderFP) {
updateNotificationCount();
var btn = document.createElement('button');
btn.textContent = 'mute';
btn.className = 'mute';
li.appendChild(btn);
}
var message = document.createElement('p');
message.textContent = c.chat.value.message;
message.innerHTML = transform(message.innerHTML);
li.appendChild(message);
var createdDate = moment(new Date(c.chat.value.created));
var timestamp = document.createElement('time');
timestamp.setAttribute('datetime', createdDate.toISOString());
timestamp.textContent = createdDate.format('LT');
timestamp.className = 'timestamp';
li.appendChild(timestamp);
var size = addChat.is(":visible") ? addChat[0].getBoundingClientRect().bottom : $(window).innerHeight();
var last = chatList[0].lastChild;
var bottom = last ? last.getBoundingClientRect().bottom : 0;
var follow = bottom < size + 50;
chatList.append(li);
setupWaypoints(li);
debug('Appended chat %s', c.chat.key);
// if scrolled to bottom of window then scroll the new thing into view
// otherwise, you are reading the history... allow user to scroll up.
if (follow) {
var children = chatList.children();
if (children.length > CHAT_LIMIT) {
children.first().remove().waypoint('destroy');
}
li.scrollIntoView();
}
}
};
img.onload = onComplete;
img.onerror = onComplete;
img.src = c.chat.value.media;
}
};

The HTMLImageElement object will cause functions assigned to its onload and onerror properties to be called at the appropriate times (i.e. when the HTTP response is received or the wait for it times out).
The code to do this is built into the browser. The properties (or the addEventListener function in more modern code) are the only ways with which you can interact with that code.

In Javascript you can store functions in variables, this is what you did with onComplete().
The img object will execute a function(so called callback) after successfully loading the image (onload) or if it could not load the image(onerror).
To tell the img object which method to call after those events, you need to give it the method name like this without parenthesis:
img.onload = onComplete;
img.onerror = onComplete;
If you would use parenthesis the function would be executed immediately and img.onload wouldn't contain a reference to a function but the result of onCompleted.

img.onload = onComplete; will assign the function onComplete to the onload handler. THis means, that the function is called, when the vent happens.
img.onload = onComplete(); will assign the result of calling the function onComplete to the onload handler. This means, that the function is called immediately, is expected to return another function (or a string containing valid JS), which in turn will be called when the event happens.

Related

canvas drawImage and scope issue

I'm writing a small javascript class that I'm trying to use to create an image via canvas
I keep running into an issue where when I run the update function to redraw the canvas, some properties are undefined when they should be an instance of an image
To better explain what I mean, this code is what I have:
this.setHabboLeft = function(src)
{
var callback = this.update;
this.habboLeft = getImage(src, callback);
}
var getImage = function(src, callback)
{
var img = new Image;
if(typeof callback == 'function')
{
img.onload = callback;
}
img.src = src;
return img;
}
this.update = function()
{
console.log('updating...', this.background, this.habboLeft);
...
}
I am using setHabboLeft that sets an image from an external URL, then once that is loaded, it will run this.update()
I think the problem is with the onload callback in getImage. The console.log should return the image object but tells me it's undefined
EDIT for #gus27
Note, the image is on a different domain
I call the functions like:
// set background of lovelock
lovelockCanvas.setBackground($(self).data('picture'));
// create logged in user habbo
var habboUrl = habboCreator.generateUrl({
habbo_username: '{{ Auth::user()->habbo_username }}'
});
lovelockCanvas.setHabboLeft(habboUrl);
EDIT 2
Function being called
http://pastebin.com/ZRut9gyD
full js class
http://pastebin.com/WjRUkj4X
FIXED:
Instead of my update using this to draw everything, I created a new function and passed this as a parameter
See pastebin: http://pastebin.com/BTNigqLS
The problem is in the update function which is called by the img. When update is called by the img (by it's onload callback) the this variable in the update function refers to the img.
The documentation for this states:
Inside a function, the value of this depends on how the function is
called.
You can try something like that:
this.setHabboLeft = function(src)
{
var callback = this.update;
this.habboLeft = getImage(src, callback, this);
}
var getImage = function(src, callback, obj)
{
var img = new Image;
if(typeof callback == 'function')
{
img.onload = function(){
callback(obj);
};
}
img.src = src;
return img;
}
this.update = function(obj)
{
console.log(this); // this is here the img, not the lovelockCanvas
console.log('updating...', obj.background, obj.habboLeft);
}
Function.bind(objRef)
All functions are a type of object called Function MDN Function object. They come with some methods and properties, one of which is called bind.
Bind creates a new copy of the function binding its keyword (this) to the function.
Thus to solve the problem of an event overwriting the functions binding you simple do the binding yourself to ensure it is not overwritten by the native event handler.
this.setHabboLeft = function(src){
// create a new function bound to this
this.habboLeft = getImage(src, this.update.bind(this));
}
var getImage = function(src, callback){
var img = new Image;
img.onload = typeof callback == 'function' ? callback : undefined;
img.src = src;
return img;
}
this.update = function() {
console.log('updating...', this.background, this.habboLeft);
...
}
Additionally you don't have or add more intermediate steps to keep hold of the event object.
this.setHabboLeft = function(src){
// create a new update function bound to this
// only reference the image if it loads
getImage(src, this.update.bind(this));
}
var getImage = function(src, callback){
var img = new Image;
img.onload = typeof callback == 'function' ? callback : undefined;
img.src = src;
}
this.update = function(event){
// image loaded so can be referenced
this.habboLeft = event.target; // the image object
}

Image onload call on an object property

I have a problem calling the onload() function of a new image object when that image is immediately set as an object property. My parser tells me that imageMap[featureId].onload is not a function. I don't get why the imageMap[featureId] isn't synonymous with the image itself? The resourceList is used as an argument to preloadResource(). I have a setinterval elsewhere waiting for the loaded count to equal the resource count. Any ideas please?
var resourceList = ["path.png","path.png","path.png","path.png"];
var loadedResource = 0;
function preloadResource(resArr)
{
var buildIndex = 1;
for (i = 1; i <= resArr.length; i++)
{
var featureId = "feature" + buildIndex;
imageMap[featureId] = new Image();
imageMap[featureId].path = resArr[(i - 1)];
buildIndex++;
imageMap[featureId].onload(function () {loadedResource++;})
imageMap[featureId].src = resArr[(i -1)];
}
}
The error is telling you the problem: "onload is not a method."
imageMap[featureId].onload = function () {loadedResource++;};
or use addEventListener

get actual width/height of an image without loading the image into the page with javascript

I was wondering if it was possible to get the width and height of an image without putting an image into page, i.e without creating an image tag that displays it.
trying to make a sprite class using this method.
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
this.setDim = function() {
this.fullWidth = this.img.width;
this.fullHeight = this.img.height;
}
this.img.onload = this.setDim();
console.log(this.fullWidth);
return this;
}
however this.fullWidth returns undefined
and the below that ignores the onload returns 0
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
this.fullWidth = this.img.width;
this.fullHeight;
this.setDim = function() {
this.fullWidth = this.img.naturalWidth;
this.fullHeight = this.img.height;
console.log(this.fullWidth)
}
console.log(this.fullWidth)
//this.img.onload = this.setDim();
return this;
}
I don't really want to use Jquery for this.
I have also tried this.img.natrualWidth (as you can see in the example above)
it also returns 0
Any advice would be great,
Thanks
Updated this to match #vihan1086 answer
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
var self = this;
self.loaded = function () {};
this.setDim = function() {
self.fullWidth = this.width;
self.fullHeight = this.height;
self.frameWidth = this.width / self.frames;
self.frameHeight = this.height;
self.loaded.apply(self, []);
}
this.loaded = function() {
return this;
}
this.img.onload = this.setDim;
}
then use
sprite = new Sprite(sprite,5);
sprite.loaded = function() {
console.log(sprite.fullWidth);
}
Problem
var img = new Image();
img.src = '/my/src/to/file';
//this refers to the current function at this point
img.onload = function () {
//this is 'img' at this point not the function
}
Solution
this is not in scope so you would add:
var self = this;//Self and this are referring to the same thing, the function
img.onload = function () {
//this refers to image but self still refers to the function's this
self.width = this.width;
self.height = this.height;
}
console.log(this.width);//logs width
console.log(this.height);//logs height
This leaves async problems which can be solved using two methods
A
this.img.onload = this.setDim; //Noticed the dropped ()
B
self.loaded = function () {};
this.setDim = function () {
//...
self.loaded.apply(self, []);
}
then
var sprite = new Sprite(...);
sprite.loaded = function () {
console.log(this.fullHeight);
}
Explanation
img.onload() changes the scope of the code, resulting in your this referring to img. Now the weird part. We created a variable self which refers to this, this allows us to refer to this in a different scope by using self.
Other
img.onload is "async" which means it won't follow along with the rest of the code. This means the console.log() has run, but the img.onload hasn't. Be careful when working when this type of code (I wrote a few solutions in the update). You should wait until img.onload is finished before checking the values. I've worked on something like this before and I'll see if I can find what I did to address all these issues; if I can, I'll update this answer.
UPDATE: I wouldn't run the setDim function at first and let the user run it setDim() -> setDim. If you wish to load the dimensions at first, put a load function to your Sprite() which is run when the dimensions are retrieved.
In javascript the statements are executed asynchronously. To know more about this read this excellent article Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
In your case as #Juhana mentioned passing reference should get the issue resolved

Javascript: How do I get the height of unloaded images in a loop?

I am loading a json file and parsing it into an array in Javascript. One of the elements is the path to an image. I am not ready to load the images yet but I need to get the image's height. I know how to do that with code like the following (found on other stackoverflow pages)
function getimageheight(img) {
var tmpImg = new Image();
tmpImg.onload = function() {
var ht = this.height;
return ht+0;
}
tmpImg.src = img;
}
If I try to call this function in a loop, it returns undefined because the onload for the images is running slower than the loop. My actual code is this:
var j = 0;
$.each(cat.placemarks, function(index, mark) {
markers[cat.name][j] = [];
markers[cat.name][j].name = mark.name;
markers[cat.name][j].title = mark.title;
markers[cat.name][j].markerURL = mark.markerURL;
markers[cat.name][j].imageURL = mark.imageURL;
markers[cat.name][j].imageHEIGHT = getimageheight(projpath+mark.imageURL);
j++;
});
If I call the function once, it works. But calling it in a loop does not. How can I fix this?
If you store reference to data object in Img object used to load it, you can set the value of its properties after the loading is done. Hope that makes sense... Your data will not be ready to use before loading is complete tho. Heres the code
var total=cat.placemarks.length;//if an array, otherwise use another each cycle to get object count
var loaded=0;
$each(cat.placemarks, function(index, mark) {
markers[cat.name][j] = [];
var tmpImg = new Image();
tmpImg.refToObjWithNameOfYourChoice=markers[cat.name][j];
tmpImg.onload = function() {
this.refToObjWithNameOfYourChoice.imageHEIGHT=this.heigh;
loaded++;
if(loaded==total){
//markers data is ready to use - add function callback herer or sumthin'
}
}
tmpImg.src=projpath+mark.imageURL;
markers[cat.name][j].name = mark.name;
markers[cat.name][j].title = mark.title;
markers[cat.name][j].markerURL = mark.markerURL;
markers[cat.name][j].imageURL = mark.imageURL;
j++;
});
markers[cat.name][j].imageHEIGHT is undefined because getImageHeight() isn't returning anything. And naturally, the image load will occur much more slowly than your each() loop, so it won't do you any good to have getImageHeight() return something. You'll have to set up your load() callback to determine which image has been loaded and update the height of the corresponding markers element.

Javascript: every event-handler defined in for-loop is the same, uses last iteration's values

I have trouble understanding the scoping rules in Javascript.
In the example below, I would assume that scope url variable is private in the for-loop. And that the onload-event function would see this private instance.
But things does not seems work like that - the alert will popup with the last url twice.
If somebody can clarify what is going on, I'll be grateful.
<html>
<head>
</head>
<body>
<script type="text/javascript">
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = function(){
alert(url);
}
img.src = url;
}
</script>
</body>
</html>
JavaScript does not have block-scope.
The only way to create new variable scope is in a function.
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
function createImg( url ) {
var img = new Image();
img.onload = function(){
alert(url);
}
img.src = url;
return img;
}
for (var i=0;i<testArray.length;i++){
var img = createImg(testArray[i]);
}
Passing the testArray[i] to a function that creates and returns the new image ensure that the url referenced in the onload handler will be the one that was scoped in the function.
EDIT:
Ultimately, you'd never do this if all you need is access to the url.
You'd just get it from the property of the element via this.
function onloadHandler(){
alert( this.src ); // <--- get the url from the .src property!
}
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = onloadHandler;
img.src = url;
}
This way you're not creating an identical handler function instance in the loop, but rather sharing the same instance, and referencing the element that received the event via this.
Javascript is not block-scoped, and thus requires a new function every time you want a new scope. See the answer by patrick dw.
This is why it is advantageous to use [].map(function(x){...}) or [].forEach(function(x){...}) which are in the javascript standard, since you'll need to define those functions anyway.
var imageArray = urlArray.map(function(url) {
var image = new Image();
image.src = url;
image.onload = function() {
alert(url);
};
return image;
});
Try this :)
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = function(){
alert([img.src, url, i]);
}
img.src = url;
}

Categories