Loop inside function doesn't append content to parent - javascript

I am trying to append content to a parent element using a loop inside a function.
I want to append several divs to an element, but (obviously) want the flexibility to change things such as class name, text, etc.
Here is my code:
var head = document.getElementById('head');
var someText = "Hello, World";
var someAttributes = [['class', 'blue'], ['id', 'footer'], ['name', 'bunk']];
function createEl(tag, parent) {
var newElement = document.createElement(tag);
return parent.appendChild(newElement);
}
function addText(text, parent) {
return parent.textContent = text;
}
function addAttribute(attributes, parent) {
for(var i = 0; i < attributes.length; i++) {
parent.setAttribute(attributes[i][0], attributes[i][1]);
}
}
function newEl(parent, element, attr, text) {
var el = createEl(element, parent);
addText(text, el);
addAttribute(attr, el);
return parent;
}
var derp = newEl(head, 'div', someAttributes, someText);
function loop(num, content) {
for(var i = 0; i < num; i++) {
content;
console.log('derp');
}
}
loop(3, derp);
I can get it working if I change it to the following:
function loop(num, a, b, c, d) {
for(var i = 0; i < num; i++) {
content newEl(a, b, c, d);
console.log('derp');
}
}
loop(3, head, 'div', someAttributes, someText);
Why it doesn't work with loop(3, derp)

derp is useless variable which holds head (the return value of newEl), make it function and call inside loopo
var derp = function(){
return newEl(head, 'div', someAttributes, someText);
};
function loop(num, content) {
for(var i = 0; i < num; i++) {
content(); // <== call function derp here
console.log('derp');
}
}
loop(3, derp);

Create one function and loop everything within that function. In order to keep track of each element, push them into an array. Remember, ids must be unique, so addAttribute() will invalidate your HTML because each element will have the same id. Btw, try to avoid functions within loops, it can get messy.
SNIPPET
var base = document.createElement('main');
document.body.appendChild(base);
var tag = "div";
var str = " reporting for duty, sir!";
var att = [['class', 'infantry'], ['name', 'trooper']];
var cnt = 0;
var qty = 3;
var ico = 'https://cdn2.iconfinder.com/data/icons/starwars/icons/128/clone-4.png';
function cloneLab(tag, str, att, qty) {
var unit = [];
for(var i = 0; i < qty; i++) {
var trooper = document.createElement(tag);
unit.push(trooper);
cnt++;
unit[i].id = 'trooper'+cnt;
for(var j = 0; j < att.length; j++) {
trooper.setAttribute(att[j][0], att[j][1]);
}
trooper.textContent = unit[i].id + str;
trooper.style.backgroundImage = "url("+ico+")";
base.appendChild(unit[i]);
}
return unit;
}
cloneLab(tag, str, att, qty);
main { border: 5px inset grey; }
.infantry { border: 1px solid blue; background-repeat: no-repeat; background-position: center; background-size: contain; }

You could use Object.create(derp); to build new objects that inherit from derp
var head = document.getElementById('head');
var someText = "Hello, World";
var someAttributes = [
['class', 'blue'],
['id', 'footer'],
['name', 'bunk']
];
function createEl(tag, parent) {
var newElement = document.createElement(tag);
return parent.appendChild(newElement);
}
function addText(text, parent) {
return parent.textContent = text;
}
function addAttribute(attributes, parent) {
for (var i = 0; i < attributes.length; i++) {
parent.setAttribute(attributes[i][0], attributes[i][1]);
}
}
function newEl(parent, element, attr, text) {
var el = createEl(element, parent);
addText(text, el);
addAttribute(attr, el);
return parent;
}
var derp = newEl(head, 'div', someAttributes, someText);
function loop(num, content) {
for (var i = 0; i < num; i++) {
newEl(head, 'div', someAttributes, someText);
Object.create(derp);
}
}
loop(3, derp);
.blue {
color: blue;
}
<div id="head" />

There are few issues with the code here.
Firstly you there is no element with the id of 'head' to get as parent. So the js fails right away.
Even if you have an element with the name of 'head', you need to wait till the document loaded.
Either you can use jQuery document.ready function or document.addEventListener("DOMContentLoaded", function(event) { }); if you gonna use pure js. You can copy your code in side these above mentioned functions.
If you follow these steps your code will work perfectly.
PS: Please note that document.addEventListener("DOMContentLoaded", function(event) { }); may not work with IE.
HOPE THIS WILL HELP YOU.

Related

JavaScript: is there a way to initialize elements in a live collection automatically?

Consider the following example
// a sample constructor
var SampleConstructor = function(element,options);
// a full live collection
var domCollection = document.getElementsByTagName('*');
// bulk init
for (var i = 0; i < domCollection.length; i++) {
if ('some conditions required by component') {
new SampleConstructor( domCollection[i], {});
}
}
Questions
Will the newly added elements into the DOM get initialized by the sample constructor?
If not, is there a way to do it without jQuery and without looping through the collection on an interval basis?
Note
The needed solution is for IE8+
Here is a code sample to illustrate my comment. However, as you can see it doesn't keep track of DOM changes, indeed, I prefer the opposite way which is widely used by modern JavaScript frameworks like Angular : observe a raw data structure and update the DOM accordingly.
// Observable
Observable = function () {
this.observers = [];
};
Observable.prototype.addObserver = function (observer) {
this.observers.push(observer);
};
Observable.prototype.emit = function (evt, args) {
var i, n = this.observers.length;
for (i = 0; i < n; i++) {
this.observers[i].update(this, evt, args);
}
};
// Collection
Collection = function () {
this.items = [];
Observable.call(this);
};
Collection.prototype = new Observable();
Collection.prototype.size = function () {
return this.items.length;
};
Collection.prototype.add = function (item) {
this.items.push(item);
this.emit("added", [item]);
};
Collection.prototype.removeAt = function (i) {
var items = this.items.splice(i, 1);
this.emit("removed", [items[0], i]);
};
// program
var i, s = "h*e*l*l*o";
var collection = new Collection();
var words = document.getElementsByTagName("div");
var wordA = words[0], wordB = words[1];
collection.addObserver({
update: function (src, evt, args) {
this[evt](args);
},
added: function (args) {
wordA.appendChild(
this.createSpan(args[0])
);
wordB.appendChild(
this.createSpan(args[0])
);
},
removed: function (args) {
wordB.removeChild(
wordB.childNodes[args[1]]
);
},
createSpan: function (c) {
var child;
child = document.createElement("span");
child.textContent = c;
return child;
}
});
for (i = 0; i < s.length; i++) {
collection.add(s[i]);
}
for (i = 1; i < 5; i++) {
collection.removeAt(i);
}
function rdm (max) {
return Math.floor(
Math.random() * max
);
}
function addRdmLetter () {
collection.add(
(rdm(26) + 10).toString(36)
);
}
function removeRdmLetter () {
var n = collection.size();
if (n > 0) collection.removeAt(rdm(n));
}
function showLetters () {
alert(collection.items.join(""));
}
body {
font-size: 16px;
font-family: Courier;
}
span {
padding: 5px;
display: inline-block;
margin: -1px 0 0 -1px;
border: 1px solid #999;
}
#buttons {
position: absolute;
top: 10px;
right: 10px;
}
<p>wordA</p><div></div>
<p>wordB</p><div></div>
<div id="buttons">
<button type="button" onclick="addRdmLetter()">Add letter</button>
<button type="button" onclick="removeRdmLetter()">Remove letter</button>
<button type="button" onclick="showLetters()">Show letters</button>
</div>
function compare(arr,newarr,callback){
var index=0;
var newindex=0;
while(newarr.length!=newindex){
if ( arr[index] == newarr[newindex]) {
index++; newindex++;
} else {
callback (newarr[newindex]);
newindex++;
}
}
}
//onload
var store=[];
compare([],store=document.getElementsByClassName("*"),yourconstructor);
//regular
var temp=document.getElementsByClassName("*");
compare(store,temp,yourconstructor);
store=temp;
I think its the most efficient to check. The only solution I know is to do it regularly using setTimeout. Another way would be to detect all js dom changes, something like:
var add = Element.prototype.appendChild;
Element.prototype.appendChild=function(el){
yourconstructor(el);
add.call(this,el);
};
Note: very hacky

javascript could not achieve the return

I had made this function, and this is new yet, and I don't really know how to handle this roughly.
var $;
(function() {
$ = function(e) {
return new query(e);
};
var query = function(e) {
var e = document.querySelectorAll(e), i;
for (i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length;
return this;
};
$.fn.prototype = {
hide: function() {
var i;
for (i = 0; i < this.length; i++) {
this[i].style.display = 'block';
}
return this;
},
hasClass: function (klass) {
var e = this, i;
var t = [];
for (i = 0; i < e.length; i++) {
var k = e[i].className;
var array = k.split(' ');
// If the element has the class, add it for return
if (array.indexOf(klass) > -1) {
t.push(e[i]);
}
}
// Return the list of matched elements
return t;
}
}
} ());
window.onload = function() {
$(".element").hasClass("someClass").hide();
}
So yeah, that's the code above. I think I have matched the class, but what the problem is, It's not returning the elements. I'm new to prototyping so please don't be harsh. I really need to fix this one. Please don't tell me to go and have jquery. I don't want to use that massive library just because I want some css selectors.
this inside the function isn't the current element.
hasClass: function (klass) {
var e = this, i;
var t = [];
for (i = 0; i < e.length; i++) {
var k = e[i].className;
var array = k.split(' ');
// If the element has the class, add it for return
if (array.indexOf(klass) > -1) {
t.push(e[i]);
}
}
// Return the list of matched elements
return t;
}
and that is why your script do not return the element.
ALSO:
You tried to use jQuery's method to add your own functions in library, but actually this will not work, as you're writing pure js, not jquery, and you need to do many thing to make it work like jQuery.
$.fn.prototype = {
hide: function() {
var i;
for (i = 0; i < this.length; i++) {
this[i].style.display = 'block';
}
return this;
},
hasClass: function (klass) {
var e = this, i;
var t = [];
for (i = 0; i < e.length; i++) {
var k = e[i].className;
var array = k.split(' ');
// If the element has the class, add it for return
if (array.indexOf(klass) > -1) {
t.push(e[i]);
}
}
// Return the list of matched elements
return t;
}
}
}
The problem here is this line: $.fn.prototype = {};. There is no $.fn in your code anywhere, so you cannot set a prototype property on it. (In jQuery, $.fn is set to jQuery.prototype, that's why you can set $.fn.someMethod.)
If you want the hasClass method to exists on your objects, you need to modify the prototype of query. Your $ function returns new query objects, so query is where the hasClass method should be.
Also, your hasClass method returns an array. Arrays do not have a method called hide. You need to have your hasClass method return a query object, so that you can continue to chain methods.
P.S. Shouldn't a hide method set the style.display to 'none'?
Anyway, your code should look like this:
var $;
(function() {
$ = function(e) {
return new query(e);
};
var query = function(e) {
var e = Array.isArray(e) ? e : document.querySelectorAll(e),
i;
for (i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length;
return this;
};
query.prototype = {
hide: function() {
var i;
for (i = 0; i < this.length; i++) {
this[i].style.display = 'none';
}
return this;
},
hasClass: function(klass) {
var e = this,
i;
var t = [];
for (i = 0; i < e.length; i++) {
var k = e[i].className;
var array = k.split(' ');
// If the element has the class, add it for return
if (array.indexOf(klass) > -1) {
t.push(e[i]);
}
}
// Return the list of matched elements
return new query(t);
}
}
}());
window.onload = function() {
$(".element").hasClass("someClass").hide();
}
<p class="someClass element">You can't see me!</p>
(Note: Array.isArray doesn't work in IE < 9.)
If all you want is to query for elements matching a condition and hide it based on element type, id or class name CSS query selectors along with querySelector() or querySelectorAll() api.
For your code sample the same can be rewritten as below
var elements = document.querySelectorAll("element.someClass");
for(var i = 0; i <elements.length; i++) {
elements[i].style.visibility = 'hidden';
}

Binding event listeners multiple times in JavaScript

I'm trying to get the directory list, level by level, using JavaScript.
I have this paths array as input.
var _paths = [];
_paths.push("meta1/meta2/test/home/myself/hi.jpg");
_paths.push("meta1/meta2/test/home/myself/hi1.jpg");
_paths.push("meta1/meta2/test/home/myself/hi2.jpg");
_paths.push("meta1/meta2/test/work/she/100.jpg");
_paths.push("meta1/meta2/test/work/she/110.jpg");
_paths.push("meta1/meta2/test/work/she/120.jpg");
_paths.push("meta1/meta2/test/work/hard/soft/she/120.jpg");
_paths.push("meta1/meta2/test/work/hard/soft/she/121.jpg");
_paths.push("meta1/meta2/test/work/she/220.jpg");
and I want to have a "test" as output, which will be clickable. After click "test", it should be replaced by "home" and "work". After click on "home" - "myself", on "work" - "hard" and "she".
I wrote this:
CodepenCode
and it works only once, only when clicking on "test".
Simply rebind the listeners after the directories have been drawn. You bind them only once, thus they work only once.
Wrap the binding function into a named function:
function bindListeners(){
$('.sub').click(function() {
word = $(this).text();
filteredArr = findString(_paths, word);
drawList(filteredArr, word);
});
}
And call it at the end of drawList:
var drawList = function (paths, word) {
var folders = getFolders(paths, word);
if (folders.length > 0) {
$('.canvas').html('');
for (i = 0; i < folders.length; i++) {
$('.canvas').append("<div class='sub'>" + folders[i] + "</div><br />");
}
}
bindListeners();
}
Demo.
If anyone is curious about building out the data structure:
(function iteratePaths() {
var dirs = [];
for(var i = 0; i < _paths.length; i++) {
buildDirectories(dirs, _paths[i].split('/'));
}
})();
function findDir(dir, obj) {
for(var i = 0; i < dir.length; i++) {
if(dir[i].name === obj.name) {
return dir[i];
}
}
return undefined;
}
function buildDirectories(dir, subs) {
if(subs.length === 0) return;
var obj = {name: subs.shift(), dirs: []};
var existingDir = findDir(dir, obj);
if(!existingDir) {
dir.push(obj);
}
buildDirectories((existingDir || obj).dirs, subs);
}

JavaScript: How to set a parent object as a parameter within an addEventListener function?

how can I set the object (which is part of the buttons array) as a parameter within the addEventListener function? buttons[i] is not working..
Here is a part of the code:
var buttonNames = ["canteen","locations","floorplan","guestbook","pictures"];
var buttonDivNames = ["btn1","btn2","btn3","btn4","btn5"];
var buttons = [];
window.onload = function() {
for(var i = 0; i<buttonNames.length; i++) {
var obj = new Object();
obj.targetLink = buttonNames[i] + ".html";
obj.defaultImage = "img/buttons/"+buttonNames[i]+"_default.jpg";
obj.hoverImage = "img/buttons/"+buttonNames[i]+"_hover.jpg";
obj.div = document.getElementById(buttonDivNames[i]);
obj.divPicture = obj.div.getElementsByClassName("thumbnailPicture")[0];
obj.divLink = obj.div.getElementsByClassName("thumbnailLink")[0];
buttons.push(obj);
}
for(var i = 0; i<buttons.length; i++) {
buttons[i].divPicture.addEventListener("mouseover",function() { anotherFunction(buttons[i]) },false)
}
}
function anotherFunction(arg) {
console.log(arg.targetLink);
}
Thanks guys, this way it works:
for(var i = 0; i<buttons.length; i++) {
initButton(buttons[i]);
}
}
function initButton(arg) {
arg.divPicture.addEventListener("mouseover",function() {anotherFunction(arg);},false)
}
function anotherFunction(arg) {
console.log(arg.targetLink);
}
As pointed out in the comment section, you could use an IIFE to create a new scope, that holds the value of the current i:
for(var i = 0; i<buttons.length; i++) {
(function (i) {
buttons[i].divPicture.addEventListener("mouseover",function() { anotherFunction(buttons[i]) },false)
}(i));
}
or, even better, create a seperate function that handles the adding of the eventlistener:
function addEventlistenerToButton(button) {
button.divPicture.addEventListener("mouseover",function() { anotherFunction(button) },false)
}
// ....
for(var i = 0; i<buttons.length; i++) {
addEventlistenerToButton(buttons[i]);
}
In addition to that, you could also omit sending the button to the eventlistener completely and get the button from the event object directly:
for(var i = 0; i<buttons.length; i++) {
buttons[i].divPicture.addEventListener("mouseover", anotherFunction, false);
}
function anotherFunction(ev) {
ev = ev || window.event;
var src = ev.target || ev.srcElement;
console.log(src.parentNode);
}

Javascript dynamic onChange event on select

I have this javascript snippet:
var selectName["id1","id2","id3"];
setOnClickSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
alert(selId);
$(selId).onchange = function() {
$(selId).value = $(selId).options[$(selId).selectedIndex].text;
}
}
}
But when I change value to my id1 element, the alert wrote me always "id3".
Can I fix it?
EDIT:
I've changed my snippet with these statements:
setOnChangeSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(selId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
}
But selId is always the last element.
This is caused by the behavior of javaScript Closure, selId has been set to the selectName[2] at the end of the loop and that's why you get 'id3' back.
An fix is as following, the key is wrap the callback function inside another function to create another closure.
var selectName = ["id1","id2","id3"];
var setOnClickSelect = function (prefix, selectName) {
for(var i = 0; i < selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(thisId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
};
Ps: there is synyax error for var selectName["id1","id2","id3"], you should use var selectName = ["id1","id2","id3"];

Categories