I wrote a small hash change object, it will alert the url hash whenever it changes:
(function() {
function hashChange() {
this.previousHash;
this.initialize();
}
hashChange.prototype.initialize = function() {
this.setInterval = window.setInterval(this.checkHashChange, 0);
}
hasChange.prototype.uponHashChange = function(hash) {
alert('I executed!');
var hashValue = hash.split('#')[1];
alert(hashValue);
}
hashChange.prototype.checkHashChange = function() {
var hash = window.location.hash;
if(hash && hash !== this.previousHash) {
this.previousHash = hash;
this.uponHashChange(hash); // <---- doesn't execute
}
}
var hashChange = new hashChange();
})();
But this:
this.uponHashChange(hash);
Never gets executed. Why?
this.setInterval = window.setInterval(this.checkHashChange, 0);
This line is not going to do exactly what you mean. this.checkHashChange will lose its binding to its current this (which would be a hashChange instance), and will instead be invoked in the context of the window object.
You need to bind it explicitly to the correct context object:
var self = this;
this.setInterval = window.setInterval(function() { self.checkHashChange() }, 0);
Matt Greer has suggested Function.bind, which would make it more concise and likely more readable:
this.setInterval = window.setInterval(checkHashChange.bind(this), 0);
Unfortunately, Function.bind is not yet widely supported across browsers.
Related
A couple times now I have wanted to check element sizes as the page loads. I've been doing that using $(document).ready();, but find that often the properties are null. The same is true if I use $(window).load();.
To get around this I have been using a bit of a hack, where I recursively recall the function if the element is not set.
Question: Is there a better approach in terms of professionalism?
var makeMusic = {
init: function() {
if ($('#bloc-1').height() == null) {
setTimeout(function() {
makeMusic.init() ########## THIS IS THE HACK ##########
}, 10)
} else {
makeMusic.height = $('#bloc-1').height();
makeMusic.width = $('#bloc-1').width();
}
makeMusic.watchExperience();
},
watchExperience: function() {
//Some stuff
}
}
var Main = {
run: function() {
makeMusic.init();
}
}
$(document).ready(Main.run());
You do not need a hack at all. The issue here is that Main.run function is invoked before document.ready() is fired. You should:
$(document).ready(Main.run);
Instead of
$(document).ready(Main.run());
When you add () to the function name interpeter invokes it as soon as the line is reached.
When passing a callback, you should only pass a reference to the function.
In terms of proffessionalism i think its better to put your code in a namespace like this:
var app = window.app || {};
app.set = {};
app.set.makeMusic = (function(){
// private members
this.height = "";
this.width = "";
var init = function() {
height = $('#bloc-1').height();
width = $('#bloc-1').width();
alert(height + " " + width);
};
//public interface
return {
init: init
};
})(); // self invoked
$(function(){
app.set.makeMusic.init();
});
fiddle
I am trying to access the properties of the MainObj inside the onclick of an elem.
Is there a better way to design it so the reference wont be to "MainObj.config.url"
but to something like this.config.url
Sample code:
var MainObj = {
config: { url: 'http://www.mysite.com/'},
func: function()
{
elem.onclick = function() {
var url_pre = MainObj.config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
}
'this' inside the object always refers to itself (the object). just save the context into a variable and use it. the variable is often called '_this', 'self' or '_self' (here i use _self):
var MainObj = {
config: { url: 'http://www.mysite.com/'},
func: function()
{
var _self = this;
elem.onclick = function() {
var url_pre = _self.config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
}
You can use the Module pattern:
var MainObj = (function () {
var config = { url: 'http://www.mysite.com/'};
return {
func: function() {
elem.onclick = function() {
var url_pre = config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
};
}());
First we define the config object in the local function scope. After that we return an object literal in the return statement. This object contains the func function which later can be invoked like: MainObj.func.
There is, most certainly, a better way... but I must say: binding an event handler in a method is -and I'm sorry for this- a terrible idea.
You might want to check MDN, about what it has to say about the this keyword, because this has confused and tripped up many a man. In your snippet, for example, this is used correctly: it'll reference elem. Having said that, this is what you could do:
var MainObj = (function()
{
var that = {config: { url: 'http://www.google.com/'}};//create closure var, which can be referenced whenever you need it
that.func = function()
{
elem.onclick = function(e)
{
e = e || window.event;
window.open(that.config.url + this.getAttribute('href'));
};
};
return that;//expose
}());
But as I said, binding an event handler inside a method is just not the way to go:
MainObj.func();
MainObj.func();//shouldn't be possible, but it is
Why not, simply do this:
var MainObj = (function()
{
var that = {config: { url: 'http://www.google.com/'}};
that.handler = function(e)
{
e = e || window.event;
window.open(that.config.url + this.getAttribute('href'));
};
that.init = function(elem)
{//pass elem as argument
elem.onclick = that.handler;
delete that.init;//don't init twice
delete that.handler;//doesn't delete the function, but the reference too it
};
return that;//expose
}());
MainObj.init(document.body);
Even so, this is not the way I'd write this code at all, but then I do tend to over-complicate things every now and then. But do look into how the call context is determined in JS, and how closures, object references and GC works, too... it's worth the effort.
Update:
As requested by the OP - an alternative approach
(function()
{
'use strict';
var config = {url: 'http://www.google.com/'},
handlers = {load: function(e)
{
document.getElementById('container').addEventListener('click',handlers.click,false);
},
click: function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
//which element has been clicked?
if (target.tagName.toLowerCase() === 'a')
{
window.open(config.url + target.getAttribute('href'));
if (e.preventDefault)
{
e.preventDefault();
e.stopPropagation();
}
e.returnValue = false;
e.cancelBubble = true;
return false;//overkill
}
switch(target.id)
{
case 'foo':
//handle click for #foo element
return;
case 'bar': //handle bar
return;
default:
if (target.className.indexOf('clickClass') === -1)
{
return;
}
}
//treat elements with class clickClass here
}
};
document.addEventListener('load',handlers.load,false);//<-- ~= init
}());
This is just as an example, and it's far from finished. Things like the preventDefault calls, I tend to avoid (for X-browser compatibility and ease of use, I augment the Event.prototype).
I'm not going to post a ton of links to my own questions, but have a look at my profile, and check the JavaScript questions. There are a couple of examples that might be of interest to you (including one on how to augment the Event.prototype in a X-browser context)
I'm just trying to structure my Javascript better and wondering how to incorporate window.onresize into the returned object, like so:
var baseline = function(){
var tall, newHeight, target, imgl, cur, images = [];
return {
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
window.onresize = this.setbase(this.images);
},
setbase: function(imgs){
this.imgl = imgs.length;
if(this.imgl !== 0){
while(this.imgl--){
this.cur = imgs[this.imgl];
this.cur.removeAttribute("style");
this.tall = this.cur.offsetHeight;
this.newHeight = Math.floor(this.tall / this.target) * this.target;
this.cur.style.maxHeight = this.newHeight + 'px';
}
} else {
return false;
}
}
}
}();
Is this the way that people would do it, is this going to work? Thanks
EDIT:
Invoked like so:
window.onload = function(){
baseline.init('img', '24');
};
I would like it so that when the window is resized, baseline.init is called with the same params as the initial init function call...
Here's the main error
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
// This line says call setbase now and assign the result of that
// as the onresize handler
window.onresize = this.setbase(this.images);
},
Your this.images does not point to the var images = [] you've created. This is for when you're using protoype style objects. You should just use images in your functions.
Some of your variables look like they're only used in setBase, they should be local
Looking at your object, it's very hard to tell what it's supposed to do, sounds like you're wrapping code in an object just for the sake of wrapping it into an object. What does baseline mean?
Here's a better version of your code, you should read and understand http://www.joezimjs.com/javascript/javascript-closures-and-the-module-pattern/ and http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html so you can decide what pattern you want to use and how they actually work. You are mixing both patterns, even though you didn't intend to. The trick is that with the way you're writing it (module pattern) there's no need to use this in the code, they're actually local variables held be the module
var baseline = function(){
// Don't use "this.tall", just "tall" gets you the variable
// Class variables, are you sure you need them throughout the class
var tall, newHeight, target, imgl, cur, images = [];
// Different name for the parameter so it doesn't get confused with
// the class variables
function init(selector, pTarget) {
images = document.querySelectorAll(selector);
target = pTarget;
setBase();
// Since we're not using this, you
// can just reference the function itself
window.onresize = setBase
}
// Most JS developers name methods using camelCase
function setBase() {
imgl = imgs.length;
if(imgl !== 0){
while(imgl--){
cur = imgs[imgl];
cur.removeAttribute("style");
tall = cur.offsetHeight;
newHeight = Math.floor(tall / target) * target;
cur.style.maxHeight = newHeight + 'px';
}
// should you return true here? what does returning
// something even mean here?
} else {
return false;
}
}
// Return just the public interface
return {
init: init
setBase: setBase
};
}();
I started trying to create a timer function that would let me wrap a callback function so that I could later alter the behavior dynamically.
This led to a general realization that I really don't understand functions yet, and definitely don't understand what is happening with 'this'
I have a test environment setup on jsfiddle
myns = {};
myns.somefunc = function(txt) {
this.data = txt;
this.play = function() {
alert(this.data + ' : '+dafunc.data);
};
};
var dafunc = new myns.somefunc('hello world');
myns.Timer = function(msec, callback) {
this.callback = null;
this.timerID = null;
this.ding = function() {
this.callback();
};
this.set1 = function( msec, callback ) {
this.stop();
this.callback = callback;
this.timerID = setTimeout(this.ding, msec );
};
this.set2 = function( msec, callback ) {
this.callback = callback;
var wrappedDing = (function(who) {
return function() {
who.ding();
};
})(this);
this.timerID = setTimeout(wrappedDing, msec );
};
//this.set1(msec, callback);
this.set2(msec, callback);
};
var ttimer = new myns.Timer(1000, dafunc.play);
If I use the set1 method, then the callback doesn't work.
So I am trying the set2 method. This gets me to the play method but "this" is not referring to the instance of somefunc.
I thought I was on the right track, but the mix up on 'this' has me confused.
Any clues would be welcome.
The problem is that, unlike in a language like python, when you take a dafunc.play and pass it somewhere else (callback = dafunc.play) it forgets it was associated with dafunc, son you you would need to use yet another wrapper function, like you did in the set2 function.
var ttimer = new myns.Timer(1000, function(){ return dafunc.play(); });
Making all there extra functions by yourself is annoying. You could instead use the bind method that is available in newer browsers:
var wrappedDing = this.ding.bind(this);
new myns.Timer(1000, dafunc.play.bind(dafunc) );
Or you could use a similar shim if you need to support older versions of IE too.
Finally, if you are not going to take advantage of some form of inheritance or dynamic binding, you could instead rewrite your code to use closures. Since everything is lexicaly scoped, you don't have to worry about the this anymore:
(btw, I ended up simplifying the code in the proccess...)
myns = {};
myns.somefunc = function(txt) {
var obj = { data : txt };
obj.play = function() {
alert(obj.data);
};
return obj;
};
var dafunc = myns.somefunc('hello world');
myns.timer = function(msec, callback) {
var timerID = null;
var set = function(){
stop();
timerID = setTimeout(callback, msec);
};
set();
return {
set: set
};
};
var ttimer = myns.timer(1000, dafunc.play);
And one last thing: If you don't hate yourself use console.log and your browser's debugger and development console instead of using alerts for output.
I am writing a little class and I don't get it why this doesn't work:
var Browsertest = {
isIE: /MSIE (\d+\.\d+)/.test(this.getUserAgent()),
getUserAgent: function() {
return navigator.userAgent;
}
};
console.log(Browsertest.isIE);
I get the error that getUserAgent() doesn't exists/is available (in IE9 and other browsers).
You're calling the getUserAgent function before it is defined. When using object literals, instance members need to be defined before they are used.
Two alternatives...
One:
var Browsertest = {
getUserAgent: function() {
return navigator.userAgent;
},
isIE: function() { return /MSIE (\d+\.\d+)/.test(this.getUserAgent()); }
};
console.log(Browsertest.isIE());
Two:
var Browsertest = new function() {
var that = this;
this.getUserAgent = function() {
return navigator.userAgent;
};
this.isIE = /MSIE (\d+\.\d+)/.test(that.getUserAgent());
};
console.log(Browsertest.isIE);
Since isIE is being defined as a property before getUserAgent(), you must define it as a function rather than a scalar:
var Browsertest = {
isIE: function() {
return /MSIE (\d+\.\d+)/.test(this.getUserAgent());
},
getUserAgent: function() {
return navigator.userAgent;
}
};
// Call it as a function
console.log(Browsertest.isIE());
You are calling this.getUserAgent in a place where this resolves to the global object.
Well first i'd like to point out, please do not use user agent sniffing any more, it is highly frowned upon these days.
See This link for more info why UA sniffing is bad
Answer to your question:
It will work if you declare the getUserAgent method before the isIE method.
var Browsertest = {
getUserAgent: function() {
return navigator.userAgent;
},
isIE: /MSIE (\d+\.\d+)/.test(this.getUserAgent())
};
This is because:
/MSIE (\d+\.\d+)/.test(this.getUserAgent())
Is immediately executed when it is parsed, because it is an expression, not a function declaration. Therefore it does not know about getUserAgent, because that method has simply not yet been parsed.
But the method getUserAgent is redundant, so you could also write it like this:
var Browsertest = {
isIE: /MSIE (\d+\.\d+)/.test(navigator.userAgent)
};