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)
};
Related
trying to get my head around objects, methods, closures, etc... in Javascript.
Can't see why this isn't working, some fundamental flaw in my thinking I guess. I'm expecting the val variable to be passed through to the addNote() function but it isn't. I thought that any variables declared outside of a function are available to that function, as long as they're not within another function. Is that not correct?
if(typeof(Storage) !== "undefined") {
console.log(localStorage);
var $input = $('#input'),
$submit = $('#submit'),
$list = $('#list'),
val = $input.val();
var noteApp = {
addNote : function(val) {
var item = val.wrap('<li />');
item.appendTo($list);
clearField();
},
clearField : function() {
$input.val = '';
},
delNote : function(note) {
}
};
$submit.on('click', function(){
noteApp.addNote();
});
} else {
}
I'm trying to learn how the pros manage to get their code so clean, concise and modular. I figured a note app would be a perfect start, shame I got stuck at the first hurdle...
Cheers.
There are several issues with the code in the question
defining an argument named val and not passing an argument to the function
when calling clearField() inside the object literal it's this.clearField()
You're only getting the value once, not on every click
val is a string, it has no wrap method
$input.val = ''; is not valid jQuery
I would clean it up like this
var noteApp = {
init: function() {
if (this.hasStorage) {
this.elements().events();
}
},
elements: function() {
this.input = $('#input');
this.submit = $('#submit');
this.list = $('#list');
return this;
},
events: function() {
var self = this;
this.submit.on('click', function(){
self.addNote();
});
},
hasStorage: (function() {
return typeof(Storage) !== "undefined";
})(),
addNote: function() {
this.list.append('<li>' + this.input.val() + '</li>');
this.clearField();
return this;
},
clearField: function() {
this.input.val('');
},
delNote : function(note) {
}
}
FIDDLE
Remember to call the init method
$(function() { noteApp.init(); });
In your call to addNote(), you don't pass any argument for the val, so it will be undefined:
noteApp.addNote();
// ^^ nothing
Pass the input (seems you want the jQuery object not the string value because of your val.wrap call):
noteApp.addNote($input);
When you declare the val in the function, it is scoped to that function and will only be populated if the function call passes a value for that argument. Even if you have another variable in an upper scope with the same name val, they are still differentiated. Any reference to val in the function will refer to the local val not the upper scope.
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 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.
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.
In trying to make my Javascript unobtrusive, I'm using onLoads to add functionality to <input>s and such. With Dojo, this looks something like:
var coolInput = dojo.byId('cool_input');
if(coolInput) {
dojo.addOnLoad(function() {
coolInput.onkeyup = function() { ... };
});
}
Or, approximately equivalently:
dojo.addOnLoad(function() {
dojo.forEach(dojo.query('#cool_input'), function(elt) {
elt.onkeyup = function() { ... };
});
});
Has anyone written an implementation of Ruby's andand so that I could do the following?
dojo.addOnLoad(function() {
// the input's onkeyup is set iff the input exists
dojo.byId('cool_input').andand().onkeyup = function() { ... };
});
or
dojo.byId('cool_input').andand(function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
I don't know Dojo, but shouldn't your first example read
dojo.addOnLoad(function() {
var coolInput = dojo.byId('cool_input');
if(coolInput)
coolInput.onkeyup = function() { ... };
});
Otherwise, you might end up trying to access the element before the DOM has been built.
Back to your question: In JavaScript, I'd implement andand() as
function andand(obj, func, args) {
return obj && func.apply(obj, args || []);
}
Your example could then be written as
dojo.addOnLoad(function() {
andand(dojo.byId('cool_input'), function() {
this.onkeyup = function() { ... };
});
});
which isn't really that much shorter than using the explicit if statement - so why bother?
The exact syntax you want is not possible in JavaScript. The way JavaScript executes would need to change in a pretty fundamental fashion. For example:
var name = getUserById(id).andand().name;
// ^
// |-------------------------------
// if getUserById returns null, execution MUST stop here |
// otherwise, you'll get a "null is not an object" exception
However, JavaScript doesn't work that way. It simply doesn't.
The following line performs almost exactly what you want.
var name = (var user = getUserById(id)) ? user.name : null;
But readability won't scale to larger examples. For example:
// this is what you want to see
var initial = getUserById(id).andand().name.andand()[0];
// this is the best that JavaScript can do
var initial = (var name = (var user = getUserById(id)) ? user.name : null) ? name[0] : null;
And there is the side-effect of those unnecessary variables. I use those variables to avoid the double lookup. The variables are mucking up the context, and if that's a huge deal, you can use anonymous functions:
var name = (function() {return (var user = getUserById(id)) ? user.name : null;})();
Now, the user variable is cleaned-up properly, and everybody's happy. But wow! what a lot of typing! :)
You want dojo.behavior.
dojo.behavior.add({
'#cool_input': {
onKeyUp: function(evt) { ... }
}
});
How about something like this:
function andand(elt, f) {
if (elt)
return f(elt);
return null;
}
Call like this:
andand(dojo.byId('cool_input'), function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
As far as I know there isn't a built-in JavaScript function that has that same functionality. I think the best solution though is to query by class instead of id and use dojo.forEach(...) as you will be guaranteed a non-null element in the forEach closure.
You could always use the JavaScript equivalent:
dojo.byId('cool_input') && dojo.byId('cool_input').whateverYouWantToDo(...);
I've never used dojo, but most javascript frameworks (when dealing with the DOM) return the calling element when a method is called from the element object (poor wording, sorry). So andand() would be implicit.
dojo.addOnLoad(function() {
dojo.byId('cool_input').onkeyup(function(evt) { /*event handler code*/
});
});
For a list:
Array.prototype.andand = function(property, fn) {
if (this.filter(property).length > 0) this.map(fn);
}