jQuery selection via class generating error - javascript

I am attempting to get some divs that all have the same class via the following jquery code:
var divs = $('.divClass');
And then loop through and get each height and left css property of the each div. I have tried 2 methods, both unsuccesfully.
First:
divs.each( function(d) {
var height = d.height();
// also tried:
var height2 = d.css("height");
});
Second:
var divArray = divs.toArray();
for (var i = 0; i < divArray.length; i++) {
var height = divArray[i].height();
}
Both these throw the error: "Uncaught TypeError: Undefined is not a function.". What is really strange is divs.first().height() returns the correct value. Also, the array has the correct number of members. Is there something wrong I am doing with either iteration scheme? This is incredibly annoying. Today I found out why everyone complains about JS. Thanks

each() has two arguments, index and the DOM element (which would also be the value of this)
divs.each( function(index, element) {
var height = $(element).height();
});

You are doing it wrong because d will not be a DOM element but the zero-based index of the current element in the matched set.
Instead of this, within the .each callback refer to the current element with $(this):
divs.each(function() {
var height = $(this).height();
});
You can also get the current element from the second (not first) argument passed to the callback, but there's no need to.

Related

Javascript: Recursion, jQuery Error

Working on a navigation menu script with jQuery. The script is being designed with recursion so that there is no hard coded limit to the number of levels the menu has.
I'll start with the code:
navigationMenu.prototype.reset = function ( ulElement, colorIndex, colors ) { //Color index should always be 1 when directly calling this function
var listItems = $(ulElement.children);
var numItems = listItems.length;
var targetWidth = (100 / numItems) + '%';
listItems.each( function ( x ) {
var children = $(listItems[x].children);
var xT = $(listItems[x]).prop('tagName');
var subMenu = null;
children.each( function ( y ) {
var yT = $(children[y]).prop('tagName');
if (yT == 'UL') {
subMenu = $(children[y]);
} else if (yT == 'A') {
$(children[y]).css('background-color', colors[colorIndex-1]); //Offset by 1 to facilitate for 0 indexed arrays
$(children[y]).hover( function () { //Set hover color to the opposite
$(children[y]).css('background-color',colors[(3-colorIndex)-1]); //3-1 = 2 and 3-2 = 1, subtract 1 to facilitate 0 indexed arrays
}, function() {
$(children[y]).css('background-color',colors[colorIndex-1]); //3-1 = 2 and 3-2 = 1, subtract 1 to facilitate 0 indexed arrays
}); //Rest of style SHOULD be handled by css (width 100%, text color, text align)
}
});
if (subMenu !== null) { //Recurse
navigationMenu.prototype.reset(subMenu, (3 - colorIndex), colors); //Not defined?
}
if (xT == 'LI') { //Format the element
$(listItems[x]).css('width',targetWidth);
$(listItems[x]).css('background-color', colors[colorIndex]);
}
});
};
Next, The error:
Uncaught TypeError: Cannot read property 'firstChild' of null <-whitespace-> jquery-1.11.1.min.js:2
What concerns me is that the error does not seem to come directly from my code, rather, a function within the jQuery library; however, I'm placing good money on the fact that it is because of something I did wrong.
A live demo can be found here:
http://proofoftheilluminati.com/test/test.html
For an idea of the final look of the menu you can see the top level with hover effect and a simple JS script that maths the link widths here:
http://proofoftheilluminati.com/test/index.html
Script:
http://proofoftheilluminati.com/test/scripts/menu.js
I'm hosting a freshly downloaded copy of jQuery version 1.11.1:
http://proofoftheilluminati.com/test/scripts/jquery-1.11.1.min.js
What it should be doing:
Top level list should be orange with black over effect
second level list should be black with orange hover effect
third level list should be same as first, etc.
Positioning is handled by external css file
What it is doing:
Handles top level list correctly, seems to error before style second level list.
Please let me know if I left anything out. I try to be thorough.
Edit: The supplied code has a comment on the line that calls itself:
//Not defined?
This was left over from a previous error, I was having trouble getting it to recognize the recursive function call. I tried the following lines here and they would not allow the function to progress:
this.reset(subMenu, (3 - colorIndex), colors);
reset(subMenu, (3 - colorIndex), colors);
navigationMenu.reset(subMenu, (3 - colorIndex), colors);
Additionally, this function is called when the document is ready:
$(document).ready(function() {
s = new navigationMenu('#NavMenu', '#884106', '#000000', -1);
});
Edit: modified code to use x/y instead of index and xT/yT instead of tag (removed nested variables with same name)
When you first call navigationMenu.prototype.reset, I'm guessing ulElement is a DOM element, but when you call it recursively, you are passing it subMenu, which is a jQuery object. That will be a problem for the following line:
var listItems = $(ulElement.children);
Try changing the following line of code:
navigationMenu.prototype.reset(subMenu, (3 - colorIndex), colors);
To:
navigationMenu.prototype.reset(subMenu[0], (3 - colorIndex), colors);
I prefer to always prefix variables that refer to jQuery objects with "$" to keep them straight.
You could also use this inside the functions given to .each(). So instead of:
children.each(function(index) {
var tag = $(children[index]).prop('tagName');
You could have:
children.each(function() {
var $child = $(this),
tag = $child.prop('tagName');
You could also consider using the jQuery .children() method, instead of the children DOM element property

Unable to set number in .data() method

I am trying to set a number as my data type dynamically using .data() method in jQuery, but so far no luck. This works using the .attr() method as I have listed in below. Why does the .data() method not work with numbers?
var container = $(this).find('#container'); // element which should have the data
Attempt 1:
container.data(24, "opacity:0;");
Attempt 2:
container.data("24", "opacity:0;");
The following code works using .attr():
container.attr("data-123", 1223);
My personal code:
function loader($div, $page) {
$div.load(siteURL + $page + '/ #container', function() {
var container = $(this).find('#container');
container.data("24", "opacity:0;");
container.attr("data-24", "opacity:0;"); //this works...
});
}
loader($('section#about'), 'about');
UPDATE: Here is a jsFiddle
Historically jQuery supported the data() method by keeping track of values set using it in a separate data structure. This allowed you do store things like objects using the API.
While retrieving data, the API will check both the data attribute as well as well as its internal store for the value.
Setting, however still goes straight to the internal store.
Since the question has changed significantly:
$.data will fail when sending a number.
Doing this in the console you will see the following (none of these affect the markup of the element itself):
// Error
$('div').data(24, 'foo')
TypeError: Object 24 has no method 'replace'
// Success
$('div').data("24", 'foo')
b.fn.b.init[966]
$('div').data("24")
"foo"
// Success
$('div').data("24", 24)
b.fn.b.init[966]
$('div').data("24")
24
None of these will affect a data attribute on the element itself. The end result of the markup will be:
<div>Hello</div>
If you are looking to set a data-xxx attribute on the element, or any attribute for that matter, an elements attribute must begin with an alpha character:
// Error
$('div').attr("24", "opacity:0")
InvalidCharacterError: An invalid or illegal character was specified, such as in an XML name.
// Success
$('div').attr("data-24", "opacity:0")
b.fn.b.init[966]
The end result of the successful call will be:
<div data-24="opacity:0">Hello</div>
skrollr does not use jQuery's .data function it parses the DOM elements attribute list which is why using .attr("data-24" works as the attribute is added to the DOM attribute list
.data("24","somevalue") does not update the DOM elements attribute list,
.attr("data-24","somevalue") however does update the DOM elemetns attribute list which allows skrollr to parse the new style.
FROM SKROLLR.JS
Starting at Line 343:
//Iterate over all attributes and search for key frame attributes.
var attributeIndex = 0;
var attributesLength = el.attributes.length;
for (; attributeIndex < attributesLength; attributeIndex++) {
var attr = el.attributes[attributeIndex];
if(attr.name === 'data-anchor-target') {
anchorTarget = document.querySelector(attr.value);
if(anchorTarget === null) {
throw 'Unable to find anchor target "' + attr.value + '"';
}
continue;
}
//Global smooth scrolling can be overridden by the element attribute.
if(attr.name === 'data-smooth-scrolling') {
smoothScrollThis = attr.value !== 'off';
continue;
}
//Global edge strategy can be overridden by the element attribute.
if(attr.name === 'data-edge-strategy') {
edgeStrategy = attr.value;
continue;
}
var match = attr.name.match(rxKeyframeAttribute);
if(match === null) {
continue;
}
var constant = match[1];
//If there is a constant, get it's value or fall back to 0.
constant = constant && _constants[constant.substr(1)] || 0;
//Parse key frame offset. If undefined will be casted to 0.
var offset = (match[2] | 0) + constant;
var anchor1 = match[3];
//If second anchor is not set, the first will be taken for both.
var anchor2 = match[4] || anchor1;
var kf = {
offset: offset,
props: attr.value,
//Point back to the element as well.
element: el
};
keyFrames.push(kf);
//"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
kf.mode = 'absolute';
//data-end needs to be calculated after all key frames are know.
if(anchor1 === ANCHOR_END) {
kf.isEnd = true;
} else {
//For data-start we can already set the key frame w/o calculations.
//#59: "scale" options should only affect absolute mode.
kf.frame = offset * _scale;
delete kf.offset;
}
}
//"relative" mode, where numbers are relative to anchors.
else {
kf.mode = 'relative';
kf.anchors = [anchor1, anchor2];
}
}
container.data("24", "opacity:0;"); works right? First parameter must be a String (as per jQuery spec).
Because the data() method expects a string.
Hence I guess it'll be breaking if you pass it a number!
Can you try using the jQuery object itself:
var container = $(this).find('#container');
jQuery.data(container, "24", "opacity:0;");
alert("24 is equal to: " + jQuery.data(container, "24") );
You should note this will not affect the DOM as it uses jQuery's local storage.
JSFiddle: http://jsfiddle.net/markwylde/8Q5Yh/1/
container.data("24") actually works for me.
It depends on the version of jQuery too. When you call .data as a setter, all versions will call either .split or .replace which are String methods, not Number methods. Using .data(24) as an accessor seems to work past version 1.8.
This may also be browser dependent as dataset is not available in some browsers.
My advice would be to use a descriptive name for the data rather than just a number (unless you're talking about form 24 or something, but then why not use form24?)
EDIT: Using .data does not alter the HTML if the dataset attribute is available on the element. Using .attr always sets an attribute which will alter the HTML. This has nothing to do with using strings vs. numbers.

How can I keep a jQuery DOM element reference when splicing/sorting an array?

I have an array of objects. One of the properties of these objects is a jQuery reference to a DOM element that may or may not actually be attached to the DOM at any given time;
For example:
Array = [{
name : 'whatever 1',
element : $('<div id="item_1" class="item"><img src="" /></div>')
},
{
name : 'whatever 2',
element : $('<div id="item_2" class="item"><img src="" /></div>')
}];
When this array is untouched I can detach and append these elements to the DOM without any troubles as well as use standard jQuery methods upon the elements.
For example:
Array[0].element.find('img');
...Will work fine.
However if I sort or splice this array, I lose the references.
I understand the reason why this is happening but what I would like to know is if there is anyway around this so that this element can continually be changed, attached, detached, modified while sorting or splicing the overall array itself?
Thanks in advance.
EDIT:
Here is a code sample of my rearrange function:
rearrangeItems : function(){
var self = this;
var offset = 0;
// get number of items that are less than insertindex
for(var i = 0; i < self.cache.selecteditems.length; i++) {
if(self.cache.selecteditems[i] < self.cache.rearrangepos){
offset++;
}
}
//subtract the offset from the intended insertion index
var rearrangeindex = self.cache.rearrangepos - offset;
var removedItems = [];
//sort the selected element indexes into ascending order
self.cache.selecteditems.sort(function (a, b) {
if (a < b) return -1;
else if (b < a) return 1;
return 0;
});
//remove the selected array elemens from the overall array and push them into the temporary array
for(var i = 0; i < self.cache.selecteditems.length; i++) {
var index = self.cache.selecteditems[i];
removedItems.push(self.cache.items.splice(index - removedItems.length, 1)[0]);
}
//Add the selected array elements back into the main array at the correct insertion point
self.cache.items.splice.apply(self.cache.items, [rearrangeindex, 0].concat(removedItems));
}
When calling this function all array elements are reordered exactly as intended.
Before reordering I can do the following:
self.cache.items[index].html.find('img');
Afterwards however, it will result in an empty object (the html property is the equivalent of the element property in my example above).
I would work with the ID, cause you have one. Don't know if this is the cleanest solution but it will work.
Calling your example like this:
$('#' + Array[0].element.attr('id')).find('img');
Hope this works for you.
Sadly this was down to my own stupidity. In my code I was referencing the element incorrectly.
I was actually doing the following:
self.cache.items[index].html.find('#image_' + index);
After reordering the elements I was intentionally resetting indexes afterwards, therefore when calling this after a sort/reorder the element was incorrect.
by switching to a class selector everything was fixed.
self.cache.items[index].html.find('.image_item');
How embarrassing! My apologies to all.

Javascript style.left is empty string

next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
pos = el.style[direction].split('px')[0];
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
I'm using the simple code above to try and move an element. Now when I breakpoint on this, the value of el.style[direction] is: " ". So then when i try to do anything with it, it breaks. Why would this be? Isn't style.left supposed to return an integer?
Why would this be?
Presumably because it hasn't been set to anything.
Isn't style.left supposed to return an integer?
No. It is supposed to return a string containing the value of the CSS left property as set directly on the element (either by setting the JS property itself or by using a style attribute). It does not get a value from the cascade and it should only be an integer if the value is 0 (since all other lengths require units).
See How to get computed style of a HTMLElement if you want to get the computed value for the property rather than what I described in the previous paragraph.
style provides the original style as calculated from the CSS, not the updated and possibly dynamic style. You probably want currentStyle instead.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
var lft = document.defaultView.getComputedStyle(el)[direction];
pos = parseFloat(lft);
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
Note: like Elliot said you'll have to get the currentStyle/computedStyle. Here's a way to make it cross-browser, however when applying styles via JS, this is one good case where some sort of framework (eg Prototype [Scriptaculous], jQuery) would be useful.
Just a comment.
In your code:
> pos = el.style[direction].split('px')[0];
> pos = parseInt(pos, 10) + 10;
The split in the first line is superfluous, in the second line parseInt will convert (say) 10px to the number 10 just as effectively (and more efficiently) than what you have.
pos = parseInt(el.style[direction], 10);

select an array of elements and use them

Using this syntax:
var position = array($('#ipadmenu > section').attr('data-order'));
I cannot get my code to work. I have never used arrays before so im kind of lost on how to use them. (especially in jquery).
How would I make an array of all section elements and associate the value of data-order to that list. Example:
first section - data-order:1
second section - data-order:2
etc and then use that info afterwards.
Thank you!
Since .attr just gets one attribute -- the first one found by the jQuery selector -- you need to build your array element by element. One way to do that is .each (you can also use .data to extract data attributes):
var position = new Array;
$('#ipadmenu > section').each(function() {
position.push($(this).data('order'));
});
alert(position[0]); // alerts "1"
This will be an indexed array, not an associative array. To build one of those (which in JavaScript is technically an object, not any kind of array) just change the inner part of your .each loop:
var position = {};
$('#ipadmenu > section').each(function(i) {
position["section"+i] = $(this).data('order');
});
The resulting object position can now be accessed like:
alert(position['section1']); // alerts "1"
A different approach involves using jQuery.map, but since that only works on arrays, not jQuery objects, you need to use jQuery.makeArray to convert your selection into a true array first:
var position = $.map($.makeArray($('#ipadmenu > section')), function() {
return $(this).data('order');
} ); // position is now an indexed array
This approach is technically shorter than using .each, but I find it less clear.
Javascript:
var orders = [];
$('#ipadmenu > section').each(function() {
orders.push($(this).data('order'))
});
HTML:
<div id="ipadmenu">
<section data-order="1">1</section>
<section data-order="2">2</section>
</div>
You will want to do something like this:
// Get the elements and put them in an array
var position = $('#ipadmenu section').toArray();
console.log(position);
// Loop through the array
for (var i = 0; i < position.length; i++){
// Display the attribute value for each one
console.log("Section " + i + ": " + $(position[i]).attr('data-order'));
}
Working example here: http://jsfiddle.net/U6n8E/3/

Categories

Resources