JQuery If Statement using each value from an array - javascript

I am writing a function that will be executed on multiple views of an application, and each view can have up to 50 instances of the same element: '.console'. I need to be able to perform an action every time the viewport scrolls to each instance. I have the following code setting up the variables:
//Create empty array with variable values, up to 50
var console = [];
//Find each instance of ".console" and populate the array with its pixel position.
$('.console').each(function() {
console.push($(this)[0].offsetTop);
});
//Determine the current pixel position of the scroll
var scroll = $(document).scrollTop();
Those variables all work fine and dandy, but after hours of pouring over jquery docs I can't figure the if statement out. Here is what I have that works well for the first item in the array:
if (scroll == console[0]){
$('.container').show();
} else {
$('.container').hide();
}
However, I want it to be anytime the scroll position matches each of the values in that array, hopefully something like this:
if (scroll == console[0-50])
Here is the full chunk as is:
$(document).on('scroll', function(){
//Create empty array with variable values, up to 50
var console = [];
//Find each instance of ".console" and populate the array with its pixel position.
$('.console').each(function() {
console.push($(this)[0].offsetTop);
});
//Determine the current pixel position of the scroll
var scroll = $(document).scrollTop();
//Anytime the scroll matches any of the instances of console, show a div
if (scroll == console[0]){
$('.container').show();
} else {
$('.container').hide();
}
});
Any help would be appreciated. I am pretty new to Javascript/JQuery so if I'm approaching the problem in the wrong way altogether, please let me know. Thanks!

Since you said it works for the first one, I'm guessing this may work.
// cache the container
var container = $('.container');
$(document).on('scroll', function(){
//Determine the current pixel position of the scroll
var scroll = $(document).scrollTop();
//Create empty array with variable values, up to 50
var console = [];
//Find each instance of ".console" and populate the array with its pixel position.
$('.console').each(function(index) {
console.push($(this)[0].offsetTop);
if (scroll == console[index]){
$(container).show();
} else {
$(container).hide();
}
});
});

You may wish to take a look at Waypoints. It's a jQuery plugin that is well suited for what you're trying to accomplish.
I whipped up a quick jsFiddle to show it in action: http://jsfiddle.net/dmillz/4xqMb/
$(".console").waypoint(function(direction) {
// Hide or show your ".container" object
});
More Waypoint examples: http://imakewebthings.com/jquery-waypoints/#get-started

Hopefully I understand your problem, which is as follows:
You have a bunch of elements with the .console class, and you want to appear as soon as they are in the viewport. When these elements aren't in the viewport you want them to dissapear?
Since you're interested in when these objects with the .console class are in the viewport, I suggest using this jQuery plugin
http://plugins.jquery.com/appear/
https://github.com/morr/jquery.appear
I suggest wrapping each of the .console objects in a container with another class, and then as these containers appear and disappear show and hide them.
At document ready just do the following:
$(document).ready(function() {
$('<.container-class>').appear();
$('<.container-class>').on('appear', function() { $(this).find('.console').show(); });
$('<.container-class>').on('disappear', function() { $(this).find('.console').hide(); });
});

To answer the question, you could do this:
var cons = $.map($('.console'), function(el) {
return $(el).offset().top;
});
$(document).on('scroll', function(){
var scroll = $(window).scrollTop();
$('.container').toggle( $.inArray(scroll, cons) != -1 );
});
But creating something for a range, considering the height of each element, the height of the window etc. would be a lot more involved.

While the problem was solved via another answer, figuring out how to perform a loop for each value in the array wasn't really solved ... UNTIL NOW!
This is probably a really gross and bloated way to do it, but if you essentially count how many items are in the array, you can then run a loop that many times, putting in the index for each value in the array. Code below:
//Create empty array with variable values
var console = [];
//Find each instance of ".console" and populate the array with its pixel position.
$('.console').each(function() {
console.push($(this)[0].offsetTop);
});
//Count the number of items in the array
var consoleIndex = console.length - 1;
$(document).on('scroll', function(){
//Determine the current pixel position of the scroll
var scroll = $(document).scrollTop();
//Anytime the scroll matches any of the instances of console, show a div
for (var i = 0; i <= consoleIndex; i++) {
if (scroll = console[i]) {
$('.container').toggle();
}
}
});

Related

Prevent overlapping while positioning element at height of another

Inside a long text document there are some "special words" to which I want to display notes/annotations on the left. Each note should be as close as possible to the level of the word it is refering to.
The HTML for this is organised in a table. Each paragraph is one table row, consisting on annotations in the left and main text in the right table column. the notes/annotations go to the left. However, unfortunately, there are also some other elements/text nodes in there.
<table>
<tr>
<td class"comments">
<span id="dog" class="note">Note for dog</span>
<span id="cat" class="note">Note for cat</span>
<span id="horse" class="note">Note for horse</span>
Somethin else than a note.
</td>
<td>[Text...]
<span id="dog_anchor" class="reference">Dog</span>
<span id="cat_anchor" class="reference">Cat</span>
<span id="horse_anchor" class="reference">Horse</span>
[Text...]
</td>
</tr>
</table>
It's easy to change the "note"-spans to absolute and positioned them on the level of their reference:
$('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
$(this).css('top', pos_of_ref); // set own position to position of reference element
});
However, life is not so simple here. Since there could be a lot of reference words in one line (while on other there are none of them) I need a rather sophisticated way to distribute the notes so that they are as close as possible to their references without destroying anything in the layout (e.g. being placed outside of the table cell or overlapping with other elements).
Furthermore, the height of the table cells could not be changed. Elements which are not notes must not be moved. (Note elements are always in the order they appear in the main text. That's not the problem.)
So, I need an algorithm like this:
Take all notes in a table cell.
Analyse blank space in that table cell: Which areas are blank, which are blocked?
Distribute the notes in the table cell so that each note is as close as possible to its reference word without any element colliding with any other item in the table cell.
Is there any fast and elegant way to do this without having to write hundreds of lines of code?
Here is a JSfiddle: https://jsfiddle.net/5vLsrLa7/7/
[Update on suggested solutions]
Simply setting the position of the side notes to relative or just moving notes down won't work, because in this case, the side notes will just go downwards relative to their desired position which results in side notes way to far from their reference words. After all, for a neat solution I need to side notes spread in both directions: up and down.
[Update]
The expected result would be something like this:
As you see, it's never possible to place all the notes at the height of their reference. However, the free space is used to position them as close as possible, moving them up and down.
I changed move() function as follows:
function move(){
var prev_offset = 0;
$('span.note').each(function (index, value){
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
if (prev_offset >= pos_of_ref){
pos_of_ref = prev_offset + 30;
}
$(this).css('top', pos_of_ref); // set own position to position of reference element
prev_offset = pos_of_ref;
});
}
I'm assuming that your element's notes will be in the correct order always
I made some changes to your javascript:
function move()
{
var arrayTops = [];
$('span[class*="note"]').each(function (index, value)
{
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
pos_of_ref = getCorrectTopPosition(arrayTops,pos_of_ref);
$(this).css('top', pos_of_ref); // set own position to position of reference element
arrayTops.push(pos_of_ref);
});
}
function getCorrectTopPosition(arrayTops, newOffsetTop)
{
var notesHeight = 18;
var marginBetweenNotes = 3;
var noteheightWithMargin = notesHeight + marginBetweenNotes;
var lastTop = arrayTops[arrayTops.length-1];
if((lastTop + noteheightWithMargin) >= newOffsetTop)
return lastTop + noteheightWithMargin;
return newOffsetTop;
}
Thanks for all the answers and comments. I was finally able to figure out at least a partical solution which works for me.
First of all, I was able to restructure my HTML, so that now the "non note" elements in the left td are all wrapped in one div which is now the very first element in the td. So, now there is nothing between notes, maybe something before them.
The idea of my solution is not to give the notes a new position but to set a new margin-top to each of them. The maximum amount of margin-top values to be added within a table cell is calculated before (called "roaming space"), being the space below the last note in a table cell. Thus, the table layout is not destroyed.
function move_notes() {
$('tr').each(function (index, value) {
var current_tr = $(this);
var last_app_element_in_tr = $(this).find('span[class*="note"]').last();
if ($(last_app_element_in_tr).length) /* Only preceed if there is at least one note in the table row */ {
var tr_height = $(this).height();
var tr_offset = $(this).offset().top;
var bottom_of_tr = tr_offset + tr_height;
var bottom_of_last_app_el = $(last_app_element_in_tr).offset().top + $(last_app_element_in_tr).height();
var roaming_space = bottom_of_tr - bottom_of_last_app_el; // Calculate the amount of pixels which are "free": The space below the very last note element
$(this).find('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = $(current_tr).find("#" + my_id + "_anchor");
var pos_of_ref = $(element_ref).offset().top;
var new_margin_top;
/* Calculate the new margin top: The note should be at the same level as the reference element.
When loading, in most cases the notes are placed too high. So, the margin top of the note should equal
the amount of pixels which the note is "too high". So we subtract the height and the offset of the element
before the current note from the offset of the reference. */
var previous_note = $(this).prev();
// not just notes, but every element in the td in general
if (! $(previous_note).length) // If there is no previous_note, than take the table cell
{
closest_td = $(this).closest("td");
new_margin_top = pos_of_ref - $(closest_td).offset().top;
} else {
new_margin_top = pos_of_ref - $(previous_note).offset().top - $(previous_note).height();
}
var difference_to_previous = $(this).css('marginTop').replace(/[^-\d\.]/g, '') - new_margin_top; // Calculate the difference between the old and the new margin top
if (new_margin_top > 0 && Math.abs(difference_to_previous) > 2) // Only move, if the new margin is greater than zero (no negative margins!) if the difference is greater than 2px (thus preventing ugly "micro moving".
{
var new_roaming_space = roaming_space - difference_to_previous;
if (new_roaming_space > 0) /* if there is still room to move */ {
var margin_top_ready = new_margin_top + "px";
$(this).css('margin-top', margin_top_ready);
roaming_space = new_roaming_space;
} else /* If there is no more space to move: */ {
var margin_top_ready = roaming_space + "px"; // take the rest of the "roaming space" left as margin top
$(this).css('margin-top', margin_top_ready);
return false; // Stop the execution because there is nothing left to do.
}
}
});
}
});
}
window.onload = function () {
move_notes();
};
$(window).resize(function () {
move_notes();
});
As you will notice, one of my main concerns is still not addressed: Notes are only moved down, never up. Because of various problems with my real world webpage I didn't implement that yet. However, an algorith could be something like: If the new margin top is greater than the height of the current note and the difference between the offet of the current note anchor and the following note anchor is less than the height of the current note, than subtract the height of the current note from the new margin.
Still, two problems remain:
If the window is maximized or quickly resized from a rather thin width to a greater width, the adjustment of the note positions won't work. I don't know why.
The performance could be better. As a user, you can see the notes jump down. (Because of strange and unpredictable behaviour in Firefox, I had to move the event handler from document.ready to window.onload)

How to determine the top element?

I have a complex structure of many nested, absolutely positioned elements.
These elements may or may not have their z-index set.
They also may or may not have the same parent element.
I am wondering what is the best / simplest way to return which element is on 'top'. Something like the following...
$(".panel").topMost()
Thanks (in advance) for your help
Do you mean the element with highest z-index:
$(document).ready(function() {
var array = [];
$("*").each(function() {
array.push($(this).css("z-index"));
});
var highest = Math.max.apply(Math, array);
console.log(highest);
});
A plugin is there ..topZindex
$.topZIndex("div");
Try this:
var z = [];
$('.panel').each(function(i, el) {
var $panel = $(el),
zindex = $panel.css('z-index');
z[zindex] = $panel;
});
var topMost = z.slice(-1);
See if you don't specify z-index to absolute elems then last of the element will be on top of other elems, means last element will have a greater highest default z-index calculated by browser itself.
Try this fiddle: http://jsfiddle.net/fLB2W/
var array = [];
$("*").each(function () {
if ($(this).css('position') == 'absolute') {
array.push($(this).css("position")+'<--pos & class -->'+$(this).attr('class'));
}
});
console.log(array[array.length-1]);
I faced similar issue with Modal dialog while displaying jQuery UI datepicker and using event parameters to figure out the clicked icon. Modal dialog overlay was preventing the new datepicker from showing on top of the modal dialog.
The best solution worked in three browsers (IE, Firefox, and Chrome) is:
function topZIndex(target, selector) {
var objs = $(target).parents(selector + '[z-index > 0]');
var a1 = -1;
$.each(objs, function (index, z1) {a1=(z1.style.zIndex > a1)? z1.style.zIndex : a1; });
return a1;
};
using event parameters as follows:
var zIndex = topZIndex(event.currentTarget, 'div')+1;
or
var zIndex = topZIndex(event.currentTarget, '*')+1;
Both combinations will generate same result, however it is more efficient to be specific by specifying 'div' instead of '*'
Then assuming my date picker id is datepickerpanel to set the new zIndex for datepicker
$('#datepickerpanel').css('z-index', zIndex);
This solution provides proper z-index value to place the new object on top of modal dialog.

making function with multiple variables to pass javascript/jquery

I have a little snippet I want to make into a function. But I'm new to javascript. Obviously there is something wrong with the way I pass variables or the way I call them...
So in nutshell, this works: http://jsfiddle.net/kkvbz/
But this doesn't: http://jsfiddle.net/PrtD4/
Problem is I need it as a function, so I've to make 1 version work.
Full snippet:
function cutandMakeslides (containerid,liperslide) {
//This is for footer slider, it rewrites 1 ul into several uls that contain 4 li max.
// get the container, useful for later too...
var container = $(containerid);
// get all available UL and LI elements...
var li_elements = container.find("> UL > LI").clone();
// remove the current content so that we can rebuild it for the slider...
container.find("> UL").remove();
// build the slider container...
var slide_container = $("<div />");
// tricky part: looping through the LI's and building each of the slides...
// first create some helpful variables...
var li_elements_per_slide = liperslide;
var li_counter = 0;
// create the first slide, with a UL to hold the LI's...
var current_li_div = $("<div />");
current_li_div.append($("<ul />"));
// loop through the LI's...
li_elements.each(function(index, element){
li_counter++;
var current_li = $(element).clone();
current_li_div.find("> UL").append(current_li);
if (li_counter % li_elements_per_slide == 0)
{
// we've hit 4 in this list, so add the slide and make
// a new one, using same code as before...
container.append(current_li_div);
current_li_div = $("<div />");
current_li_div.append($("<ul />"));
}
});
// we might have an uneven number of LI's, so we need to check for this...
if (li_counter % li_elements_per_slide != 0)
container.append(current_li_div);
} // end function cutandMakeslides
//activate function above
$(function() { cutandMakeslides(".fproductslides",3); });
Problematic parts:
function cutandMakeslides (containerid,liperslide) {
var container = $(containerid);
var li_elements_per_slide = liperslide;
}
$(function() { cutandMakeslides(".fproductslides",3); });
So after extensive testing and moving the code to a fiddle, the problem seems to have resolved itself during moving it to the fiddle, so we believe there was a minor spelling or syntax error...however using a code comparison tool I was unable to find anything...
none of the problematic parts seemed to have an error:
function cutandMakeslides (containerid,liperslide) {
var container = $(containerid);
var li_elements_per_slide = liperslide;
}
$(function() { cutandMakeslides(".fproductslides",3); });
Here's a copy of the working fiddle
From your 'problematic parts' section it appears you are trying to pass a class(".fproductslides" is a class, classes start with '.' and Id's start with '#') instead of an Id as your variable names lead me to believe you want to do...

Get all HTML elements scrolled into view

I've got a scrollable table in HTML that updates frequently (about once per second) and can contain upwards of 1000 rows. Obviously, it's not reasonable to replace the entire table every time it updates, so I'd like to just replace the table rows that are currently visible.
My first attempt was to just check iterate over all the rows and check their offsets; this works, but it's far too slow to be effective.
What I'm trying to do now is use document.elementFromPoint() to find the topmost element overtop the <tbody>, which is usually a <td> element from where I can get its containing <tr>. This almost works, except in the case where the table itself is obscured by another element (a floating lightbox, for example).
I'm currently looking for either a third solution, or a way to get all elements under a specific point, not just the topmost one. If anyone has any idea how to accomplish either of those, that would be much appreciated.
So I was thinking about this one and came up with this.
http://jsfiddle.net/RKzRE/7/
Instead of monitoring all 1000 rows just monitor a subset of them. Cache their scrolltops on page load for efficiency. So what if you update a few rows above or below the display area? You can find a happy medium between granularity and speed of updates.
I only implemented scrolling down and hardcoded the number of rows to update. But I think the details will be easy to figure out.
//create an object to hold a list of row top locations
var rowmarkers = new Object;
//gather all rows and store their top location
$('tr').each(function(index) {
//create markers for every ten rows
if (index % 10 == 0) {
$(this).addClass('marker');
rowmarkers[$(this).prop('id')] = $(this).offset().top;
}
});
//track whether user scrolls up or down
var prevScrollTop = $(document).scrollTop();
//monitor scroll event
$(document).scroll(function() {
var currentScrollTop = $(this).scrollTop();
if (currentScrollTop > prevScrollTop) {
downScroll(currentScrollTop);
} else {
//up
}
prevScrollTop = currentScrollTop;
});
function downScroll(scrollTop) {
//find the first row that is visible on screen
for (var row in rowmarkers) {
if (rowmarkers[row] > scrollTop) {
//all rows after this can be updated
var updaterow = $('#' + row).prevAll('.marker:first');
if (!updaterow.length) { updaterow = $('.marker:first'); }
//hardcoded # of rows to update
for (var i = 0; i < 20; i++) {
console.log($(updaterow).prop('id'));
updaterow = updaterow.next('tr');
updaterow.not('.marker').addClass('updaterow');
}
return;
}
}
}

JQuery .each() callback function doesn't run on each iteration/loop

Here's what should happen.
1. Get the rel attribute of the clicked link
2. For every div with class 'entry':
(i)Get its 'left' position
(ii) Calculate its outer height
(iii)Loop through all instances of 'a.tag_filter'. If it finds the same string in the 'rel' as the one oringinally clicked on then add 1 to 'V' and break out of the loop.
(iv)If 'V' is equal to 0 after the loop we know the same tag isn't present within that '.entry' so fade it out.
(v)Once the fadeout has finished loop through all the '.entry' after the faded out one and get their 'left' values.
(vi)If the left value of the faded entry = the left value of the current '.entry' then reposition it to the new 'top' value.
What is currently happening.
It runs through and fades out all the correct '.entry' elements and only after all of them have faded out does it reposition them remaining '.entry' elements.
After each element is faded out I would like the repositioning loop to run so it essentially positions the remaining elements one at a time rather than all at once.
Heres my code EDIT:
$('a.tag_filter').click(function(e){
e.preventDefault();
var selectTag = $(this).attr('rel');
$('div.spotlight_entry_container_grid').each(function(i){
var $entry = $(this);
var tagArray = [];
$('a.tag_filter', this).each(function(){
tagArray.push ($(this).attr('rel'));
});
if($.inArray(selectTag,tagArray) == -1){
var leftPos = $entry.css("left");
var topPos = $entry.css("top");
$entry.fadeOut(1000, function(){
var nextLeftPos;
var nextTopPos;
$('div.spotlight_entry_container_grid:gt('+i+')').each(function(j) {
var $laterEntry = $(this);
nextLeftPos = $laterEntry.css("left");
nextTopPos = $laterEntry.css("top");
//we need to keep the entries in their columns.
//matching left values will do it. No need to animate left values.
if(leftPos == nextLeftPos){
$laterEntry.animate({ top: topPos});
}
});
});
}
});
});
Hopefully that makes sense
Any help would be appreciated!
I'm probably doing something crazy but I just can't spot it.
Thanks
in here
$('a.tag_filter', this).each(function(){
var curTag = $(this).attr('rel');
if(curTag == selectTag){
v++;
return false;
}
});
returning false inside of $().each() breaks the looping through each element in the wrapped set.
From the documentation
Returning 'false' from within the each
function completely stops the loop
through all of the elements (this is
like using a 'break' with a normal
loop). Returning 'true' from within
the loop skips to the next iteration
(this is like using a 'continue' with
a normal loop).
Also, I would recommend caching $(this) inside of each each() in a local variable for performance instead of referencing it several times.
EDIT:
After looking at the code further, I think the following should do it
$('a.tag_filter').click(function(e){
// prevent the default anchor behaviour
e.preventDefault();
var selectTag = $(this).attr('rel');
$('div.entry').each(function(i){
var $entry = $(this);
// get an array of the anchor tag rel attributes
var tagArray = [];
$('a.tag_filter', this).each(function() {
tagArray.push ($(this).attr('rel'));
});
// if we can't find the selected tag in the entries tags
if ($.inArray(selectTag,tagArray) == -1) {
var leftPos = $entry.css("left");
var topPos = $entry.css("top");
$entry.fadeOut(1000, function(){
var nextLeftPos;
var nextTopPos;
$('div.entry:gt('+i+')').each(function(j) {
var $laterEntry = $(this);
nextLeftPos = $laterEntry.css("left");
nextTopPos = $laterEntry.css("top");
// for the first element, set top and left to the faded out element values
if (j == 0) {
$laterEntry.animate({ top: topPos, left: leftPos });
}
// for later elements in the loop, ste the values to the previous element values
else {
$laterEntry.animate({ top: nextTopPos, left: nextLeftPos });
}
});
});
}
});
});
You don't need to cache $(this), jQuery auto caches the this selector for function callbacks.

Categories

Resources