Append one dynamically-created element to another - javascript

I have the following prototype-based code:
for (j = 0; j < Math.floor(len / 2); ++j) {
var tr = $('tr');
var id1 = id[1] + (j * 2);
var id2 = id[1] + (j * 2 + 1);
// need to change the bits below here I believe
tr.appendChild(id1.createCheckbox());
tr.appendChild(id1.createButton());
tr.appendChild(id2.createCheckbox());
tr.appendChild(id2.createButton());
t[i].appendChild(tr);
}
Which is prototype-based code to append the result of another function to my new <tr> element. The index [i] comes from a for-loop which envelopes this for-loop and is working fine.
My question is, how to convert this into jQuery? Basically I want to do the opposite of this case here.
Before anyone suggests just dynamically creating a new button element, the createButton() and createCheckbox() are essential functions to create and format special buttons and also work fine.
Cheers peeps

First of all check this code again. Now it manipulates with the same element (with identifier "tr") in each iteration of loop. That's strange.
Find methods createCheckbox() and createButton(), they may look like
Number.prototype.createCheckbox = function() {
return new Element('input', {type: 'checkbox', value: 'cb' + this});
}
and convert them to functions. After conversion they should accept number (or maybe string if id[] is array of strings) as an argument and return jQuery collection with created checkbox/button:
function createCheckbox(id) {
return $('<input type="checkbox" value="cb' + id + '">');
}
Convert your code to
for (j = 0; j < Math.floor(len / 2); ++j) {
var $tr = $('#tr'); // get element with id="tr"
var id1 = id[1] + (j * 2);
var id2 = id[1] + (j * 2 + 1);
$tr.append(createCheckbox(id1));
$tr.append(createButton(id1));
$tr.append(createCheckbox(id2));
$tr.append(createButton(id2));
$tr.appendTo(t[i]); // note that t[i] is element, not a jQuery collection
}

not sure exactly what you asking for, maybe this:
for (j = 0; j < Math.floor(len / 2); ++j) {
var tr = $('<tr></tr>');
var id1 = id[1] + (j * 2);
var id2 = id[1] + (j * 2 + 1);
// need to change the bits below here I believe
tr.append(id1.createCheckbox());
tr.append(id1.createButton());
tr.append(id2.createCheckbox());
tr.append(id2.createButton());
$(t[i]).append(tr);
}

Try document.createElement('tr'); instead. It should work with the rest of your code, which is using JavaScript DOM manipulation. If you'd rather switch to jQuery DOM manipulation, use MamaWalter's approach.

Related

append() and $(this).addClass() method in jQuery is not working

I am new to jQuery and I am refactoring former JavaScript code into jQuery. I use the append() and $(this).addClass() methods, but it seems they don't work. I don't know what the problem is.
The JavaScript code is about creating a puzzle game (15 puzzles). I am trying to add an element into an HTML file in the jQuery way.
var tile = function(i, j) {
this.seq = i * 4 + j + 1;
this.row = i + 1;
this.column = j + 1;
if (i * 4 + j != 15) {
$(this).addClass("block puzzle row" + this.row + " column" + this.column);
var xPosition = -j * 88;
var yPosition = -i * 88;
$(this).css("backgroundPosition", xPosition + "px " + yPosition + "px");
} else {
$(this).addClass("block row" + this.row + " column" + this.column);
$(this).attr('id', "blank");
}
}
function Init() {
var node = $("#imgContent"); // imgContent is a div
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var t = new tile(i, j);
node.append(t);
}
}
// Generate the original picture before the start
Judge.isStart = false;
}
How do I use these jQuery methods properly?
You're using $(this) incorrectly.
When the tile() function is called with new, it is instantiated as an object and 'this' becomes a reference to that object. In doing so, you can assign values to its internal state using 'this.' syntax.
jQuery is primarily designed to manipulate the Document Object Model (DOM). Usually you'll pass a selector into jQuery which finds the matching HTMLElements from the document and returns them for manipulation. $(this) within the context of tile is passing the this reference which jQuery just returns back to you.
Important part here is that $(this) represents the tile object and not actually an element from the document. The .css .addClass and .append functions aren't applicable, as they work on elements. You need to pass a selector or an element into jQuery to use them.
A solution is to create an element within tile that can be appended to the document.
https://jsfiddle.net/hxqduoef/2/
var tile = function(i, j) {
this.seq = i * 4 + j + 1;
this.row = i + 1;
this.column = j + 1;
// $("<div></div>") creates a new div element wrapped in the jQuery object, it isn't part of the document until it gets appended. saving to this allows further manipulations within this constructor.
this.element = $("<div>Tile at "+ i +" "+ j +"</div>");
if (i * 4 + j != 15) {
this.element.addClass("block puzzle row" + this.row + " column" + this.column);
var xPosition = -j * 88;
var yPosition = -i * 88;
this.element.css("backgroundPosition", xPosition + "px " + yPosition + "px");
} else {
this.element.addClass("block row" + this.row + " column" + this.column);
this.element.attr('id', "blank");
}
}
function Init() {
var node = $("#imgContent"); // imgContent is a div
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var t = new tile(i, j);
node.append(t.element);
}
}
// Generate the original picture before the start
// Judge.isStart = false;
}
This adds the "this.element" property, sets it as a new DIV element and references it instead of the t in the node.append(t.element) statement.
I hope this helps.
context of this is getting changed, keep this to a variable, then use that variable.
var $this = this;
var tile = function(i, j) {
$this.// your code
}
$(this) is actually defined by the invoker of the function, while explicit variables remain intact inside the function declaration block known as the enclosure.
What's happening in your function is tile(i,j), is being called explicitly, this means context or the "this" of the function is the window object.Being specific to your question, this is bound to the global object, that is window.
For example, consider the following function,
function myFunc(){
}
and,
var myObj= { myFunc: myFunc};
If you call using myObj.myFunc(); then this is bound to myObj.
If you call myFunc() directly, such as, myFunc();,
then this is bound to the global object, that is window.
Hence, calling a function without a following parent object will generally get you the global object which in most browsers means the window object.

adding whole total using javascript

function total()
{
var tot = 0;
for(var i = 1; i <= 20; i++)
{
var total_id = "total_" + i;
tot = tot + document.getElementById(total_id).value;
}
document.getElementById(total).value = tot;
}
"this code should display total; i have several total_i id to display my total in each row. then i have display that row total to my total named form"
tot = tot + document.getElementById(total_id).value;
Note that the value of an element is a string, so + here is a string concatenation, not addition (for example, 1 + 1 = 11)
If you want to do addition (1 + 1 = 2), use
tot = tot + parseInt(document.getElementById(total_id).value);
you can replace your code line tot = tot + document.getElementById(total_id).value;
with: tot = tot + parseInt(document.getElementById(total_id).value); as the javascript is taking it as a string not a integr value. We can convert it to integer by using parseInt().
Your immediate problem, as mentioned in another answer, is that you're adding strings, specifically the value property on a DOM element which is a string. You'll find any number of questions on SO about that.
If you have the elements in an array, finding the sum just becomes
function add(a, b) { return a + b; }
function sum(array) { return array.reduce(add); }
sum(elements.map(function(elt) { return +elt.value; });
However, you're also using the anti-pattern of using ID's as a poor-man's way of identifying and referring to DOM elements. It's better just to remember the elements themselves as you create and add them (if in fact that's how they're being created). That way, you don't have to spend the rest of live constructing IDs and adding them to elements and then constructing them again to pass to getElementById to find them again. By "remembering the elements themselves as you create them", I mean instead of doing something like this:
for (i=0; i<n; i++) {
var elt = document.createElement('div');
elt.setAttribute('id', 'total_' + i);
parent.appendChild(elt);
}
or the equivalent in jQuery, do something like
var elements = [];
for (i=0; i<n; i++) {
var elt = document.createElement('div');
elements.push(elt);
parent.appendChild(elt);
}
Now you have the array of elements themselves, and you don't have to poking around with getElementById every time you want to access them.

Adding two indexed geometries to a BufferGeometry

At the moment I'm using a set of predefined data (containing the indices, vertices and colors) and multiple THREE.Geometry's to add objects to a scene. As you could imagine, this is pretty slow as it requires adding and removing many objects at once or merging many together.
However if I used a single THREE.BufferGeometry this would allow me to use _gl.bufferSubData to add and remove objects and in theory should have minimal affect on the performance.
The trouble I'm having is putting this into practice. I already have the bufferSubData function working, however I'm unable to add two sets of data in the same BufferGeometry. I'm guessing this is because the data does not follow on from each other (as they're two separate objects) so they're both using the same indices. This image shows the result.
I've created a JSFiddle which uses an array named section containing the chunk data. If anyone could take a look and change it so it adds both sets of data I would really appreciate it:
http://jsfiddle.net/dUqwT/
Also, I've been unable to find the purpose of the index offset. If someone could link or explain what it's used for, that would be very helpful.
Thanks for the help!
Alright, worked out how it's done and I've updated the JSFiddle:
http://jsfiddle.net/dUqwT/1/
It was more simple than I thought and it had nothing to do with the index offset (still no idea what that does). I just ensured that it appends to each array correctly so positions, indices and colors aren't overwritten. Did that by using two variables and setting them to the appropriate lengths.
Realistically, as the objects I'm adding to the BufferGeometry will be dynamic, I'll need to give each object a certain amount of the buffer it can use instead of setting two vars to the length. That will allow me to then remove and change each object using _gl.bufferSubData.
for (var chunkID = 0; chunkID < 2; chunkID++) {
var chunkIndices = section[chunkID].indices;
var chunkVertices = section[chunkID].vertices;
var chunkColors = section[chunkID].colors;
var sectionXPos = chunkID * 32;
var sectionYPos = 0;
var sectionZPos = 0;
// Add indices to BufferGeometry
for ( var i = 0; i < chunkIndices.length; i ++ ) {
var q = chunkIndices[i];
var j = i * 3 + iLength;
indices[ j ] = (q[0] + vLength / 3) % chunkSize;
indices[ j + 1 ] = (q[1] + vLength / 3) % chunkSize;
indices[ j + 2 ] = (q[2] + vLength / 3) % chunkSize;
}
// Add vertices to BufferGeometry
for ( var i = 0; i < chunkVertices.length; i ++ ) {
var q = chunkVertices[i];
var j = i * 3 + vLength;
// positions
positions[ j ] = q[0] + sectionXPos;
positions[ j + 1 ] = q[1] + sectionYPos;
positions[ j + 2 ] = q[2] + sectionZPos;
// colors
var hexColor = chunkColors[i / 4];
color.set(hexColor);
colors[ j ] = color.r;
colors[ j + 1 ] = color.g;
colors[ j + 2 ] = color.b;
}
iLength += chunkIndices.length * 3;
vLength += chunkVertices.length * 3;
}

How to make js "for" loop more efficient?

I am wondering how to make the loop below more efficient. On my page it has more iterations than 100, that is why, as you can imagine it is troublesome.
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
document.getElementById('nodeHolder').appendChild(el);
}
Thanx for help in advance.
All I can really suggest is getting a reference to the nodeHolder element outside of the for:
var nodeHolder = document.getElementById('nodeHolder');
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
nodeHolder.appendChild(el);
}
Or perhaps using a document fragment to hold the interim changes/appends and then add that to to the nodeHolder after the loop:
var fragment = document.createDocumentFragment();
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
fragment.appendChild(el);
}
document.getElementById('nodeHolder').appendChild(fragment.cloneNode(true));
JS Fiddle showing both approaches (with timing if the console is available).
jQuery way...
var d = '';
for(var i=0;i<1000;i++) d += '<div>Node ' + (i + 1) + '</div>';
$('#nodeHolder').append(d);
Or javascript inside html...
<div id="nodeHolder">
<script>(function() { for(var i=0;i<1000;i++) document.write('<div>Node ' + (i + 1) + '</div>'); })();</script>
</div>
Maybe: You could generate a HTML String in the for loop like:
<div>Node 1</div><div>Node 2</div>
and then set the .innerHTML property of nodeHolder to this string after the whole loop is completed.
(... and let the DOM renderer figure out how to best optimize the action)
In summary:
Cache your DOM selector.
Ditch the for loop, and go for a reverse while loop.
Only append your element to the DOM once. The DOM is almost always the bottleneck.
In this pattern, you can take advantage of a reverse loop:
//Cache the DOM element
var node = document.getElementById('nodeHolder'),
markup = [];
//Run the loop backwards for additional speed
var i = 10000;
while(i--){
markup.push('<div>Node ' + (i + 1) + '</div>');
}
//Reverse the array, join it, then set the innerHTML
node.innerHTML = markup.reverse().join('');​
Working example: http://jsfiddle.net/ZAkMZ/3/
Reverse while loop speed reference: https://blogs.oracle.com/greimer/entry/best_way_to_code_a
jQuery version:
//Cache the DOM element
var node = $('#nodeHolder'),
markup = [];
//Run the loop backwards for additional speed
var i = 10000;
while(i--){
markup.push('<div>Node ' + (i + 1) + '</div>');
}
//Reverse the array, join it, then set the innerHTML
node.append(markup.reverse().join(''));​
You definitely need to use DocumentFragment as suggested by #David Thomas.
The most efficient version as I see is this... cloneNode is always better than createElement, switching between shallow or deep copy (still faster than createEl) as required.
Not that it gives a significant gain but you should shy away from assignments when you can. Store data in variables only when you need to. Of course when it comes to readability it's a different thing.
var fragment = document.createDocumentFragment();
var tplEl = document.createElement('div');
var contentTpl = document.createTextNode('Node ');
for (var i = 1; i <= 1000; i++) {
var curNode = contentTpl.cloneNode(false);
curNode.nodeValue = curNode.nodeValue + i;
tplEl.cloneNode(false).appendChild(curNode);
fragment.appendChild(tplEl);
}
document.getElementById('nodeHolder').appendChild(fragment);
Note that adding the fragment to the nodeHolder adds all childs of fragment as childs to nodeHolder triggering only 1 flow as opposed to 1000 flows in your earlier code.
Reference: Speeding up JavaScript
May be this way:
for (var i = 0; i < 100; i+=1) {
$("<div/>").appendTo("body").attr({"class":'txtHolder'});
$(".txtHolder").append(i+1);
}​

Javascript Random problem?

var swf=["1.swf","2.swf","3.swf"];
var i = Math.floor(Math.random()*swf.length);
alert(swf[i]); // swf[1] >> 2.swf
This case ,Random output One number.
How to Random output two different numbers ?
var swf = ['1.swf', '2.swf', '3.swf'],
// shuffle
swf = swf.sort(function () { return Math.floor(Math.random() * 3) - 1; });
// use swf[0]
// use swf[1]
Even though the above should work fine, for academical correctness and highest performance and compatibility, you may want to shuffle like this instead:
var n = swf.length;
for(var i = n - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = swf[i];
swf[i] = swf[j];
swf[j] = tmp;
}
Credits to tvanfosson and Fisher/Yates. :)
You can use splice to remove the chosen element, then simply select another randomly. The following leaves the original array intact, but if that's not necessary you can use the original and omit the copy. Shown using a loop to demonstrate how to select an arbitrary number of times upto the size of the original array.
var swf=["1.swf","2.swf","3.swf"];
var elementsToChoose = 2;
var copy = swf.slice(0);
var chosen = [];
for (var j = 0; j < elementsToChoose && copy.length; ++j) {
var i = Math.floor(Math.random()*copy.length);
chosen.push( copy.splice(i,1) );
}
for (var j = 0, len = chosen.length; j < len; ++j) {
alert(chosen[j]);
}
I would prefer this way as the bounds are known (you are not getting a random number and comparing it what you already have. It could loop 1 or 1000 times).
var swf = ['1.swf', '2.swf', '3.swf'],
length = swf.length,
i = Math.floor(Math.random() * length);
firstRandom = swf[i];
// I originally used `delete` operator here. It doesn't remove the member, just
// set its value to `undefined`. Using `splice` is the correct way to do it.
swf.splice(i, 1);
length--;
var j = Math.floor(Math.random() * length),
secondRandom = swf[j];
alert(firstRandom + ' - ' + secondRandom);
Patrick DW informed me of delete operator just leaving the value as undefined. I did some Googling and came up with this alternate solution.
Be sure to check Tvanfosson's answer or Deceze's answer for cleaner/alternate solutions.
This is what I would do to require two numbers to be different (could be better answer out there)
var swf=["1.swf","2.swf","3.swf"];
var i = Math.floor(Math.random()*swf.length);
var j;
do {
j = Math.floor(Math.random()*swf.length);
} while (j === i);
alert(swf[i]);
alert(swf[j]);
Edit: should be j===i

Categories

Resources