jquery offset method doesn't always work / exist - javascript

Good morning and happy new year everyone!
I've run into a snag on something and need to figure out a solution or an alternative, and I don't know how to approach this. I actually hope it's something easy; meaning one of you all have dealt with this already.
The problem is that I'm doing rollovers that contain information. They're divs that get moved to the absolute location. Now I've tried this with jquery 1.6 - 1.9.1. Of course this has to work in multiple browsers.
What needs to happen is on rollover show a div, and when you rollout of that div, make it hide.
...
// .columnItem is class level and works
$(".columnItem").mouseleave(function() {
$(this).css("display", "none");
});
...
$(".column").mouseenter(function() {
var currentItem = $(this)[0]; // this is where the problem is
// hide all .columnItems
$(".columnItem").css("display", "none");
// i get this error: Object #<HTMLDivElement> has no method 'offset' (viewing in chrome console)
var offsetTop = currentItem.offset().top;
var columnInfoPanel = $("#column" + currentItem.innerText);
});
So the immediate thought of some would be don't use $(this)[0]. Instead, I should use $(this), and you are correct! Where the other problem comes into play is by removing the array index, currentItem.innerText is now undefined.
The only thing I can think of is I'll have to mix both, but it seems like there should be a way to use the selector and get both options.
What have you all done?
Thanks,
Kelly

Replace:
var currentItem = $(this)[0];
With:
var currentItem = $(this).eq(0);
This creates a new jQuery object containing only the first element, so offset will work.
Then you can use either currentItem[0].innerText or currentItem.text(), whichever you prefer.

Skip the [0] at the beginning as you are saying.
But then change the last line to:
var columnInfoPanel = $("#column" + currentItem[0].innerText);
De-referencing the jQuery selector gives you the DOM-object.
If you want to stick to pure jQuery, the .text() / .html() methods will give you the same functionality.

Related

Using HammerJS with jQuery

Below I grab the container and on swipe left and right I trigger a click which is another function to do some fancy animation jazz.
var element = document.getElementsByClassName('container');
var hammertime = new Hammer(element);
hammertime.on('swiperight', function(ev) {
$('.btn-left').trigger('click');
});
hammertime.on('swipeleft', function(ev) {
$('.btn-right').trigger('click');
});
I keep getting this error about my Hammerjs library i've included (http://hammerjs.github.io/dist/hammer.min.js) Uncaught TypeError: a.addEventListener is not a function.
I did some research and found another stackoverflow question that says I can't pass jQuery through Hammerjs thus I should use jQuery Hammer. I took a look at the repo and it's not been updated in nearly two years.
Do I need to use the jQuery Hammer (https://github.com/hammerjs/jquery.hammer.js) extension, or can I pass through jQuery without needing it.
The problem is that new Hammer() is expecting a single element, and getElementsByClassName is returning a NodeList.
If you change it to
var hammertime = new Hammer(element[0])
that should work as expected, since you'll be passing the first element that matches .container. If you want to bind it to all elements that have the .container class, you'll need to loop through them on your own. I rarely use jQuery for anything, so I usually include this little helper function for looping through NodeLists
function doToAll(elems, fn){
var len = elems.length
while(--len > -1){
fn(elems[len])
}
}
In this case, you'd use it like this:
doToAll(document.getElementsByClassName('container'), function(element){
var hammertime = new Hammer(element)
//add in the rest of your functionality
})

One function for many buttons

I have dynamically created elements on the page, a picture and three buttons which are created upon clicking the main button.
All of this works, but now I am trying to change the display on the dynamically created div with the pics to "none".
More than one issue arises here for me, first I cannot find out how to make the div "images" the target, or select it.
I am trying to get one function to do this for all the elements, they are all structured equally just the pictures are different.
function hidePic(arrayPos){
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
document.getElementsByClassName("closingButton")[0].addEventListener("click", function(){
hidePic(0);
});
This is the relevant code, lines 4 to 10. If this is commented out, the rest of the code works, but as it is I get entirely unrelated errors in dev Tools.
Click this link to see Codepen.
So the question is, how can I best implement the above code?
So just working on the code above you can do this in order to make it work for all instances. First let me point out that this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]";
will never work. That line is building a string. What you really want to make that line work is:
var elem = document.getElementsByClassName("closingButton")[arrayPos];
But even that I find unnecessary. Take a look at this code.
function hidePic (elem) {
var finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
var closingButtons = document.getElementsByClassName("closingButton");
var index = 0, length = closingButtons.length;
for ( ; index < length; index++) {
closingButtons[index].addEventListener("click",
function () {
hidePic(this);
}
);
}
This first finds all elements with the class closingButton. Then for each one we attach a click event listener. Instead of attempting to pass some index to this hidePic function we already have our function context which is what you seem to be trying to find in the function so lets just pass that and use it to find the image inside.
Let me know if you have any questions. I took a look at your codepen as well. I am not sure you should be forcing all that interactive HTML into a button element honestly, which itself is considered an interactive element. Not sure that meets the HTML spec. Perhaps add that HTML below the button. I bet when you click on things inside of that button it will register as clicks on the button as well unless you remove the event upon inserting your elements but then it seems like its getting too complicated for the simple stuff you are trying to do here.
The codepen complains because there is no element with the "closingButton" class, so it's trying to call addEventListener on nothing, but I'm doubting that's the actual error you're seeing.
It's also worth nothing that I think this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
is excessive.
var elem = document.getElementsByClassName("closingButton")[arrayPos];
should be sufficient. Also not the syntax error at the end of the same line: it should be ; not ,. If this is the error in your code it could explain why you were getting "unrelated errors" syntax errors can cause misleading problems that are supposedly in other areas of the code!
Lastly, I'd highly recommend using JQuery to do your selection magic - it's exactly what it was designed for. If you're averse to using JS libraries, fair enough, but it would make your code a lot simpler and you can have reasonable confidence that it will perform the tasks about as optimally as is possible.

Adding span to numbers using Jquery

So I have been dabbling around in jQuery a bit this week. Trying to add some cool things to a wordpress theme I am currently working on.
With the help of this wonderful site, I received the following JS to wrap a span around numbers in a certain div. All was well until I started implementing more JS to find out this is actually causing my other JS to break.
//add span to numbers
var elem = document.getElementById('passage');
elem.innerHTML = elem.innerHTML.replace(/\b(\d+)\b/g, '<span>$1</span>');
However, I was informed since I am using jQuery, to take "document.getElementById" out and end up with this:
//add span to numbers
var elem = $( 'passage' );
elem.innerHTML = elem.innerHTML.replace( /\b(\d+)\b/g, '<span>$1</span>' );
I figured this would solve the situation with no luck. Any ideas of why this isn't working? Thanks
My end result is to be able to style numbers like this site does for their Bible verses: http://marshill.com/media/the-seven/lukewarm-in-laodicea-comfort-and-convenience-before-christ#scripture
If you're selecting by the id you need the CSS-alike id selector:
var elem = $('#passage');,
To select by class:
var elems = $('.className');
Using the $('#passage') selector, you end up with a jQuery object, rather than a DOM-node, so $('#passage').innerHTML (or elem.innerHTML) returns an error: Uncaught TypeError: Cannot call method 'replace' of undefined in Chromium 19.
To work around that, you can 'drop' back to a DOM-node with:
elem.innerHTML = $('#passage')[0].innerHTML.replace(/*...*/);
Or instead use jQuery:
elem.html(function(i,oldHTML){
return oldHTML.replace( /\b(\d+)\b/g, '<span>$1</span>' );
});​
JS Fiddle demo.
References:
jQuery selectors.
html().
For the first line, when working with ids, you want to prepend the string with a pound symbol; that is, $('#passage').
For the second line, note that doing $('#passage') returns a jQuery object, so you can't use standard methods on it. You should be using jQuery methods. The equivalent for innerHTML is .html().
So you would want to do the following:
//add span to numbers
var elem = $( '#passage' );
elem.html(elem.html().replace( /\b(\d+)\b/g, '<span>$1</span>' ));
A little ugly, but it gets the job done. David's answer is slightly more eloquent.

Javascript retrieve element by it's properties

Every HTML element has offset values. Can I return an element that has, for example, offsetLeft > 10?
Have never heard of this feature, therefore the question.
I'm aware that this can be done with loops, but those are slow. Had an idea about XPath, but cannot find anything related to properties within reference.
Thanks in advance!
P.S. No need for outdated browser compatibility- HTML5'ish can do.
As far as I'm aware, there is no way to do this that does not involve looping of some form. You could do it in standard JS with something along these lines:
var elems = document.getElementsByTagName("*"),
myElems = [];
for(var i = 0; i < elems.length; i++) {
if(elems[i].offsetLeft > 10) myElems.push(elems[i]);
}
Or, if you're using jQuery you can do it with a little less code (but it's probably even slower!):
var myElems = $("*").filter(function() {
return $(this).offset().left > 10;
});
If you think about it, you want to select all of the elements in a document with a certain property value. That's always going to involve a loop at some point, whether you write it yourself or not, as every element has to be checked.
Have you looked at this page yet? offset
jQuery can easily select attributes of elements
<div>Dont find me</div>
<div this="yes">Find me</div>
$('div[this=yes]'); // will select the second div
The problem you are going to run into is things like offset and position are calculated values, and not stored in the dom with the elements upfront. If you need to select by this, I would suggest putting them as attributes inside of the dom element itself. Then the above method with work just fine.
I would suggest the best way to do this would be to extend jQuery's selectors. Something like this works well:
$.extend($.expr[':'],{
offsetLeft: function(a,i,m) {
if(!m[3]||!(/^(<|>|=)\d+$/).test(m[3])) {return false;}
var offsetLeft = $(a).offset().left;
return m[3].substr(0,1) === '>' ?
offsetLeft > parseInt(m[3].substr(1),10) :
m[3].substr(0,1) === '<' ? offsetLeft < parseInt(m[3].substr(1),10) :
offsetLeft == parseInt(m[3].substr(1),10);
}
});
This would allow you to select elements using syntax such as
$('span:offsetLeft(>10)')
or
$('.someClass:offsetLeft(<10)')
or even
$('.someClass:offsetLeft(=10)')
Live example: http://jsfiddle.net/X4CkC/
Should add that this hooks into jQuery's selectors which are generally quite fast, but no doubt somewhere deep within there is a loop going on. There' no way of avoiding that.
You can easily do it with jQuery
$("*").each(function(index, elem){
if($(this).offset().left > 10){
// do something here with $(this)
}
});

JavaScript moving element in the DOM

Let's say I have three <div> elements on a page. How can I swap positions of the first and third <div>? jQuery is fine.
There's no need to use a library for such a trivial task:
var divs = document.getElementsByTagName("div"); // order: first, second, third
divs[2].parentNode.insertBefore(divs[2], divs[0]); // order: third, first, second
divs[2].parentNode.insertBefore(divs[2], divs[1]); // order: third, second, first
This takes account of the fact that getElementsByTagName returns a live NodeList that is automatically updated to reflect the order of the elements in the DOM as they are manipulated.
You could also use:
var divs = document.getElementsByTagName("div"); // order: first, second, third
divs[0].parentNode.appendChild(divs[0]); // order: second, third, first
divs[1].parentNode.insertBefore(divs[0], divs[1]); // order: third, second, first
and there are various other possible permutations, if you feel like experimenting:
divs[0].parentNode.appendChild(divs[0].parentNode.replaceChild(divs[2], divs[0]));
for example :-)
Trivial with jQuery
$('#div1').insertAfter('#div3');
$('#div3').insertBefore('#div2');
If you want to do it repeatedly, you'll need to use different selectors since the divs will retain their ids as they are moved around.
$(function() {
setInterval( function() {
$('div:first').insertAfter($('div').eq(2));
$('div').eq(1).insertBefore('div:first');
}, 3000 );
});
.before and .after
Use modern vanilla JS! Way better/cleaner than previously. No need to reference a parent.
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
const div3 = document.getElementById("div3");
div2.after(div1);
div2.before(div3);
All modern browsers are supported!
Browser Support
jQuery.fn.swap = function(b){
b = jQuery(b)[0];
var a = this[0];
var t = a.parentNode.insertBefore(document.createTextNode(''), a);
b.parentNode.insertBefore(a, b);
t.parentNode.insertBefore(b, t);
t.parentNode.removeChild(t);
return this;
};
and use it like this:
$('#div1').swap('#div2');
if you don't want to use jQuery you could easily adapt the function.
var swap = function () {
var divs = document.getElementsByTagName('div');
var div1 = divs[0];
var div2 = divs[1];
var div3 = divs[2];
div3.parentNode.insertBefore(div1, div3);
div1.parentNode.insertBefore(div3, div2);
};
This function may seem strange, but it heavily relies on standards in order to function properly. In fact, it may seem to function better than the jQuery version that tvanfosson posted which seems to do the swap only twice.
What standards peculiarities does it rely on?
insertBefore
Inserts the node newChild before the existing child node refChild. If
refChild is null, insert newChild at
the end of the list of children.
If newChild is a DocumentFragment object, all of its children are
inserted, in the same order, before
refChild. If the newChild is already
in the tree, it is first removed.
Jquery approach mentioned on the top will work.
You can also use JQuery and CSS .Say for e.g on Div one you have applied class1 and div2 you have applied class class2 (say for e.g each class of css provides specific position on the browser), now you can interchange the classes use jquery or javascript (that will change the position)
Sorry for bumping this thread
I stumbled over the "swap DOM-elements" problem and played around a bit
The result is a jQuery-native "solution" which seems to be really pretty (unfortunately i don't know whats happening at the jQuery internals when doing this)
The Code:
$('#element1').insertAfter($('#element2'));
The jQuery documentation says that insertAfter() moves the element and doesn't clone it

Categories

Resources