How Best to Build a Calendar with a Repeater?

Hey all,

I am trying to use a repeater to build a dynamic calendar widget that will display the correct number of days for the month and start on the right day of the week for an app.

I figured out the code to find the length of the month and which day the month starts, but I’m unclear how to update the repeater database and then how best to use the database to render the calendar.

My file is a bit of a mess due to trying things, but I just started to try to use an OnLoad interaction which reads the month start day value and places a “1” in the appropriate repeater database cell. And then maybe just adding one to that for each subsequent cell until it reaches the length of that month.

Axure Calendar Test.rp (89.5 KB)

My hope is that the final product will eventually be a widget in a widget library that has each month stacked on top of each other in a scrolling window, and users will be able to select a date range on the calendar like below. If I can get the database to work, I can do most of the rest (except maybe the green date range selection highlight) pretty easily.

My repeater database may be setup wrong and is definitely not optimized, but anyone know what is the best way generate a repeater database that could render a calendar output? I’m less concerned with optimization and more interested in learning what everything does.

Please let me know if anything is unclear or confusing in my prototype, and I’ll try to explain if I can… lol.

Thanks in advance!
Adam

Hi!

I’ve made a calendar with a repeater before. I first tried a model where I updated the repeater’s dataset to update it, but that turns out to be very slow. A nice thing about repeaters is the OnItemLoad interaction will run once per row in its dataset, but that doesn’t mean you need to display data from its dataset.

There is a useful repeater property called item.index, which just returns the number of the current row, starting at 1. So take the stock repeater (with just three items) and replace its OnItemLoad handler with this:

OnItemLoad
   set text on dateShape to [[Item.index - 1]]

This will make the repeater display the values 0, 1, 2 - even though the stored values are 1, 2, 3. Now change the dataset values to “cat,” “dog,” “bird.” The repeater will still display 0, 1, 2. Add “mouse” to the dataset. You now get 0, 1, 2, 3.

So for the calendar, here’s the strategy I used

Put just a single shape in the repeater row with no text in it. I’m calling it dateShape. This is the shape you will be putting the dates in. In the Style tab for the repeater, set Layout to horizontal and have it wrap on every 7th item. (You can also set the spacing between rows and columns here.)

Put 37 rows in your repeater - the max number of dates that can appear (including the blank ones at the beginning of the month. Heck, put a few more to be safe). They can have any value you want because we won’t be looking at those values - just make them all 0.

Also, in the repeater’s property tab, below the dataset, turn OFF “fit content to HTML”

Now all you have to do is figure out which day of the week that the first date of the month starts on (with Sunday being 0 and Saturday being 6) and subtract that number from Item.index, just as we subtracted 1 in the expression above. Then you make sure (using a condition) that you don’t bother setting the text of the repeater’s shape when that expression returns zero or less, or if it’s greater than the number of days in the month.

Here are all of the variables you will need to define. If you want this to be a widget, I’d recommend using the text of shapes instead of variables to store your values so things don’t break when you copy your widget to a file that doesn’t have those variables defined. You’ll need to know about local variables. Here’s a post on that. You might try it with global variables first since that’s easier, just to get things working. Note that you can display the values of global variables in the browser by choosing Axure’s console tab on the left side (where the page listing is).

The variables below assume you want to show September 2018

monthNum = 9
yearNum = 2018
firstWeekdayNum = 6 (which means Saturday - see below)
firstDate = 9/01/2018
lastDate = 09/30/2018

The only variables you will need to set the values of is monthNum and yearNum. You can calculate all of the other variables from that.

firstDate = [[ (Date.parse(monthNum + "/01/" + yearNum)+0).toUTCString() ]]
lastDate = [[ firstDate.addMonths(1).addDays(-1) ]] 
firstWeekdayNum = [[ firstDate.getDay() ]] -- conveniently returns 0 for Sunday, 6 for Saturday

Note: Date.parse(dateString) returns the date/time of the supplied string in milliseconds. We’re adding zero to that result to force .toUTCString() to interpret the result of Date.parse() as a number rather than a string.

So your strategy is the following:

  1. set the monthNum and yearNum as desired
  2. calculate the other variables
  3. force the repeater to redraw itself (see below)

Then in the repeater, your code would look like this:

OnItemLoad
   If [[Item.index - firstWeekdayNum]] is greater than 0 AND
      [[Item.index - firstWeekdayNum]] is less than or equals [[lastDate.getDate()]]
        set text of dateShape to [[Item.index - firstWeekdayNum]]
   Else If
        hide dateShape

Note: the getDate() function returns the day-of-the-month portion of a date, so 20 for September 20th.

To force the repeater to redraw itself, you can set its itemsPerPage to the number of days you will need to draw:

set ItemsPerPage of (repeater) to [[firstWeekdayNum + lastDate.getDate()]]

[ Edit ] Here’s an implementation of the above. (I figured I should make sure it works!)
calendarexample.rp (66.6 KB)

@AdamEngstrom,

This was a pretty fun challenge! Try this one out:
Axure Calendar Test 2.rp (212.6 KB)

I used a similar approach to @josephxbrick for designing the calendar repeater. Just one widget in the repeater for the date number. I started with 31 rows and put ascending numbers in the only repeater column, “DayNum”. To adjust the first day of the week, I add blank rows (actually a space char in DayNum) and sort the days so they get populated to top of repeater. Using your formula for determining the first day of the month, “Monday” equals “1” so we just need to add one row to the top; two for Tuesday and so on.

We also need to adjust the number of days for the given month (30 for September, 28 for February, etc.) and I do that by applying a filter to the repeater. To only show 30 days, I add a condition in the Add Filter for: [[Item.DayNum < '31']].

I started off testing if I could reliably adjust the calendar repeater for any month. See the Home v2 page. The repeater just sets the text on the daynum widget for each item, then in the OnLoad event, it sorts the rows. Seems stupid because they are already numerically ordered, but when a blank row is later added, it ensures it gets placed at the top. (This is also why I set the text to the DayNum value and don’t use the Item.index). When a month is selected in the droplist, it sets the header text and the value of OnLoadVariable to that month’s timecode string. Then it “auto-clicks” the Adjust Calendar button to fire its code, which will add the proper blank spaces to the front of the month and remove days as needed from the end of the month.

Then, on the Home v3 page I rolled all this up within the calendar group and used a hidden text widget instead of the OnLoadVariable, so it is all self-contained and doesn’t require any global variables. I added a “next month” calendar group, changing only the widget names and “Now” to “Now.addMonths(1)”. (Note, your formula in your original post for getting the next month doesn’t work.)

You could repeat this for all 12 months, or just keep these two as two states in a dynamic panel and adjust their months by 1 as a user scrolls through a calendar year. (If I find more time I’ll see if I can work that bit out.)

3 Likes

Almost forgot to pass this along, too. A recent thread that shows how to select date ranges in a repeater calendar. Should be able to add this to yours.

https://forum.axure.com/t/hover-state-remain-after-unhover/53188/4?u=mbc66