Functions in javascript - javascript

I am new to javascript and I was messing around with it. I was checking out jquery and I wanted to see if I could create something to do the same things. This is my js file
//returns a dom
var $ = function(selector) {
return document.querySelector(selector);
};
//adds something to the dom
var append = function(value) {
this.innerHTML += value;
};
//clears dom
var empty = function() {
while (this.lastChild) this.removeChild(this.lastChild);
};
When I call $('#test') I get the dom with id test. When I call $('#test').append('hi there') it works. However when I try $('#test').empty() I get a Uncaught TypeError: $(...).empty is not a function Any idea why? If I am doing something comletely wrong please let me know. Thanks

Your functions aren't added to the prototype chain of DOM elements, (that wouldn't be a good idea), so they can't be called as methods of DOM elements.
append works, because the DOM node already had a method called append, and it has nothing to do with the function you stored in a variable called append.
jQuery works by creating a container object that holds a collection of DOM elements and has it's own methods. EG:
var $ = function(selector) {
var objects = document.querySelectorAll(selector);
return {
append: function ( ) {
// do something to `objects` here
},
empty: function ( ) {
},
};
};

Related

Why does my HTML constructor object return [object Object] instead of [HTMLElementElement]?

Background
I'm trying to make a completely pure JavaScript GUI for creating HTML content for learning purposes. The only real html in the file will be one <script></script> element. I'm almost finished, I think.
I made a custom HTML element constructor, but when I create an object, [object Object] is displayed instead of [object HTMLWhateverElement] when I do alert(whatever); (see the example below). I think that's preventing me from appending child elements to parent elements when both are made by the constructor.
The main problem
If I could get HTML instances to append to HTML tags and element instances, then I would be very happy.
Constructor
function element(tagName) {
var element = document.createElement(tagName);
this.setText = function(elementText) {
if(element.childNodes.length < 1) {
var text = document.createTextNode(elementText);
element.appendChild(text);
} else {
element.childNodes[0].nodeValue = elementText;
}
}
this.removeText = function() {
element.removeChild(element.childNodes[0]);
}
this.setAttribute = function(attribute,value) {
element.setAttribute(attribute,value);
}
this.removeAttribute = function(attribute) {
element.removeAttribute(attribute);
}
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
this.details = function(){
alert(
"Type: " + element +
"\n\"typeof\": " + typeof(element) // Shouldn't need this right?
);
}
}
Example
ul = new element("ul");
ul.appendTo(body); // works fine if BODY is actually an HTML tag
alert(body); // gives me [object HTMLBodyElement] :)
alert(ul); // gives me [object Object] >(
li = new element("li"); // works :)
li.setText("list item text"); // works :)
li.appendTo(ul); // doesn't work >(
If I could just figure out how to append JavaScript-created (child) elements to other JavaScript-created (parent) elements, I'd be golden. I think it has to do with the return value of instantiated elements made by the constructor.
EDIT
1) Possible answer
#carter-sand Thank you.
Adding a new this.appendChild method works but reinvents the wheel
of HTML object's built in appendChild method.
2) Possible answer
#s4mok Thanks for the hint.
Changing var element to this.elem works but creates a
funky interface:
li.appendTo(ul.elem);
vs.
li.appendTo(ul);
I'm trying to emulate a private member.
Both answers work but neither return the value [object
HTMLWhateverElement] when I do :
alert(li);
Is there a way to reap the benefits of all of the above?
ul = new element("ul");
The above line is instantiating the function element() and the instance which is assigned to ul is an object and not HTMLWhateverElement
ul.appendTo(body); // works fine if BODY is actually an HTML tag
The above works because your code is :
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
whereas the parent is body and an HMTL Element and has a method appendChild, and the element that you are appending is the element from this line:
var element = document.createElement(tagName);
Why the below code does not work?
li = new element("li"); // works :)
li.setText("list item text"); // works :)
li.appendTo(li); // doesn't work >(
The answer to that is first of all li is not a HTML Element which the code
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
will fail since the instance li is not an html element but rather an object instance of function element()
Another error in this code is that you have a circular li.appendTo(li); which if you inspect your code, you are appending as child li to itself. something like a paradox or a circular dependency.
EDIT:
My Recommendation:
First of all, maybe change the name of your "element" inside the function element to avoid confusion. :D
Expose the var element = document.createElement(tagName); so that you could access it from the parameter parent of your method appendTo which will allow you to use parent.getTheExposedElement().appendChild ()
Your function returns an instance of itself [object Object] when created using the "new" keyword. You can override that by specifically returning your element. However you will then need to change "this" to your element instead because "this" refers to the function new instance. Also as suggested it may be less confusing if you changed "element" to something else like "newElement".
function element(tagName) {
var element = document.createElement(tagName);
element.setText = function(elementText) {
if(element.childNodes.length < 1) {
var text = document.createTextNode(elementText);
element.appendChild(text);
} else {
element.childNodes[0].nodeValue = elementText;
}
}
element.removeText = function() {
element.removeChild(element.childNodes[0]);
}
element.setAttribute = function(attribute,value) {
element.setAttribute(attribute,value);
}
element.removeAttribute = function(attribute) {
element.removeAttribute(attribute);
}
element.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
element.details = function(){
alert(
"Type: " + element +
"\n\"typeof\": " + typeof(element) // Shouldn't need this right?
);
}
return element;
}
ul = new element("ul");
ul.appendTo(document.body); // works fine if BODY is actually an HTML tag
alert(document.body);
alert(ul);
li = new element("li");
li.setText("list item text");
li.appendTo(ul);
If I run your example, I notice the following error message in the console:
Uncaught TypeError: Object #<element> has no method 'appendChild'
This is because your custom element instance does not have the appendChild method that you're using in appendTo. You need to add one to your constructor, like this:
this.appendChild = function(child) {
element.appendChild(child);
}

jQuery object plugin is not persistant

I am trying to extend a jquery element with custom properties and functions so I can track my elements and get their custom properties anytime.
at the moment I have done this:
jQuery.fn.designerElement = function (tpl, cls) {
this.__template = tpl
this.des__class = cls;
this.compile = function () {
this.html(this.__template(this));
return this;
}
return this;
};
var template = Handlebars.compile(
'<div id="' + +new Date() + '" class="{{des__class}}"></div>'
);
var el = $('<div></div>').designerElement(template, "form-container");
el.attr('id', "test");
el.compile();
$('body').append(el);
Now if I call $('#test').compile() it say says the method is undefined.
Here is a fiddle: http://jsfiddle.net/jmorvan/HLVj4/
To explain my context, I need the methods and properties available directly on the object for some dataBindings to work, thats why i can't use .data(). It seemed to me jquery plugin would be the best approach but I am definitly missing something here.
So just to be clear I would need to be able to access properties like this: $('#test').__template as well as functions.
thanks for your time!
I think you already know what is going on but here's a short explanation: With $('#test') you are creating a new jQuery object. It contains the same element but you are defining properties for the jQuery object, not the element. So what you are asking is if there is a way to add the functionality to the element but with the jQuery object attached. In jQuery data() can after all be used to do this:
jQuery.fn.designerElement = function (tpl, cls) {
this.__template = tpl
this.des__class = cls;
this.compile = function () {
this.html(this.__template(this));
return this;
}
this.data('this', this);
return this;
};
And retrieve that object with:
var test = $('#test').data('this');
Here is the fiddle
Another solution is to store all these objects in a globally available array or JSON object.

Is it a bad practice to add reference to a Javascript object in one of its attributes?

Let's say I have a setup like this.
var Account = function(data) {
this.data = data;
this.domElement = (function(){ code that generates DOM element that will represent this account })();
this.domElement.objectReference = this;
}
Account.prototype = {
show: function() { this.domElement.classList.remove('hidden'); },
hide: function() { this.domElement.classList.add('hidden') }
}
My question is about the last line: this.domElement.objectReference = this;
It would be a useful thing to have because then I can add event listeners to my DOM element and still get access to the object itself. I can add methods that would affect my DOM element, such as hide() and show(), for instance, without having to resort to modifying visibility of the DOM element using CSS directly.
I tested this code and it works like I want it to, but I'm curious whether this would cause memory leaks or some other unpleasantness or if it's an acceptable thing to do?
Thank you!
Luka
I know this has been answered by #PaulS. already, but I find the answer counter intuitive (returning a DOM element from the Account constructor is not expected) and too DOM-centric, but at the same time the implementation is very simple, so I am not sure what to think ;)
Anyway, I just wanted to show a different way of doing it. You can store Account instances in a map and give them a unique id (perhaps they have one already), then you store that id as a data attribute on the DOM element. Finally you implement a getById function or something similar to retrieve the account instance by id from the listeners.
That's pretty much how jQuery's data works.
Here's an example with delegated events like you wanted from the comments.
DEMO
var Account = (function (accounts, id) {
function Account(data) {
accounts[this._id = ++id] = this;
this.el = createEl.call(this);
}
Account.prototype = {
constructor: Account,
show: function() { this.el.classList.remove('hidden'); },
hide: function() { this.el.classList.add('hidden'); }
};
function createEl() {
var el = this.el = document.createElement('div');
el.className = 'account';
el.innerHTML = el.dataset.accountId = this._id;
return el;
}
Account.getById = function (id) {
return accounts[id];
};
Account.init = function () {
//add delegate listeners
document.addEventListener('click', function (e) {
var target = e.target,
account = Account.getById(target.dataset.accountId);
if (!account) return;
account.hide();
});
};
return Account;
})({}, 0);
//once DOM loaded
Account.init(); //start listening to events
var body = document.body;
body.appendChild(new Account().el);
body.appendChild(new Account().el);
Why not have domElement as a variable, and return it from your function? To keep the reference to your constructed Object (but only where this is as expected), you could do a if (this instanceof Account) domElement.objectReference = this;
You've now saved yourself from circular references and can access both the Node and the Object. Doing it this way around is more helpful if you're expecting to lose the direct reference to your Account instance, but expect to need it when "looking up" the Node it relates to at some later time.
Code as requested
var Account = function (data) {
var domElement; // var it
this.data = data;
domElement = (function(){/* ... */}()); // use var
if (this instanceof Account)
domElement.objectReference = this; // assign `this`
return domElement;
};
// prototype as before
Returned element is now the Node, not the Object; so you'd access the Account instance like this
var domElement = new Account();
domElement.objectReference.show(); // for example
In my opinion there is nothing good about referencing the object inside of the object itself. The main reason for this is complexity and obscurity.
If you would point out how exactly are you using this domElement.objectReference later in the code, I am sure that I or someone else would be able to provide a solution without this reference.

Explain: jQuery Caching Code

This snippet of code will return an element from a cache if it has been selected previously or select, cache, and return the element. It is useful for updating contents of elements which are never significantly changed (i.e, the parent of a counter seen by the user where the number changes but the parent does not). The code is as follows:
var $$ = (function() {
var cache = {};
return (function (selector) {
return cache[selector] || ( cache[selector] = jQuery (selector) );
});
})();
You can use it like so:
$$('#id')
Now... how the heck does this work? How does $$ have access to jQuery selector? It has nothing to do with $$ starting with $, you could just as well do var foo. How does $$ map what is passed into it to selector. I would expect to see var selector = argumentName inside of $$. Also, to me it does not appear that $$ is setup to receive arguments (e.g., function(input){} ) but it readily does?
This small piece of code is incredibly confusing to me and some clarity would be greatly appreciated. Thanks!
It's pretty straightforward. Here is the equivalent code but in an unpacked version to make it more explicit:
function generateCachingJQuery() {
var cache = {};
function queryFunc(selector) {
if (cache[selector]) {
return cache[selector];
}
else {
cache[selector] = jQuery(selector); //same as $(selector)
return cache[selector];
}
}
return queryFunc;
}
var $$ = generateCachingJQuery();
If you notice, first you have an anonymous function - which I named generateCachingJQuery here - which returns the function that $$ ends up being. This is done so that nothing but the internal function (named queryFunc here) has access to the cache variable. The rest is just a one-liner which I unpacked here to make it more clear what it's doing.
EDIT: To be clear, $$ ends up being queryFunc in the above code, not generateCachingJQuery. Notice that queryFunc takes selector as a variable.
var $$ = (function() { // begin closure
var cache = {}; // keep in memory through closure
// The function that gets assigned to `$$`
return function(selector) {
// If the element has already been queried (exists in the cache)
// then return the element that was previously stored,
// otherwise query the new element, add it to the cache and return it
return cache[selector] || (cache[selector] = jQuery(selector));
};
})(); // end closure

JS chaining pattern to insert elements into the DOM

I am struggling to make the pattern below work. I'm not interested in using a library.
function _createElement(tagNm, attr){
var el = document.createElement(tagNm);
for(var at in attr){
el.setAttribute(at, attr[at]);
}
}
//below function is not correct just for giving you idea
function _append(ele){
this.appendChild(ele)
return this;
}
// I would like to be able to achive following chaining patter with this.
var div = document.getElementById('div');
div._append( _createElement('div', {
id : 'parent', className: 'parent'
})).appendChile( _createElement('div', {
id : 'child', className: 'child'
}));
For something like that to work, you're going to have to have some sort of object to be the focal point of the chained calls. The object would be the value of this, in other words, in your "_append" function. An alternative would be to have your functions extend the native DOM object prototypes, but that won't work in older IE versions (and maybe not even newer ones; I'm not sure).
You could perhaps root everything in a wrapper around document.getElementById(). You'd set up a "class" object for all your append() and appendChild and whatever other functions you'd like to gather up for this facility. Those functions would all go on the prototype for that:
function chainingWrapper(elem) {
this.targetElement = elem;
}
chainingWrapper.prototype = {
append: function(arg) {
// do the append operation
return this;
},
appendChild: function(arg) {
// do the appendChild operation
return this;
},
// ...
};
Now you'll have a utility to start things off:
function forElement(id) {
return new chainingWrapper(document.getElementById(id));
}
So then you could do:
var div = forElement("myDiv");
div.appendChild(xyz).append(abc);
Inside the functions like "append" you'll be able to refer to that <div> as this.targetElement. There are a zillion other interesting things you could do with this sort of structure, of course.
The issue that you're having is that div (a DOM element) doesn't have a method called _append. You can't reliably modify the prototype of DOM elements, so you can't add the method (and it would be a bad idea even if you could).
Instead, you need to create a wrapper object, and create the append method on that:
function Appender(id) {
if (!(this instanceof Appender)) { // if the new keyword wasn't used
return new Appender(el);
}
this.element = document.getElementById(id); // create an instance variable of the element with the id passed
this.append = function(child) { // proxy the appendChild function
this.element.appendChild(child);
return this; // return the Appender object for chaining
};
}
You could then use this as follows:
Appender('div').append(_createElement('div', {
id : 'parent', className: 'parent'
})).append( _createElement('div', {
id : 'child', className: 'child'
}));
NB If you want to follow this approach, you're going to need to learn quite a bit more about Javascript's object model.
Even if you don't want to use it, you should defently take a look at some JavaScript library, like jQuery. They do exactly what you try to achieve.
jQuery does this, by putting the DOMElement inside a Wrapper and then call functions on that wrapper. These functions manipulate the given DOMObject underneath.
Your example would look like this:
var div = document.getElementById('div');
$(div).append( _createElement('div', {id : 'parent', className: 'parent'})...
And your try to implement jQuery's "$" function. The code can be found here.
It's possible to extend the DOM. Here's a working example:
HTMLElement.prototype.append = function(ele) {
return this.appendChild(ele); // returns the new child
}
function _createElement(tagNm, attr){
var el = document.createElement(tagNm);
for (var at in attr) {
el.setAttribute(at, attr[at]);
}
return el;
}
var div = document.getElementById('myDiv');
div.append(_createElement('div', {
id : 'parent', className: 'parent'
})).appendChild( _createElement('div', {
id : 'child', className: 'child'
})).appendChild(document.createTextNode("test"));
But it's probably not a good idea:
In Javascript, can you extend the DOM?
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
What others are saying is correct. You need to some how add the method _append to your object. For example, jQuery returns a jQuery object after each call. The jQuery object has functions like .each, .ajax etc etc. It's a big wrapper.
Something like this will get you the chaining you're looking for:
function _createElement(tagNm, attr){
var el = document.createElement(tagNm);
for(var at in attr){
el.setAttribute(at, attr[at]);
}
return el;
}
function customObj(Id) {
var obj = document.getElementById(Id);
obj._append = function(el) { this.appendChild(el); return this; }
return obj;
}
// I would like to be able to achive following chaining patter with this.
var div = customObj('test');
div._append( _createElement('div', {
id : 'parent', className: 'parent'
}))._append( _createElement('div', {
id : 'child', className: 'child'
}));

Categories