Issue with jQuery data - javascript

I'm struggling a little. I'm attempting to utilize the cool feature of assigning a custom attribute to an element. However it isn't working.
Basically, I'm trying to assign multiple elements to the TR element identifying information in that row. Here is what it looks like:
for (x=0; x< theData.length; x++)
{
// create table row
var selector = "wc_" + wID + "_row_" + x;
oRow = document.createElement("TR");
oRow.setAttribute("id", selector);
tBodyO.appendChild(oRow);
var rowCount = 0;
var identCnt = 0;
for (var index in theData[x])
{
identCnt = 0;
if (hasIdentify)
{
for (y=0; y < theIdent.length; y++)
{
console.log("ROW: "+x+" , checking: "+index+" === "+theIdent[y]["data_name"]);
if (index === theIdent[y]["data_name"])
{
myrow = $("#" + selector);
//myrow.attr("test","works");
console.log("ident Dataname: "+theIdent[y]["data_name"]+ " identify:"+theIdent[y]["identify"]+" value: "+theData[x][index]);
jQuery.data( myrow, "test", { first: 16, last: "pizza!" });
alert(myrow.data("test"));
}
}
}
}
}
I've left out some code that's not really relevant, but I hope you get the picture. What I'm trying to do is this:
I have an array that contains "identity" information about a particular row of data. It can have 0, 1 or more such identity pieces. I want to store these as a custom data attribute on the <TR> element.
Each data will have a distinct key (example: i_0_1 where 0 is the data row number and 1 is the identity counter)
However, I can't get this to work. I've tried lots of alternatives even trying to resort to .attr() with no luck. What am I doing wrong??
I'm getting an alert that says undefined. When I try the myrow.attr("test","works"); route I can see the attribute in the DOM. Why won't the data() way work for me?

I just figured out what the problem is. I guess after reading the docs on jQuery.data() I didn't see anyone really clearly come out and say:
You will not see the data info in the DOM - element when you look at the source.
To the credit of everyone here, they did tell me, I just didn't know what I was reading (now that I know what is expected)
Matt wrote:
"ill not actually set an attribute or property on the element; jQuery stores this data in an internal cache, and merely maps it to the element you selected"
Although after playing around with the .attr() and setAttribute() functions I just became more confused.
Thanks everyone!

Related

getElementById(...) returns null unexpectedly

EDITED: I pretty much checked dozens of articles here on this issue, but I simply can't find the problem in my code: The line (commented below) returns null, although I expect it to return the particular element I created and added above.
element2 = document.createElement("button");
element2.innerHTML = "-";
element2.text = rowCounter;
element2.style.backgroundColor = 'white';
element2.id = "kuerzer"+rowCounter; //rowCounter is an iterator variable
ell2.appendChild(element2);
console.log(document.getElementById(element2.id)); //returns null
However, I am now trying to find this element in another function like this:
function structureGantData(){
var gantData = [[]];
for(i=0; i<document.getElementById("myTable").rows.length; i++){
console.log("schleife");
gantData[i][0] = document.getElementById("kuerzer",(i+1)).text; //ISSUE
Could anyone help me? :-)
Thanks alot!
Fabian
You have not added the created element to the DOM, so it cannot be found there.
You may use say Node.appendChild (or any other similar) method to append it somewhere first.
I believe you meant to call document.getElementById("kuerzer" + (i + 1)) rather than document.getElementById("kuerzer", i + 1) in structureGantData.

Dynamic array using variable

I am trying to dynamically access arrays, otherwise I would have to duplicate my code, when in reality the only difference would be buildablesets1 and buildablesets2.
var buildablesets1 = [""];
var buildablesets2 = [""];
var buildablesetsx = "buildablesets" + playerturn;
for (var i = 0; i < set.length; i++) {
if (doesOwnSet('player'+playerturn, set[i])) {
if(playerturn===1) buildablesets1.push(set[i]);
}
}
if (buildablesetsx.length > 1){
var b = $("#buildsets"+playerturn);
for (k = 1; k < buildablesetsx.length; k++){
b.append("<option value='" + buildablesetsx[k]+ "'>" + buildablesetsx[k] + "</option>");
}
}
The code pushes any sets owned by the player to buildablesetsX. Then the dropdown box #buildsetsX is populated with all the owned sets.
1. How do I get the effect of buildablesetsX where X depends on the players turn.
2. I want to depopulate the dropdown box on each turn otherwise it will duplicate, since it is appending. (would apreciate a better way of doing this, ideally I want to populate the dropdown box only if there is a new set).
Plus sorry I understand that this question has been asked before, but I didnt understand the answers or the question exactly.
Any time you find yourself creating variables with sequential numbers, and wanting to access them dynamically, what you really should be using is an array. In this case, you should use a two-dimensional array:
var buildablesets = [[""], [""]];
The first dimension is the player number, the second dimension is the list of sets that player has built.
To access a particular player's sets, do:
buildablesetsx = buildablesets[playerturn];
The rest of your code will work as you've written it, with the above variable assignment.

Fast way to append large list to dom

I am trying to populate an empty list using an array of photo names. I have made this work already, but with a large amount of photos it can become relatively slow. More than 500 photos is not unusual.
I am wondering if someone could find a way to make this code work faster, or tell me what make this code run slow so I can have another look at it myself.
The code I have is as follows. this.photoListElement is a jQuery object referring to the unordered list element. photoNames is an array of photo name strings. Variables are declared at the top of the function which is not shown here. isThumbDownloaded checks a variable in an object. getThumb and getThumbId are functions which add some strings together.
(...)
place = [];
for(i=0; i< photoNames.length; ++i) {
photoName = photoNames[i];
if(coverages.isThumbDownloaded(coverageId, photoName)){ // A function which checks if a photo is downloaded. If it is, the photo should not be hidden, and the right background should be applied.
bg = 'background-image:url(\''+coverages.getThumb(coverageId, photoName) + '?' + new Date().getTime()+'\');';
cls = '';
} else {
bg = '';
cls = 'hidden';
}
place[i] = '<div id="'+ this.getThumbId(photoName) +'" photoName="'+photoName+'" style="'+bg+'" class="phoPhoto '+cls+'" onclick="$.mobile.changePage($(\'#carousel\'), {transition: \'slidefade\'})"></div>';
}
this.photoListElement.html(place.join(''));
(...)
Help is very much appreciated.
After research
The problem is not the loop, although some minor optimizations can be done, but the the dom insertion.
In the loop you are counting the number of photoNames on each iteration:
for(i=0; i< photoNames.length; ++i)
Keep the length in a variable instead and the loop will be faster:
for (var i = 0, ilen = photoNames.length; i < ilen; i += 1)
Also, string concatenation might be faster than array join, check this jsperf .
So:
place = "";
...
place += '<div>.......';
..
this.photoListElement.html(place);
Try using
$('ul').append(ilen);

Need help optimizing script that hides table rows

I made this Greasemonkey script:
var maxpi = 250;
var p1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]/td[11]";
var p2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]/td[11]";
..
var p25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]/td[11]";
var r1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]";
var r2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]";
..
var r25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]";
var xpathPI1 = document.evaluate(p1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathPI25 = document.evaluate(p25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var xpathrow1 = document.evaluate(r1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathrow25 = document.evaluate(r25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (xpathPI1.singleNodeValue.textContent >maxpi ){
xpathrow1.singleNodeValue.style.display='none';}
..
if (xpathPI25.singleNodeValue.textContent >maxpi ){
xpathrow25.singleNodeValue.style.display='none';}
Basically, it checks a table row's 11th field and if its contents > than 250 it hides the row.
With my limited javascript knowledge took quite some time get this working.
The problem is that I have to rewrite every single line if I want to check-hide another row.
I want to make it more usable so I can use it on similar tables without rewriting the whole thing.
Maybe I need to use a different XPath type or use some kind of changing variable?
Of course, there are more ways to improve your script.
Firstly, you need to thoroughly think through WHAT exactly you want to look for. Is is every row and column? Is it rows/columns with some text, class, any other attribute? You can even select only those nodes that have their text value greater than your maxpi!
Read something about XPath, the possibly best resource is the official one.
Some random thoughts on what could be useful regarding XPath:
//table//tr[5]/td[2] ... the double slash is the deal here
//table//tr/td[number(text()) > 250] ... the number() and text() functions
When talking about JavaScript, that would be a little tougher, because there are so many things you could use!
Just for starters - you can create dynamically changing xpath expressions by String concatenation and For loop, like this:
for (var i = 1; i <= maxNumberOfRows; i++) {
var p1 = "//table/tbody/tr[" + i + "]";
// more work goes here...
}
Also, you could use arrays to store multiple nodes returned by your XPath expressions and work on them all with just a single command.
For more JavaScript, I would recommend the first chapters of some JavaScript tutorial, that will boost your productivity by a lot.
Use a loop and functions. Here's one way:
hideRowsWithLargeCellValue (
"/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[",
25,
"]/td[11]",
250
);
function hideRowsWithLargeCellValue (xpathPre, maxRows, xpathPost, maxpi) {
for (var J = maxRows; J >= 1; --J) {
var srchRez = document.evaluate (
xpathPre + J + xpathPost,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
if (srchRez.singleNodeValue && srchRez.singleNodeValue.textContent > maxpi) {
var rowToHide = srchRez.singleNodeValue.parentNode;
rowToHide.style.display='none';
}
}
}
Then read "Dont Repeat Yourself" (sic).

Sorting Divs in jQuery by Custom Sort Order

I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;

Categories

Resources