0

I am working on a calender. Calender as divs in a scrollable outer div

The structure is simple: The outer div #calender contains all the date-fields with class .field and ID as #DD-MM-YYYY. Now, I want the month name on top ("January") to February when the user scrolls past the #01-02-2015 DIV and so on... So the month name is dynamic.

Question: HOW do I detect which div is scrolled to?

Praxis Ashelin
  • 5,137
  • 2
  • 20
  • 46
Caspertl
  • 21
  • 9

5 Answers5

1

You can't really know which div is scrolled to. You can compare the original location of a div (relative to document), and see if the scrolled window has reached that location.

window.pageYOffset will give you the number of pixels the document has already scrolled to, whenever it is requested. Now what you'll need is to attach an event listener to the scroll event, give it the relative-to-document-top location of #01-02-2015 div, and checks to see if window.pageYOffset is greater than that. If it is, you can replace the month name. If it's less, you can replace it back.

To know the #01-02-2015 div's offset relative to document, you can jquery's offset() function, or try out the following and let me know if it works:

function getOffsetTop(element){
    var offsetTop = 0;
    do {

      if ( !isNaN( element.offsetTop ) )
          offsetTop += element.offsetTop;
    } while( element = element.offsetParent );

    return offsetTop;
}

(Adapted from finding element's position relative to the document )

** EDIT **

So, as you say the getOffsetTop function works. At page load, you'll want to get the locations/offsets of all the .first div's, and let's assume you also gave them a class or data-id with the specific month. So, let's start by creating an array of these values, like:

var firsts = document.querySelectorAll('.first');
var array = []
for(var i=0;i<firsts.length;i++)
    array.push({month:firsts[i].getAttribute('data-id'), offset:getOffsetTop(firsts[i])});

Now you have an array that looks like [{month:'january',offset:214},{month:'february',offset:462}...].

Again, without looking at code, I'm thinking you will need to have a global (or anyway declared outside the scroll function) variable, declared at load time, that stores the index of the current month being looked at (assuming window.pageYOffset is zero then, you can start with array[0]). The scroll function will need to keep checking against the array item before and after this month to see if either of those scroll points are being reached, and if so, change the div content as you need, and also update the currentMonth variable. Something like:

var currentMonth = 0;
window.onscroll = function(){
    var index = currentMonth;
    if(array[index-1] && window.pageYOffset < array[index-1].offset)){
         // change the specific div's innerHTML to that of previous one. You can also target a particular `first` by using document.querySelector('.first[data-id="'+array[index].month+"]'):
         currentMonth = currentMonth - 1;
    }

    else if(array[index+1] && window.pageYOffset > array[index+1].offset)){
         // change the div to next one
         currentMonth = currentMonth + 1;
    }
}

I haven't tested any of this but let me know how you make out, or if the console throws errors.

** EDIT3 **: MDN reference (link) recommends using window.pageYOffset instead of window.scrollY for cross-browser compatibility, so I've updated my answer.

Community
  • 1
  • 1
Sidd
  • 1,389
  • 7
  • 17
  • I used your getOffsetTop() method like this: https://dl.dropboxusercontent.com/u/7647549/monthmay.PNG and it works (alerts "month: may" when i scroll past 01-05-2015). BUT, this means that i must hardcode all the IF-statements for every month (eg. 01-05-2015, 01-06-2015, 01-07-2015.... 01-01-2016...)? – Caspertl Apr 20 '15 at 20:09
  • It's hard to say without looking at your HTML. If you give all those div's a class (eg _first_), then you can do it simply using _document.querySelectorAll('.first'). – Sidd Apr 20 '15 at 20:23
  • I could give first days in month a _first_ class. And the javascript could detect when scrolled past a _first_ class. But how do i then know which month the _first_ class'ed div represent? – Caspertl Apr 20 '15 at 20:26
  • You can get the _first_ that is currently scrolled to, find its parent container, find the heading tag (or whatever you are using to store the month name), and get its innerHTML / innerText, and change it to the next one, based on an array of months, or on parent container's _nextSibling_'s heading tag innerHTML. – Sidd Apr 20 '15 at 20:31
  • Interesting! You are telling me, that i can get the _first_ that is being scrolled to. How? If i could get that element, i could also give the _first_ a class that represents it's month, like: _
    _ and then just get the relevant _first_'s className and use a substring to get the month-name?! :)
    – Caspertl Apr 20 '15 at 20:39
  • Thank you for your detailed answer, it looks very promising! i'll spend some testing it and i'll let you know if it works :) – Caspertl Apr 20 '15 at 21:16
  • So i've used your code to create the array. However, when i try to print out the content of the array with a for-loop, i get: _undefined...x12_. I gave the _.first_ a data-id like "01" or "02" for february and so on. – Caspertl Apr 21 '15 at 15:58
  • You really have to share some HTML code at this point (ideally via jsfiddle), at the very least a tree structure. I really can't know what's happening without that. – Sidd Apr 21 '15 at 16:08
  • This is the neccesary html. The structure should be simple (i know there is divs for 2 years) – Caspertl Apr 21 '15 at 16:17
  • You have given the _data-id_ to a _span_ element inside the element with _first_ class. Won't work with my code. Give the _data-id_ attribute to the same element. Otherwise it is correctly working. Your array printing code might be wrong. – Sidd Apr 21 '15 at 16:28
  • Hm.. I've updated the same jsfiddle with the data-id on .field and added the javascript and a test-field to output. I don't see any results.. – Caspertl Apr 21 '15 at 16:41
0

You could use .offsetTop to check the current y position of each cell, calculate which row is currently shown on top, and then check the current month through its id.

for (var i=0; i < rows.length; i++) {
    // Select the last day, incase the first few days are still from the previous month
    var lastcell = rows[i].querySelector(".field:last-of-type");

    // Check if offsetTop is smaller than 100 (leave some error margin)
    if (lastcell.offsetTop < 100) {
        var id = lastcell.attribute("id").split("-");
        var month = id[1];
        // Update month
    }
}
Praxis Ashelin
  • 5,137
  • 2
  • 20
  • 46
  • This seems like a really nice solution, but: my fields are divs and not rows in a table. – Caspertl Apr 20 '15 at 19:44
  • @CasperTraberg-Larsen That doesn't change anything to the code. `rows` is just a variable name. It can contain divs too. – Praxis Ashelin Apr 21 '15 at 11:38
  • Could you explain your code then? How is `rows` defined? `rows.length` is what? And `querySelector(".Field:last-of-of-type")` does what? – Caspertl Apr 21 '15 at 15:18
  • @CasperTraberg-Larsen `rows` can be anything you want. Since I didn't know how your HTML looks, I left it open for interpretation. For example you could have `var rows = document.querySelector("#calendar .row");` before my code (assuming your fields are nested in a `
    `). You can read some documentation on [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) and [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type).
    – Praxis Ashelin Apr 21 '15 at 17:00
0

I would calculate it on base of .field height and current scroll of #calendar. Current scroll of div you can get with the following code:

var position = document.getElementById('calendar').scrollTop;
Praxis Ashelin
  • 5,137
  • 2
  • 20
  • 46
Tomasz Białecki
  • 1,041
  • 6
  • 10
  • I like the simplicity! But that would only work if the calendar started at 1st January. The calendar is dynamic and starts with the current date. (20. in the picture) – Caspertl Apr 20 '15 at 19:45
0

Add hidden divs as separators between each month (use css for this). Maybe each month should start on new (css) line. Then you can detect which month you are into by getting the scroll offset of the calendar and see between which separators is in. Assuming you know the offset of each separator or you can loop through with jQuery and the get the one that is closer.

UPDATE:

Between the dates of each month add a hidden separator div e.g <span class="month-separator"></span>. Then these separators by definition will have different offsets (since the dates of a whole month are between each one of them). Then when user scrolls, compute the current offset loop through each separator and see if the offset is close to one of them:

pseudo-code:

var calendarScroll = calendar.pageYOffset; // compute this
var whichMonth = 1; // start with january, i.e 1st month
// for each month separator
$('.month-separator').each(function(index){
   // break when encountering the month that is not yet scrolled to
   if ($(this).offset().top > calendarScroll) 
   {
      whichMonth = index;
      return false; // break from each loop
   }
});

You will probably want to put the code above inside the scroll handler like the window.onscroll event handler (e.g see example with sticky header on scroll here).

Nikos M.
  • 8,033
  • 4
  • 36
  • 43
  • I get the logic! however, i am not sure how to actually implement this :/ – Caspertl Apr 20 '15 at 19:47
  • @CasperTraberg-Larsen, updated answer with pseudo-code – Nikos M. Apr 20 '15 at 19:53
  • Your code results in no javascript working at all = some sort of syntax error. I computed the calendarScroll correctly, and everything else is copied from your code above – Caspertl Apr 21 '15 at 16:03
  • @CasperTraberg-Larsen, it is an illustrative pseudo-code, can you post what error you get (it misses some semicolns etc so it might be that) – Nikos M. Apr 21 '15 at 18:47
  • @CasperTraberg-Larsen, also the code, assumes jQuery($) is used – Nikos M. Apr 21 '15 at 18:48
0

Assuming you already have your code in place that highlights the next date on scroll, you probably have a handle on the next date that will be highlighted. A simple approach would be on scroll, get the value of the highlighted div (should be the date). If the value === 1, it's the first of a new month so display the next month.

To get the next month, you could store an array of all months and then iterate over the array. If the current iteration is equal to the current month on the calendar, return the next element of the array

function getNextMonth(current_month) {
    var month_array = [
        'January',
        'February',
        'March',
        'etc'
    ];
    $.each(month_array, function(month, foo) {
        if(month == current_month) {
            return month_array[($.inArray(current_month, month_array) + 1) % month_array.length];
        }
    });
}

var calendar = $('calendar');

calendar.hover(function() {
    calendar.scroll(function() {
        //Your logic to change highlighted div
        var current_date = $('#datediv').val(),//However you are selecting your current date div
            month = $('#month_div');
        if(current_date === 1) {
            var next_month = getNextMonth(month);
            month.val(next_month);
        }
    });
});

Honestly though, from a user standpoint it would be odd to scroll through dates on a calendar like that. With most calendars I've seen, scrolling will change the month. That may be a better design method for you unless you have to do it the way you're doing it now.

Walker Boh
  • 750
  • 6
  • 13