Ok I'm not sure the title of this post is the correct way to refer to what I mean and I'm pretty sure I already know the answer to this question but I just wanted some clarification.
If I have an oject like this
var myObj = {
settings : {
domObj = document.getElementById('elem1');
},
myFunc1 : function () {
return this.domObj;
},
myFunc2 : function () {
return this.domObj;
}
}
myObj.myFunc1();
myObj.myFunc2();
Is the domObj cached the first time it is accessed or is the dom traversed in both functions? I am attempting to access the Dom only once but not sure if this is a possible solution.
Assuming you really meant this:
var myObj = {
settings : function() {
domObj = document.getElementById('elem1');
},
myFunc1 : function() {
return this.domObj;
},
myFunc2 : function() {
return this.domObj;
}
};
the answer is that "domObj" is a global variable because you forgot the var keyword. Now, you may have meant this:
var myObj = {
domObj: null,
settings : function() {
this.domObj = document.getElementById('elem1');
},
myFunc1 : function() {
return this.domObj;
},
myFunc2 : function() {
return this.domObj;
}
};
in which case "domObj" is just a property of "myObj". It'd get set if you call
myObj.settings();
Assuming your doing "this.domObj =" and the other corrections you've noted; yes; the DOM element is cached in this.domObj. The only time the DOM is actually traversed is when you're calling DOM traversal methods. Assigning a DOM element to a variable/object property works exactly the same as any other assignment.
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 have a simple OOP code I started:
(function(window,document){
var _data = {
get:function(d){ return _data.data[d] },
set:function(prop,param){ _data.data[prop]=param },
remove:function(d){ delete _data.data[d] },
data:{}
};
window._data = _data.hasOwnProperty() ? _data.data : _data;
})(window);
What I want done when I type _data alone it'll return _data.data then if I do _data.get(... it'll do what each property needs to do. How is this done?
OR
(function(window,document){
var _data = {
get:function(d){ if(!d){return _data.data } else { return _data.data[d] } },
set:function(prop,param){ _data.data[prop]=param },
remove:function(d){ delete _data.data[d] },
data:{}
};
window._data = _data;
})(window);
It sounds like you're asking for _data to represent two different things depending upon whether a property was used with it or not. You can't do that in javascript.
You can do this in the global scope:
var _data = {
get:function(d){ return _data.data[d] },
set:function(prop,param){ _data.data[prop]=param },
remove:function(d){ delete _data.data[d] },
data:{}
};
Then, _data.get() will call that method and return data.
But, when you just refer to _data, you're going to get the whole _data object above, not just _data.data.
The only thing remotely like what you're asking for I can think of would be to make _data be a function like this:
function _data() {
return _data.data;
}
_data.get = function(d){ return _data.data[d] };
_data.set = function(prop,param){ _data.data[prop]=param };
_data.remove = function(d){ delete _data.data[d] };
_data.data = {};
This works because a function is an object that can have properties.
Then, you could do:
_data()
And that would give you _data.data.
Or, you could do :
_data.get(x)
And that would call the get method.
If you offered some explanation for why you're trying to do this, we could probably offer other ideas.
Hey all I have a question. I'm writing a small Js Object to make it easier for me to manage what page I'm on in order for me to be able to load proper scripts/styles per page. I am running into a situation that I just dont understand. I have a property currentPage that will obviously enough be set to the current page but if I just set it straight from another property I previously defined, it returns a reference error, but if I put it into a function that returns the same thing, it works. I'm not sure why that is. Can someone explain this to me? I'm not a hardcore JS Developer I just figure things out as I go, so is this something specific to JS? Here's a code sample of what I mean :
var self = PageInfo = {
locationArray : window.location.pathname.toString().split("/"),
printOutPath : function(){
console.log(self.locationArray.length);
},
//ref. error to locationArray
parentDirectory : self.locationArray[self.locationArray.length -3],
currentPage : function() {
return self.locationArray[self.locationArray.length -2]; // works
}
};
When you use JavaScript object literal syntax (creating an object with the curly braces {}) the values that go with each property are expressions that get evaluated at the moment the object is created. They can't reference properties of that same object because the object doesn't exist yet.
Note that within your object's methods you can use this instead of creating the self variable. As long as you call the methods using dot syntax like this:
PageInfo.currentPage()
...within the method this will automatically reference the object so you can do this:
var PageInfo = {
locationArray : window.location.pathname.toString().split("/"),
printOutPath : function(){
console.log(this.locationArray.length);
},
currentPage : function() { return this.locationArray[this.locationArray.length -2];}
};
alert( PageInfo.currentPage() );
Further reading: https://developer.mozilla.org/en/JavaScript/Guide/Working_with_Objects
When you define an object, you can't refer to the object until it has been created. By using a function, you're delaying the lookup of self.locationArray until the object has been created.
Object is assigned to self and PageInfo only after the execution of the statement.
So do it after the statement.
var self = PageInfo = {
locationArray : window.location.pathname.toString().split("/"),
printOutPath : function(){
console.log(self.locationArray.length);
},
currentPage : function() { return self.locationArray[self.locationArray.length -2]; // works
}
};
self.parentDirectory = self.locationArray[self.locationArray.length -3];
It will update PageInfo also
Use this inside functions to make it more OO
var self = PageInfo = {
locationArray : window.location.pathname.toString().split("/"),
printOutPath : function(){
console.log(this.locationArray.length);
},
currentPage : function() { return this.locationArray[this.locationArray.length -2]; // works
}
};
self.parentDirectory = self.locationArray[self.locationArray.length -3];
You can also create a function to set parentDirectory
var self = PageInfo = {
locationArray : window.location.pathname.toString().split("/"),
printOutPath : function(){
console.log(this.locationArray.length);
},
parentDirectory:"",
setParentDirectory: function() {
this.parentDirectory = this.locationArray[this.locationArray.length -3];
},
currentPage : function() { return this.locationArray[this.locationArray.length -2]; }
};
self.setParentDirectory();
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);
}
I know I could do this with closures (var self = this) if object was a function:
click here
<script type="text/javascript">
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
return false;
},
load : function () {
document.getElementById('x').onclick = this.handle_click;
}
};
object.load();
</script>
The simplest way to bind the call to handle_click to the object it is defined in would be something like this:
var self=this;
document.getElementById('x').onclick =
function(e) { return self.handle_click(e) };
If you need to pass in parameters or want to make the code look cleaner (for instance, if you're setting up a lot of similar event handlers), you could use a currying technique to achieve the same:
bind : function(fn)
{
var self = this;
// copy arguments into local array
var args = Array.prototype.slice.call(arguments, 0);
// returned function replaces first argument with event arg,
// calls fn with composite arguments
return function(e) { args[0] = e; return fn.apply(self, args); };
},
...
document.getElementById('x').onclick = this.bind(this.handle_click,
"this parameter is passed to handle_click()",
"as is this one");
So, the event handler part wires up just fine (I tested it myself) but, as your comment indicates, you have no access to the "y" property of the object you just defined.
This works:
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
alert(this.y);
return false;
},
load : function () {
var that = this;
document.getElementById('x').onclick = function(e) {
that.handle_click(e); // pass-through the event object
};
}
};
object.load();
There are other ways of doing this too, but this works.
I see how to do it with Jason's latest one. Any way to do it without the anonymous function?
We can directly pass an object with a handler method thanks to AddEventListener, and you will have access to its attributes:
http://www.thecssninja.com/javascript/handleevent
Hope this will help those who, like me, will look for this topic some years after!