Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Daveyon Mayne 😻
Daveyon Mayne 😻

Posted on

     

Infinite Y-Axis Calendar View Scroll

A snapshop of the calendar view from ektarOS (FarmSwop) journal app

I admired theScrollView on iOS. When done right, you get a smooth infinite scrolling of the y-axis while it handles loading of data onto the view. A similar design was done for the DayOne iOS app; it's an infinite scroll back in the past as it's a journaling app so that make sense. I'm building a similar app but for farmers for my app calledektarOS (FarmSwop).

# index.html.erb: ie farmswop.com/calendar<divclass="journal-calendar-container"><headerclass="journal-calendar-header bg-earth-50 z-50 dark:bg-gray-850 dark:text-bubble-800 grid grid-cols-7 p-1"><spanclass="journal-calendar-header__item">mon</span><spanclass="journal-calendar-header__item">tue</span><spanclass="journal-calendar-header__item">wed</span><spanclass="journal-calendar-header__item">thu</span><spanclass="journal-calendar-header__item">fri</span><spanclass="journal-calendar-header__item">sat</span><spanclass="journal-calendar-header__item">sun</span></header><divid="journal-container"class="h-screen overflow-y-auto"><%=render"journals/calendar/scrollable",direction:"past"%><%=render"journals/calendar/scrollable",direction:"future"%></div></div>
Enter fullscreen modeExit fullscreen mode

As you can see, I'm renderingjournals/calendar/scrollable twice; the top one that fetches past data while the other fetches present and future data. Let's take a look at the partial:

views/journals/calendar/scrollable:

<%=turbo_frame_tag"#{direction}_journal_timeline",src:journal_timeline_path(direction:direction,month_year:local_assigns[:month_year]||nil),loading: :lazy,target:"_top"do%><p>Loading...</p><%end%>
Enter fullscreen modeExit fullscreen mode

On page load, we make two requests; one for past data and the other for present/future data.journal_timeline_path renders a repeat ofviews/journals/calendar/scrollable in an odd way:

<%=turbo_frame_tag:"#{@direction}_journal_timeline"do%><divclass="flex flex-col flex-grow"><%if@direction=="past"%><%=render"journals/calendar/scrollable",month_year:@month_year,direction:@direction%><%=render"shared/journal_calendar",direction:@direction,calendar_data:@calendar_data%><%else%><%=render"shared/journal_calendar",direction:@direction,calendar_data:@calendar_data%><%=render"journals/calendar/scrollable",month_year:@month_year,direction:@direction%><%end%></div><%end%>
Enter fullscreen modeExit fullscreen mode

For past data, I try to keepjournals/calendar/scrollable from not being in view until the user scrolls up. It's not perfect as yet, we have to use JavaScript for this section. I'll come to that later.

shared/journal_calendar:

<%=turbo_frame_tag:"#{direction}_journal_timeline"do%><articleclass="bg-earth-100 dark:bg-gray-850 invisible"data-controller="journal"data-direction="<%=direction%>"><divclass="bg-earth-50 dark:bg-gray-840 dark:text-bubble-900 px-4 py-1 text-sm font-stdmedium"><%=@date.strftime('%B %Y')%></div><divclass="flex items-center justify-between overflow-x-auto"><tableclass="w-full"><thead></thead><tbody><%calendar_data.eachdo|week|%><tr><%week.eachdo|day|%><tdclass="journal__cell<%=day.present??'journal__cell--on':'journal__cell--off'%>"><!-- Your rendered cell --><%# render "shared/journal_cal_cell", day: %></td><%end%></tr><%end%></tbody></table></div></article><%end%>
Enter fullscreen modeExit fullscreen mode

The Calendar Controller

Calendar days are server-side rendered. No JavaScript is used to render the dates nor its data.

classJournals::CalendarController<ApplicationControllerdefindex;endend
Enter fullscreen modeExit fullscreen mode

And that's it! Literally! Joking!Journals::CalendarController is only needed to render theindex page. If you look back atviews/journals/calendar/scrollable, it fetchesjournal_timeline_path. Here's that controller:

classJournals::TimelineController<ApplicationControllerdefindex@direction=params[:direction]@month_year=params[:month_year].present??Date.parse(params[:month_year]):Date.todaycase@directionwhen"past"@month_year-=1.monthend@date=@month_year.beginning_of_month@calendar_data=Journal::Timeline.new(start_date:@date,bucket:@bucket).calendar_datacase@directionwhen"past"@month_year-=1.monthelse@month_year+=1.monthendendend
Enter fullscreen modeExit fullscreen mode

You may see references to "buckets", they are not relevant to this post. Most of this code I copied directly from my code editor.

classJournal::Timelinedefinitialize(start_date:nil,bucket:nil)@start_date=start_date@bucket=bucketenddefcalendar_datagenerate_calendar_dataendprivatedefgenerate_calendar_datacalendar_data=[]current_date=@start_dateweek=[]# Determine the number of empty cells before the first day of the monthempty_cells=(current_date.wday-1)%7empty_cells=6ifempty_cells<0# Calculate the date of the first day of the current monthfirst_day_of_current_month=current_date.beginning_of_month# Add days from the previous month, if neededempty_cells.downto(1)do|i|date=first_day_of_current_month.prev_day(i)day_data=initialize_day_data(date)week<<{}# day_dataend# Generate calendar data for the entire monthwhilecurrent_date.month==@start_date.monthday_data=initialize_day_data(current_date)week<<day_dataifcurrent_date.sunday?calendar_data<<weekweek=[]endcurrent_date=current_date.tomorrowend# Add the last week to the calendar datacalendar_data<<weekunlessweek.empty?calendar_dataenddefinitialize_day_data(date)# Get the data you need by datejournals=Recording.selectdo|rec|observed_at_date=rec.recordable.observed_at.to_dateifrec.recordable.observed_at.present?created_at_date=rec.recordable.created_at.to_dateobserved_at_date==date.to_date||created_at_date==date.to_dateend# Your data format{day:date.day,date:date.strftime("%d-%m-%Y"),journals:journals# Add any other data you need for the day, such as events}endend
Enter fullscreen modeExit fullscreen mode

generate_calendar_data took some time for me to get right. I hope it was well written?

The Stimulus JavaScripts!

Every timeshared/journal_calendar gets rendered, we execute some other code:

app/javascript/controllers/journal_controller.js:

import{Controller}from"@hotwired/stimulus"exportdefaultclassextendsController{statictargets=['cell']connect(){constcontainer=document.getElementById('journal-container');constscrollTopBefore=container.scrollTop;constdirection=this.element.dataset.direction;constnewItemsHeight=this.element.clientHeight;// clientHeight/offsetHeightif(direction==='past'){// Adjust the scroll positioncontainer.scrollTop=scrollTopBefore+newItemsHeight;this.element.classList.remove('invisible')}else{// Not sure I need this anymore.this.element.classList.remove('invisible')}}}
Enter fullscreen modeExit fullscreen mode

There's a bug with this JS. While the "past" data scrolls nicely on desktop, not so much on mobile devices. I suspect the issue with getting the height withclientHeight. Any ways, I'll look on that later.

That's all there is for an infinite y-axis scroll. Forinitialize_day_data > journals, you can put an empty array if you do not have any data and the infinite scrolling should work, but without any data.

Spot any bug(s)? Please let me know!

Have a great day! 👋

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Accounting with passion. Loves writing code with Ruby, PHP and JavaScript. Perfect bathroom singer. Prev. Sys admin @ncbja. I'm also SylarRuby
  • Location
    Birmingham, UK
  • Work
    Software Engineer
  • Joined

More fromDaveyon Mayne 😻

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp