Pass attribute, not value as parameter - javascript

I would like to make this happen in JavaScript:
function changeAttributeValue(theAttribute, numberOfPixels)
{
theAttribute = numberOfPixels + "px";
}
changeAttributeValue(myImage.style.left, 50);
changeAttributeValue(myImage.style.top, 80);
changeAttributeValue(myCanvas.width, 600);
The first two calls should move an image and the third should adjust the width of a canvas.
Like this however, just the value of the passed variables were passed, so nothing happens.
Does anybody know how to solve this problem?

You could do something like this.
function changeAttributeValue(obj, propName, numberOfPixels) {
obj[propName] = numberOfPixels + "px";
}
changeAttributeValue(myImage.style, "left", 50);
changeAttributeValue(myImage.style, "top", 80);
changeAttributeValue(myCanvas, "width", 600);

Well, here is my "solution".
It is inspired by Jared Farrish's comment. That is, this creates a "wrapper" getter/setter function for attribute access that can be used. I do not recommend it here (as it is more complicated than what is needed), but it could be used for something awesome elsewhere.
function mkAccessor(obj, attribute) {
return function (value) {
if (arguments.length > 0) {
obj[attribute] = value
}
return obj[attribute]
}
}
function changeAttribute (accessor, value) {
accessor(value)
}
changeAttribute(mkAccessor(myImage.style, "top"), 50)
changeAttribute(mkAccessor(myImage.style, "left"), 80)
var canvasWidthAccessor = mkAccessor(myCanvas, "width")
// somewhere later on
changeAttribute(canvasWidthAccessor, 600)
// or just
canvasWidthAccessor(600)
Theory is sound, but code is untested. YMMV. :-)

It's probably useful if I actually post and comment what I provided in comments.
To wit:
function setDim(attr, modifier, pix) {
if (!modifier) {
this[attr] = pix + 'px';
} else {
this[attr][modifier] = pix + 'px';
}
}
Now, how does this work? The this references the scope. If you simply call it directly:
setDim('style', 'width', 50);
That will simply call the window or global scope. Really, this is built for passing the scope using el.call() or el.apply(), more or less the same, just depends on how you want the arguments passed into the function.
How you really want to call it:
// Gives you the scope of the element #test1
var test = document.getElementById('test1');
Which then can be "called" like:
setDim.call(test, 'style', 'width', 50);
Note, if it's just an attribute (plain and simple), pass null:
setDim.call(test, 'width', null, 50);
I use setTimeout() in this fiddle for effect, but it's not needed:
http://jsfiddle.net/userdude/tCz6j/2/
Take a look through the jQuery core source and see how much .apply() is used. These two methods are really useful, if you "get" them.

Related

myFunction is not defined

in my scriptfile.js I layout a simple text re-sizing function. On its own, the function works as expected. However, when I try to add $(window).resize(fitheadliner()); to my scriptfile to fire the the fitheadliner() function on the resize event, it get a "Uncaught ReferenceError: fitheadliner is not defined" error. I have moved the resize function around the scriptfile thinking it may be a scope issue to no avail. Here is the contents of the file:
( function( $ ) {
$.fn.fitheadliner = function() {
this.each(function() {
var
$headline = $(this),
$parent = $headline.parent();
var
textW = $headline.width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($headline.css('font-size')),
newSize = originalSize * (0.9 * ratio);
$headline.css("font-size", newSize);
});
};
$(window).resize(fitheadliner());
} )( jQuery );
Here:
$.fn.fitheadliner = …
fitheadliner is defined as a property of $.fn.
But here:
$(window).resize(fitheadliner());
you are attempting to access it as a variable (and call it). Consider:
(function($) {
function fitheadliner() {
...
}
// Assign a reference, don't call the function
$(window).resize(fitheadliner);
}(jQuery));
However, you have a further issue from:
this.each(function() {
The function is called with window as this, and window doesn't have an each method. I don't understand how you aren't seeing other errors (or erroneous behaviour). Perhaps this should be replaced with a selector:
$(<selector>).each(...);
It's not quite a scoping issue. More like a qualification issue.
When this line executes
$(window).resize(fitheadliner());
You are saying, run $(window), then run fitheadliner(), then run the .resize method of the return value of the first call, passing it the return value of the first function call.
It's easy to think that the manner in which you are calling fitheadliner() would tie it to the $ object, but it doesn't. There's no reason it would. Each expression is evaluated independently and then chained appropriately.
Therefore, this expression is looking for a symbol in scope named fitheadliner that must be of type function. There are no symbols in scope with that name. There is, however, a symbol named $.fn.fitheadliner
To get past this error, you need to fully-qualify the reference, like
$(window).resize($.fn.fitheadliner());
But the fact is, I don't think that is totally what you want either. .resize takes a handler function. fitheadliner does not return a function, or return anything. It actually does some work. So I think what you meant to do was to pass a reference to fitheadliner to resize.
That's easy - take the paranthesis out.
$(window).resize($.fn.fitheadliner);
Now, even better, there is probably no reason to attach fitheadliner to the jQuery prototype like that. Try this. It may more closer to what you were trying to do.
( function( $ ) {
function fitheadliner() {
this.each(function() {
var
$parent = $(this).parent(),
$headline = $(this);
var
textW = $(this).width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($(this).css('font-size')),
newSize = originalSize * (0.9 * ratio);
$(this).css("font-size", newSize);
});
}
$(window).resize(fitheadliner);
} )( jQuery );
This defines a function in scope called fitheadliner and then passes a reference to it to resize.
Personally, I would take it one step further and inline the function anonymously since it does not need to be reused. But it's a matter of form/preference for me. There's semantically no difference.
( function( $ ) {
$(window).resize(function fitheadliner() {
this.each(function() {
var
$parent = $(this).parent(),
$headline = $(this);
var
textW = $(this).width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($(this).css('font-size')),
newSize = originalSize * (0.9 * ratio);
$(this).css("font-size", newSize);
});
});
} )( jQuery );

JavaScript Error: Object expected

I have an HTML page featuring a div with right-to-left scrolling text; the following JavaScript is located between the HEAD tags of the document.
function scroll(oid, iid) {
this.oCont = document.getElementById(oid);
this.ele = document.getElementById(iid);
this.width = this.ele.clientWidth;
this.n = this.oCont.clientWidth;
this.move = function() {
this.ele.style.left=this.n + "px"
this.n--
if(this.n<(-this.width)){this.n=this.oCont.clientWidth}
}
}
var vScroll
function setup() {
vScroll = new scroll("oScroll", "scroll");
setInterval("vScroll.move()", 20);
}
onload = function(){
setup()
}
$("scroll").hover(function() {
$("scroll").stop(true, false)
}, function(){
scroll();
});
scroll();
The scrolling text works fine; however I wanted the scrolling to stop on mouse hover. Although the text does stop scrolling when the mouse cursor passes over the div, I get a javascript error "Object expected".
I'm new to javascript and have no idea where I'm going wrong.
Any help would be greatly appreciated.
Your problem is with your setInterval. You are passing it a string! This makes it use eval! That means the code is ran in global scope, so vScroll does not exist.
Instead, pass a function to setInterval:
setInterval(function(){
vScroll.move();
}, 20);
The function passed to setInterval is called with the "context" (this value) set to null, so you cannot pass vScroll.move directly to setTimeout. You can, however. do:
setInterval(vScroll.move.bind(vScroll), 20);
but this doesn't work in all browsers.
P.S. Passing a string to setInterval is bad practice, you should always be passing a function.

Confused by this - getting error "this.myfuntion() is not a function"

Background: I am trying to edit a zen cart horizontal pop out menu to make the popout open inline within the menu. The problem I am having is that I am struggling to get my head around the javascript/jquery that came with it.
Without posting the whole thing the structure of the code is something like this:
(declare some vars)
//some functions like this:
function funcname(obj) {
//do something
}
//then one big master function like this:
function bigfunc(arg1, arg2, arg3, arg4, arg5) {
//declare some vars based on this
this.varname1=varname1;
this.varname2=varname2;
//declare some functions inside the big function
this.innerfunc1= function() {
//do stuff
}
this.innerfunc2= function() {
//do stuff
}
}//end of big function
//then goes on to declare init function
function initfunc(){
//this creates new bigfunc(arg1 arg2 arg3...) for each main menu item
}
//finally calls init function with
window.onload = initfunc();
Now on to my confusion -
1) firstly for clarification, am I correct in thinking based on all the this's floating about in bigfunc() and the fact that it is called with new bigfunc() that this is creating an object?
2)My current problem is with one of the functions inside bigfunc() which looks like this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
var maxwidth = this.children[0].width;
var nextWidth;
if (this.isMouseOnMe || this.isMouseOnChild()) {
nextWidth = divref.offsetWidth + slideSpeed_out;
if (nextWidth >= maxwidth) {
this.finishOpeningChild(divref, ulref, maxwidth);
} else {
ulref.style.left = nextWidth - maxwidth + "px";
divref.style.width = nextWidth + "px";
setTimeout("slideChildMenu('" + this.getId() + "')", slideTimeout_out);
}
}
Now my plan is to alter this to use jquery show to open the element so I tried this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
if (this.isMouseOnMe || this.isMouseOnChild()) {
$(divref).show(function(){
this.finishOpeningChild(divref, ulref);
});
}
}
But I am getting this-> TypeError: this.finishOpeningChild is not a function
Now, there is a lot of other stuff going on in this js so I wouldnt dream of asking someone on here to do my work for me, but I am hoping that if someone can explain to me why this function is not a function I may be able to work the rest out.
NOTE: I thought this was to do with the scope of "this" but the value of this appears to be exactly the same in both versions of the code.
I know this is a long one but your help is greatly appreciated.
The value of this in a function is called the "context" in which the function runs. In general, whenever you pass a callback function as an argument (as you do with $(divref).show(function() {...})), the function can run the callback in whatever context it wants. In this case, the jQuery show function chooses to run its callback in the context of the element being animated.
However, you want access to the value of this at the time the anonymous callback function is defined, rather than when it is run. The solution here is to store the outer value of this in a variable (traditionally called self) which is included in the scope of the newly-defined function:
this.slideChildMenu = function() {
//...
var self = this;
$(divref).show(function(){
self.finishOpeningChild(divref, ulref);
});
}
I am thinking that the jQuery selector has changed the scope of this.
In your example $(this); would refer to object being animated per jQuery api docs:
If supplied, the callback is fired once the animation is complete. This can be useful for stringing different animations together in sequence. The callback is not sent any arguments, but this is set to the DOM element being animated. If multiple elements are animated, it is important to note that the callback is executed once per matched element, not once for the animation as a whole.
If the object in question is instantiated you can call it with dot notation without using this like bigFunc.finishOpeningChild(divref, ulref);
You're probably a little confused about scope, it's not always easy keeping track, but doing something more like this:
var site = {
init: function(elm) {
self=site;
self.master.funcname2(self.varname1, elm); //call function in master
},
funcname: function(obj) {
//do something
},
varname1: 'some string',
varname2: 3+4,
master: function() {
this.varname3 = sin(30);
this.funcname2 = function(stuff, element) {
site.funcname(element); //call function in 'site'
var sinus = site.master.varname3; //get variable
}
}
}
window.onload = function() {
var elm = document.getElementById('elementID');
site.init(elm); //call init function
}
usually makes it a little easier to keep track.

Why object does not see his method?

I am developing a game using the framework atomJS and library libCanvas. Here is the code where the error occurs:
var Planet=atom.Class({
//other code
clearLayer : function (layer) {
layer.ctx.clearRect(this.x, this.y, this.size, this.size);
},
colonize : function (layer, angle, color,ms) {
**this.clearLayer(layer);**
drawArc({
context: layer.ctx,
x: Math.round(this.x + this.size / 2),
y: Math.round(this.y + this.size / 2),
radius: this.radius + 5,
width: 4,
color: color,
opacity: 0.6,
angleFinish: angle
});
if (this.colonizing) {
//if (this.cursorOnPlanet()) this.context.fillText(COLONIZING, (this.x + this.size / 2) - 30, this.y + this.size - 2);
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
if (angle > 360) {
this.colonizing = false;
this.state = 1;
}
} else {
clearTimeout(this.colonizingTimer);
this.clearLayer(layer);
}
},
});
On this line, this.clearLayer(layer); the script terminates with an error Object [object DOMWindow] has no method 'clearLayer'.Tell me please what's the problem?
Thanks!
It's important to see how whateverObject.colonize() is actually getting called. Anyway, it's clear that the original object's method is being bound to a different object before getting called. This is fairly common in event handlers, for example, where this usually (but not always) ends up being the event target, not the method's original object.
It's common for developers to use a closure to ensure that they have a safe reference for the original this. For example, you might define colonize in a constructor that says var self=this;, which would guarantee the name self points to the original this even if this itself gets rebound.
Another approach is to use Function.prototype.bind (which you'd have to polyfill for old JS engines), which creates a new function with a this object guaranteed to be whatever you specify.
It sounds like the function is being called from the DOM window and not the local class. When the this object is a window, you'll inevitably have scoping issues.
Your problem is with the setTimeout function. When the timeout is called, it's telling the DOMWindow, not the local class, to call the function. To fix this, wrap the call into a function.
function(){<code>}
Edit: I'm not really sure of the purpose of the extra fields in the setTimeout, so I omitted my solution. If you wrap whatever you're doing in a function, it should work though.
change
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
to
var self = this;
this.colonizingTimer = setTimeout(function(){self.colonize.call(self);}, ms,layer, angle + 5, color,ms);
The thing is that because of the timeout, the this object is removed from your object scope and at execution time refers to the global object(window) which has no method named clearLayer.
Here's a simplified demo to see the difference.
& the most correct way is to use "delay":
this.colonizingTimer = this.colonize.delay(ms, this, [layer, angle + 5, color, ms]);
But, if i understand right you want to animate angle from zero to 360 degrees? Why dont you use "Animatable" & ".animate" ?
With every question about LibCanvas you can send me an email to shocksilien#gmail.com

Write a wrapper object in Javascript

First off, let me apologize if my question isn't worded correctly - I'm not a professional coder so my terminology might be weird. I hope my code isn't too embarrassing :(
I have a fade() method that fades an image in and out with a mouse rollover. I would like to use a wrapper object (I think this is the correct term), to hold the image element and a few required properties, but I don't know how to accomplish this. fade() is called from the HTML, and is designed to be dropped into a page without much additional setup (so that I can easily add new fading images to any HTML), just like this:
<div id="obj" onmouseover="fade('obj', 1);" onmouseout="fade('obj', 0);">
The fade(obj, flag) method starts a SetInterval that fades the image in, and when the pointer is moved away, the interval is cleared and a new SetInterval is created to fade the image out. In order to save the opacity state, I've added a few properties to the object: obj.opacity, obj.upTimer, and obj.dnTimer.
Everything works okay, but I don't like the idea of adding properties to HTML elements, because it might lead to a future situation where some other method overwrites those properties. Ideally, I think there should be a wrapper object involved, but I don't know how to accomplish this cleanly without adding code to create the object when the page loads. If anyone has any suggestions, I would greatly appreciate it!
Here's my fader method:
var DELTA = 0.05;
function fade(id, flag) {
var element = document.getElementById(id);
var setCmd = "newOpacity('" + id + "', " + flag + ")";
if (!element.upTimer) {
element.upTimer = "";
element.dnTimer = "";
}
if (flag) {
clearInterval(element.dnTimer);
element.upTimer = window.setInterval(setCmd, 10);
} else {
clearInterval(element.upTimer);
element.dnTimer = window.setInterval(setCmd, 10);
}
}
function newOpacity(id, flag) {
var element = document.getElementById(id);
if (!element.opacity) {
element.opacity = 0;
element.modifier = DELTA;
}
if (flag) {
clearInterval(element.dnTimer)
element.opacity += element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity > 100) {
element.opacity = 100;
element.modifier = DELTA;
return;
}
element.opacity = Math.ceil(element.opacity);
} else {
clearInterval(element.upTimer)
element.opacity -= element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity < 0) {
element.opacity = 0;
element.modifier = DELTA;
return;
}
element.opacity =
Math.floor(element.opacity);
}
setStyle(id);
}
function setStyle(id) {
var opacity = document.getElementById(id).opacity;
with (document.getElementById(id)) {
style.opacity = (opacity / 100);
style.MozOpacity = (opacity / 100);
style.KhtmlOpacity = (opacity / 100);
style.filter = "alpha(opacity=" + opacity + ")";
}
}
You are right, adding the handlers in your HTML is not good. You also loose the possible to have several handlers for event attached to one object.
Unfortunately Microsoft goes its own way regarding attaching event handlers. But you should be able to write a small wrapper function to take care of that.
For the details, I suggest you read quirksmode.org - Advanced event registration models.
An example for W3C compatible browsers (which IE is not): Instead of adding your event handler in the HTML, get a reference to the element and call addEventListener:
var obj = document.getElementById('obj');
obj.addEventListener('mouseover', function(event) {
fade(event.currentTarget, 1);
}, false);
obj.addEventListener('mouseout', function(event) {
fade(event.currentTarget, 0);
}, false);
As you can see I'm passing directly a reference to the object, so in you fade method you already have a reference to the object.
You could wrap this in a function that accepts an ID (or reference) and every time you want to attach an event handler to a certain element, you can just pass the ID (or reference) to this function.
If you want to make your code reusable, I suggest to put everything into an object, like this:
var Fader = (function() {
var DELTA = 0.05;
function newOpacity() {}
function setStyle() {}
return {
fade: function(...) {...},
init: function(element) {
var that = this;
element.addEventListener('mouseover', function(event) {
that.fade(event.currentTarget, 1);
}, false);
element.addEventListener('mouseout', function(event) {
that.fade(event.currentTarget, 0);
}, false);
}
};
}())
Using an object to hold your functions reduces pollution of the global namespace.
Then you could call it with:
Fader.init(document.getElementById('obj'));
Explanation of the above code:
We have an immediate function (function(){...}()) which means, the function gets defined and executed (()) in one go. This function returns an object (return {...};, {..} is the object literal notation) which has the properties init and fade. Both properties hold functions that have access to all the variables defined inside the immediate function (they are closures). That means they can access newOpacity and setStyle which are not accessible from the outside. The returned object is assigned to the Fader variable.
This doesn't directly answer your question but you could use the jQuery library. It's simple, all you have to do is add a script tag at the top:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js">
Then your div would look like:
<div id="obj" onmouseover="$('#obj').fadeIn()" onmouseout="$('#obj').fadeOut()">
jQuery will handle all the browser dependencies for you so you don't have to worry about things like differences between firefox and mozilla etc...
If you want to keep your HTML clean, you should consider using JQuery to set up the events.
Your HTML will look like this:-
<div id="obj">
Your JavaScript will look "something" like this:-
$(document).ready(function() {
$("#obj").mouseover(function() {
Page.fade(this, 1);
}).mouseout(function(){
Page.fade(this, 0);
});
});
var Page = new function () {
// private-scoped variable
var DELTA = 0.05;
// public-scoped function
this.fade = function(divObj, flag) {
...
};
// private-scoped function
var newOpacity = function (divObj, flag) {
...
};
// private-scoped function
var setStyle = function (divObj) {
...
};
};
I introduced some scoping concept in your Javascript to ensure you are not going to have function overriding problems.

Categories

Resources