Jump to content

Request for Input: What features should a PW calendar module have?


bernhard
 Share

Recommended Posts

46 minutes ago, elabx said:

And as some feedback on the UI I have thrown this similar set of fields to the common folk editor and they just figure their way out, so I think you're on your way to success! 

What do you mean, I don't understand?

Link to comment
Share on other sites

1 hour ago, bernhard said:

What do you mean, I don't understand?

Nothing in particular just that it all looks really good! Made this comment thinking on how it's very complicated to setup the recurring rules, but I like the solution!

  • Like 1
Link to comment
Share on other sites

2 hours ago, elabx said:

The drawback I have faced is performance in queries of this absurdly large tables

Just load everything into Redis and hope the server never restarts. Blazing fast performance fueled by hopes and prayers  🙏 🔥 😭

  • Haha 1
Link to comment
Share on other sites

First milestone reached!!! 😍😍

Yesterday I spent the whole day with refactoring the JS of the rrule gui and I added some more options to cover hopefully 99.9 of all necessary scenarios. The gui now has a toggle to switch between simple and advanced mode to keep things as clean as possible and not overwhelm the users for simple rrules like "every week for 4 times".

At the moment 80 hours have gone into this module (excluding the daterange input, which was already done some time ago). 💪🤯

Here my answers to the open questions:

On 9/4/2024 at 6:39 PM, elabx said:

@bernhard I  think I'd request the "Never" option, since there are situations where end users don't really think about when something is going to end at least that's why I ended up implementing a "Never" fake option. I haven't really solved it in my module in a way that would seem optimal, "Never" just means an absurd amount of events (eg 5,000 hard limit). The drawback I have faced is performance in queries of this absurdly large tables (At least they always look absurd taking into account that's it's just a few pages of the site sometimes).

I don't think there is an ideal solution to this problem. I even found a bug in google calendar when comparing and playing around!!! I decided against a fake "never" option, as it does something different than what the UI shows. So in my case if the user does not enter an end date or a max number of events it get's limited to 100. This limit is configurable in the module's settings, though 😎

I've also added the dates of the first and last event beside the resulting rrule to make it clear what is going to happen.

iqLF2Rt.png

Additionally the user will get a table of all events that will be created. Sometimes - but not always! rrules are tricky 😄 - the first occurrence is the main event itself. In that case the table shows a hopefully clear indicator:

1CFyvGy.png

On 9/3/2024 at 11:35 PM, monollonom said:

I think I missed something: is the "Recurring" option creating copies of the current page (event)?

On 9/3/2024 at 11:43 PM, BrendonKoz said:

It looks like you've each chosen the differing options. 😉

Yes and no, I'm using a mix to somehow get the best of both worlds. I'm creating real pages for the events, but I'm only populating the date field. All other fields (like shown with the title in the video) will be inherited from the main event at runtime. This makes it possible to have complex event descriptions (like one built with RockPageBuilder) with images etc. and when creating 1000 recurring events it will still only consume the space of one event + 1000 date inputs (but not 1000 copies of images).

On 9/3/2024 at 11:35 PM, monollonom said:

Another question then: once you created the events, what happens if you save the page? Are the copies displayed in your table or does it start from scratch?

I hope the video answers that? 🙂 

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

11 hours ago, bernhard said:

Yes and no, I'm using a mix to somehow get the best of both worlds. I'm creating real pages for the events, but I'm only populating the date field. All other fields (like shown with the title in the video) will be inherited from the main event at runtime. This makes it possible to have complex event descriptions (like one built with RockPageBuilder) with images etc. and when creating 1000 recurring events it will still only consume the space of one event + 1000 date inputs (but not 1000 copies of images).

It’s an interesting mix indeed. Would you say these copies act as aliases but with their own date? Does it require a special template or is it that your module dynamically add a page reference field to the copies and then your module add hooks to get the main event’s data at runtime?

What is very nice is it makes it super easy for the editor to edit a specific copy. Among many other benefits 🙂

The only downside I can think of in your setup is what if a recurring event gets cancelled? Is there a way to easily delete the copies? If you have a page reference field it shouldn’t be too hard actually...

11 hours ago, bernhard said:

I hope the video answers that? 🙂 

Maybe? If I rephrase what’s happening: you create the event, set its date to "recurring" and go ahead and create your copies. Then you save the page. If you edit it again, you have the "recurring" date type selected with the options to create new ones, but no indications of the copies previously created. Is this correct?

  • Like 1
Link to comment
Share on other sites

22 hours ago, bernhard said:

Yes and no, I'm using a mix to somehow get the best of both worlds. I'm creating real pages for the events, but I'm only populating the date field. All other fields (like shown with the title in the video) will be inherited from the main event at runtime. This makes it possible to have complex event descriptions (like one built with RockPageBuilder) with images etc. and when creating 1000 recurring events it will still only consume the space of one event + 1000 date inputs (but not 1000 copies of images).

I love the planned efficiency of that. I was actually thinking that, using meta(), there might be a way (or a few ways) to mix the two, but I hadn't fully thought it through.

2 hours ago, monollonom said:

It’s an interesting mix indeed. Would you say these copies act as aliases but with their own date? Does it require a special template or is it that your module dynamically add a page reference field to the copies and then your module add hooks to get the main event’s data at runtime?

What is very nice is it makes it super easy for the editor to edit a specific copy. Among many other benefits 🙂

I was actually wondering about that after watching the video, and after bernhard's explanation of what is actually stored. So currently it sounds like there is a single page that stores all of the various data, aside from the date, and/or datetime values for recurrence. However, what does happen if there is a description exception within the series?

Example:

  • A client wants to hold a recurring meeting on the 2nd and 4th Thursday of the month. It's a book club. They know they'll be having this meeting for the full year, with the exception of major holidays. They want to include the name of the book that will be discussed for each meeting - so nearly everything will be exactly the same, with the exception of a description field (or a separate "book" field?).

If the base solution is to only duplicate the date fields, if there is a desire to edit individualized adjustments per event within a recurrence, can those pages created for recurrence attach additional field data that would override? And if yes, how would one then revert changes by overriding a description later (ex: update descriptions for all events from this date forward)?

It is completely reasonable to simply say, "This won't support that [yet?]." 😄 A lot of things should be extended by the developer taking advantage of the module, I'm simply curious how that might be achieved based on the architecture of the module.

P.S. - Although the live-loading data table is really cool, without hooks, is it possible to (optionally?) hide that from the user until/unless they'd need to interact with it? I'm just thinking that from a UX perspective, it's pretty (and super cool) to look at but can take up a large portion of screen real estate if not actually needed.

P.P.S. - Will this be a standalone Pro Rock module, or will it require RockFrontend as well?

Link to comment
Share on other sites

On 9/6/2024 at 8:57 AM, erikvanberkum said:

@bernhardfantastic this module. Looks like you are almost ready to close the cal.com app on your site and replace it.

Thx @erikvanberkum! Don't see the need to replace cal.com as it is free and works very well and has lots of options that I'd have to rebuild.

On 9/6/2024 at 8:57 AM, erikvanberkum said:

How about times zones, couldn't see that in the application.

RockCalendar will not handle timezones on its own. It will always store UTC dates in the database. If you need to support timezones than you can do so, but you have to implement that on your own. That should not be a big deal, as the part on how you present all dates to public users will be up to you anyhow.

So in my case I manage events for a cave. They have a schedule with several dates for guided tours. For example on Saturdays at 14:00 from May to end of October.

When testing this use case I realized that after passing the daylight saving change date all following events where @ 15:00 instead of 14:00 (or 13:00 instead of 14:00 I can't remember). To prevent this I let fullcalendar think that all entered dates are actually UTC dates. UTC does not have daylight saving so whenever anyone enters 14:00 it get's displayed as 14:00;

That's it and that's how fullcalendar handles timezones itself, as far as I understood: https://fullcalendar.io/docs/timeZone

When presenting those dates to users I will ignore that fact that dates are technically UTC dates and just present them as they are. That means that when anybody is browsing through the calendar he/she will see all tours starting @ 14:00 and he/she will expect that to be a local time, which is the case even though the date is technically stored as UTC.

If I did that differently the user would see 13:00 as start date for events until October and 14:00 for events after 1st of October (or something like that, don't know when the actual change happens as my smartphone will do the switch for me 😄 ).

This works for all websites presenting dates to local customers in one single timezone.

2 things to mention here:

  • If anybody from another timezone was going to edit an event in the backend, he/she would still see 14:00 as time, which might be totally off his/her local time but will be correct for the local timezone of the location the event takes place.
  • If the website presented events to users from different timezones the conversions would have to be done by the developer of the frontend. I think this should be quite easy though. Maybe we'd need to add a setting to the module in which timezone all dates are stored so that the frontend knows how to convert them to local times. Maybe it would need some modifications to the module. We'll see. If anybody knows that already please let me know.
On 9/6/2024 at 7:42 PM, monollonom said:

It’s an interesting mix indeed. Would you say these copies act as aliases but with their own date? Does it require a special template or is it that your module dynamically add a page reference field to the copies and then your module add hooks to get the main event’s data at runtime?

The plan is to handle all that by the single DaterangePicker field. So no need to setup any new templates. No need to setup anything else. No need to construct any complicated selectors. Just add the daterange field to your event template and that's it. And to query events it will be "template=event,daterange.inMonth=2024-08" or similar and you'll get all events including all recurrences.

I use a hook to only show the daterange field of recurring events and hide everything else as everything else will be inherited from the main event. That means all occurrences are of the same type and have the same fields etc. just only the main event has all the fields populated.

On 9/6/2024 at 7:42 PM, monollonom said:

The only downside I can think of in your setup is what if a recurring event gets cancelled? Is there a way to easily delete the copies? If you have a page reference field it shouldn’t be too hard actually...

I'm working on that. It's not that hard, the only annoying thing is that I need to support both the calendar UI and the use case when someone edits events via the page tree. Both should be possible imho and unfortunately as it means more work.

On 9/6/2024 at 7:42 PM, monollonom said:

Maybe? If I rephrase what’s happening: you create the event, set its date to "recurring" and go ahead and create your copies. Then you save the page. If you edit it again, you have the "recurring" date type selected with the options to create new ones, but no indications of the copies previously created. Is this correct?

Yes. I thought of adding another grid that lists already created events that are part of the series, but not sure if that's good or not. Actually I have an idea that might also solve the problem mentioned above... I'll have to try this today or tomorrow!

Generally speaking I'll add a switch that lets the user choose from

  • edit this event
  • edit this and all following
  • edit all events

But it's not so easy unfortunately as that could mean we need to update 1000s of pages and therefore I'll need a gui with a progressbar again... That's why I think it will be necessary to add a second grid to the daterange field. It adds bloat to the UI but the other option would be to save the job in the background and process it via cronjob, but that means a more complicated setup and no instant feedback to the user.

I think a second table is the better and more flexible option.

On 9/6/2024 at 9:52 PM, BrendonKoz said:

I love the planned efficiency of that. I was actually thinking that, using meta(), there might be a way (or a few ways) to mix the two, but I hadn't fully thought it through.

Yeah me too, thx 🙂 It's actually really easy. Just a Page::loaded hook

  protected function inheritFieldValues(HookEvent $event): void
  {
    /** @var Page $page */
    $page = $event->object;
    if (!$page->hasField(self::field_date)) return;
    $date = $page->getFormatted(self::field_date);
    if (!$date->isRecurring) return;
    $mainPage = $date->mainPage;
    if (!$mainPage->id) return;
    foreach ($mainPage->fields as $f) {
      if ($f->name == self::field_date) continue;
      $page->set($f->name, $mainPage->get($f->name));
    }
  }

There are some downsides with this again, but that's only an issue for advanced calendars and should be no issue for 99,9% of use cases.

On 9/6/2024 at 9:52 PM, BrendonKoz said:

A client wants to hold a recurring meeting on the 2nd and 4th Thursday of the month. It's a book club. They know they'll be having this meeting for the full year, with the exception of major holidays. They want to include the name of the book that will be discussed for each meeting - so nearly everything will be exactly the same, with the exception of a description field (or a separate "book" field?).

Thx for the great use case!! This can quite easily be accomplished with how I developed the module. It would need some modifications but nothing complex I think.

On 9/6/2024 at 9:52 PM, BrendonKoz said:

And if yes, how would one then revert changes by overriding a description later (ex: update descriptions for all events from this date forward)?

My idea would be to have one global description and then have an additional field that is explicitly not inherited. So the main description could show something like this:

Quote

## Upcoming Reading Sessions

Join our weekly book club gatherings! Each session offers a chance to dive into captivating literature, share insights, and connect with fellow book lovers. Check out our calendar below to find the next meeting that piques your interest.

This weeks book:
[book-info]

## Join Us!

Don't miss out on these enriching discussions. Mark your calendar, bring your thoughts, and let's explore new worlds together, one page at a time. We look forward to seeing you at our next reading session!

So every event would show the general info and then merge the individual book info.

If the book-info is edited it only affects one event. If the general info is edited it would update all events (as it is inherited).

On 9/6/2024 at 9:52 PM, BrendonKoz said:

P.S. - Although the live-loading data table is really cool, without hooks, is it possible to (optionally?) hide that from the user until/unless they'd need to interact with it? I'm just thinking that from a UX perspective, it's pretty (and super cool) to look at but can take up a large portion of screen real estate if not actually needed.

The problem here is scalability. What if a user wants to create 1000 recurring events? Or maybe only 100. How would you do that? One option is to do that in the background. That means a possibly veeeeeery long loading time after save. Or maybe even a timeout. Or maybe the user hitting refresh several times and creating too much events or crashing the server or whatnot.

The other option is a background job. Also with downsides as mentioned above.

I think the preview table is the most intuitive and flexible solution. But if you have better ideas go ahead 🙂 

  • Like 1
Link to comment
Share on other sites

On 9/6/2024 at 9:52 PM, BrendonKoz said:

P.P.S. - Will this be a standalone Pro Rock module, or will it require RockFrontend as well?

It will be a standalone module, but for the recurring events part you will need the "RockGrid" module. Other than that there will be no dependencies, no RockMigrations, no RockFrontend.

  • Like 3
Link to comment
Share on other sites

2 hours ago, bernhard said:

Just a Page::loaded hook

Ah and so basically an editor could set one particular copy back to a regular “date” (uncheck “recurring”) and then be able to edit this specific one, which is nicely thought!

Maybe this could be implied by the UI (and maybe it already is?!): if there is $date->mainPage, hide the options to create copies and instead display something like “This event is a copy of ‘Main Page’” with an option to break the connection (and allow to create copies from there) or suggest to uncheck “recurring” to be able to edit the event.

I wonder if the fields should also show that changes won’t be applied if it’s a copy as well?

Link to comment
Share on other sites

22 minutes ago, monollonom said:

Ah and so basically an editor could set one particular copy back to a regular “date” (uncheck “recurring”) and then be able to edit this specific one, which is nicely thought!

Exactly. That's one of the advantages of this concept 🙂 The UI is the tricky part...

19 minutes ago, monollonom said:

Maybe this could be implied by the UI (and maybe it already is?!):

Yeah a draft is already there 🙂 

7zUoPNC.png

But there is more to it to cover all options. That's the next part to tackle 🤞

  • Like 3
Link to comment
Share on other sites

15 hours ago, bernhard said:

The UI is the tricky part...

To be honest, I am just loosely following the conversation, but it might be worth taking a look at BusyCal's GUI (busymac.com). I know that the interface in a browser and in a desktop app are two different beasts, but BusyCal might inspire Bernhard, if it hasn't already. Anyway, I am a happy user of BusyCal, and I highly recommend it to any macOS users.

Link to comment
Share on other sites

This is a demo of how to use the new trash feature 😍

And this is a demo of how to create a quite complex custom recurring schedule:

I think this should already be quite usable!

  • Like 4
Link to comment
Share on other sites

This looks truly great and I can easily see myself using it in several client projects! Great work 🙂

One note on the last video (pretty sure you already thought of this but hey): if there is a need for a first save before being able to create copies, maybe hide the options?

  • Like 1
Link to comment
Share on other sites

1 minute ago, monollonom said:

This looks truly great and I can easily see myself using it in several client projects! Great work 🙂

Cool, thx 🙂 

1 minute ago, monollonom said:

One note on the last video (pretty sure you already thought of this but hey): if there is a need for a first save before being able to create copies, maybe hide the options?

Thx a lot. Always appreciated! Actually it's the other way round. I think I can just remove the warning 😄 

  • Like 2
Link to comment
Share on other sites

On 9/8/2024 at 11:23 AM, bernhard said:

Thx for the great use case!! This can quite easily be accomplished with how I developed the module. It would need some modifications but nothing complex I think.

That's excellent. 🥰

Quote

The problem here is scalability. What if a user wants to create 1000 recurring events? Or maybe only 100. How would you do that? One option is to do that in the background. That means a possibly veeeeeery long loading time after save. Or maybe even a timeout. Or maybe the user hitting refresh several times and creating too much events or crashing the server or whatnot.

The other option is a background job. Also with downsides as mentioned above.

I think the preview table is the most intuitive and flexible solution. But if you have better ideas go ahead 🙂 

Ah, I did not intend to suggest complete removal - only a collapsed interface to lessen the vertical scroll when a user might not need to interact with individual dates in a recurrence. The difficulty might be in separating out the progress bar, depending on how you're handling the HTML (since I think having that shown at all times is absolutely important). szabesz mentioned BusyCal; I use Rainlendar for my calendaring; it uses a tabbed interface to section out different aspects of events/reminders, that way (theoretically) similarly used functions are grouped together, but not everything is viewed at once. That said, there are benefits to certain users having everything viewed at once that can get lost in tabs/collapsed areas too, so I humbly refer to your best judgment and preference! 🙂

  • Like 1
Link to comment
Share on other sites

Thx. I changed the default pagination size to 5 instead of 10 which makes the grid almost half the vertical size:

oEs8pyc.png

If anybody needs to see more events the pagination size can be changed via dropdown.

I think completely hiding it is not a good idea, because the live preview is important to double check the result of the rrule that is setup above. For example it's easy to forget to change from "every day" to "every week" and with the live preview one can instantly see that, because days will be mo, tu, we, th, fr... instead of mo, mo, mo, mo...

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

@bernhard Just a small note regarding the label "On months". It would be grammatically correct to use "In months," in my opinion.

https://langeek.co/en/grammar/course/567/on-in-month

It is because "on" would be correct only if the word "month" were used with a specific day, as the linked article explains. Since the months stand alone in this case, "In months" seems to be the correct choice.

  • Like 1
Link to comment
Share on other sites

In my implementation using FieltypeCombo, I basically followed what Apple is doing in their calendar app:

1295276182_Screenshot2024-09-11at10_50_00.png.937c0992da4b3a75628eca47badae2f7.png1461750913_Screenshot2024-09-11at10_50_12.png.b67bcf901fef00f5916703a2cfa827cb.png1014479128_Screenshot2024-09-11at10_50_25.png.8681ee07ef1608b644d9730fc5e89896.png41692689_Screenshot2024-09-11at10_50_41.thumb.png.1a373b47fde967e0bcbf0946b97d5dbb.png634091176_Screenshot2024-09-11at10_50_52.png.4ef6a895fcf189d6702bdb6fdc676ae0.png1025393694_Screenshot2024-09-11at10_51_07.png.dec33595901e43a84734f358bef8b13f.png

Here’s an export of my field:

Spoiler

{
    "date": {
        "id": 100,
        "name": "date",
        "label": "Date",
        "flags": 32768,
        "type": "FieldtypeCombo",
        "i1_label": "Starts:",
        "i1_name": "start",
        "i1_type": "Datetime",
        "i2_label": "Ends:",
        "i2_name": "end",
        "i2_type": "Datetime",
        "i3_label": "All day?",
        "i3_name": "day",
        "i3_type": "Checkbox",
        "i4_label": "Starts:",
        "i4_name": "start_time",
        "i4_type": "Datetime",
        "i5_label": "Ends:",
        "i5_name": "end_time",
        "i5_type": "Datetime",
        "qty": 22,
        "hideWrap": 0,
        "useColNames": 0,
        "modSchema": 0,
        "useDataCol": 0,
        "i1_ok": "1",
        "i1_collapsed": 0,
        "i1_inputType": "text",
        "i1_htmlType": "date",
        "i1_datepicker": 3,
        "i1_timeInputSelect": 0,
        "i1_dateInputFormat": "d/m/Y",
        "i1_size": 0,
        "i1_dateSelectFormat": "yMd",
        "i1_yearFrom": 1923,
        "i1_yearTo": 2043,
        "i1_yearLock": 0,
        "i1_columnWidth": 50,
        "i2_ok": "1",
        "i2_collapsed": 0,
        "i2_inputType": "text",
        "i2_htmlType": "date",
        "i2_datepicker": 3,
        "i2_timeInputSelect": 0,
        "i2_dateInputFormat": "d/m/Y",
        "i2_size": 0,
        "i2_dateSelectFormat": "yMd",
        "i2_yearFrom": 1923,
        "i2_yearTo": 2043,
        "i2_yearLock": 0,
        "i2_columnWidth": 50,
        "i3_ok": "1",
        "i3_collapsed": 0,
        "i3_checkedValue": "1",
        "i3_columnWidth": 100,
        "i4_ok": "1",
        "i4_collapsed": 0,
        "i4_inputType": "text",
        "i4_htmlType": "datetime",
        "i4_datepicker": 3,
        "i4_timeInputSelect": 1,
        "i4_dateInputFormat": "d/m/Y",
        "i4_size": 0,
        "i4_dateSelectFormat": "yMd",
        "i4_yearFrom": 1923,
        "i4_yearTo": 2043,
        "i4_yearLock": 0,
        "i4_columnWidth": 50,
        "i5_ok": "1",
        "i5_collapsed": 0,
        "i5_inputType": "text",
        "i5_htmlType": "datetime",
        "i5_datepicker": 3,
        "i5_timeInputSelect": 1,
        "i5_dateInputFormat": "d/m/Y",
        "i5_size": 0,
        "i5_dateSelectFormat": "yMd",
        "i5_yearFrom": 1923,
        "i5_yearTo": 2043,
        "i5_yearLock": 0,
        "i5_columnWidth": 50,
        "order": "3,1,2,4,5,6,8,7,9,10,11,14,15,16,17,18,19,20",
        "i1_showIf": "date_day=1",
        "i1_defaultToday": 1,
        "i2_showIf": "date_day=1",
        "i2_defaultToday": 1,
        "i4_showIf": "date_day=0",
        "i4_timeMin": "00:00",
        "i4_timeMax": "23:59",
        "i4_defaultToday": 1,
        "i5_showIf": "date_day=0",
        "i5_timeMin": "00:00",
        "i5_timeMax": "23:59",
        "i5_defaultToday": 1,
        "i6_label": "Repeat?",
        "i6_name": "repeat",
        "i6_type": "Checkbox",
        "i7_label": " ",
        "i7_name": "frequency",
        "i7_type": "Select",
        "i8_label": "Every:",
        "i8_name": "every",
        "i8_type": "Integer",
        "i9_label": "On:",
        "i9_name": "frequency_week",
        "i9_type": "Checkboxes",
        "i10_label": "Each:",
        "i10_name": "frequency_month",
        "i10_type": "Checkboxes",
        "i11_label": "In:",
        "i11_name": "frequency_year",
        "i11_type": "Checkboxes",
        "i6_ok": "1",
        "i6_collapsed": 0,
        "i6_checkedValue": "1",
        "i6_columnWidth": 100,
        "i7_options": "+0=day(s)\n1=week(s)\n2=month(s)\n3=year(s)",
        "i7_ok": "1",
        "i7_collapsed": 0,
        "i7_columnWidth": 50,
        "i8_ok": "1",
        "i8_collapsed": 0,
        "i8_inputType": "number",
        "i8_size": 10,
        "i8_columnWidth": 50,
        "i9_options": "0=Monday\n1=Tuesday\n2=Wednesday\n3=Thursday\n4=Friday\n5=Saturday\n6=Sunday",
        "i9_ok": "1",
        "i9_collapsed": 0,
        "i9_optionColumns": 1,
        "i9_columnWidth": 100,
        "i10_options": "1=1st\n2=2nd\n3=3rd\n4=4th\n5=5th\n6=6th\n7=7th\n8=8th\n9=9th\n10=10th\n11=11th\n12=12th\n13=13th\n14=14th\n15=15th\n16=16th\n17=17th\n18=18th\n19=19th\n20=20th\n21=21st\n22=22nd\n23=23rd\n24=24th\n25=25th\n26=26th\n27=27th\n28=28th\n29=29th\n30=30th\n31=31st",
        "i10_ok": "1",
        "i10_collapsed": 0,
        "i10_optionColumns": 7,
        "i10_columnWidth": 100,
        "i11_options": "1=January\n2=February\n3=March\n4=April\n5=May\n6=June\n7=July\n8=August\n9=September\n10=October\n11=November\n12=December",
        "i11_ok": "1",
        "i11_collapsed": 0,
        "i11_optionColumns": 4,
        "i11_columnWidth": 100,
        "i7_showIf": "date_repeat=1",
        "i8_showIf": "date_repeat=1",
        "i9_showIf": "date_repeat=1,date_frequency=1",
        "i10_showIf": "date_repeat=1,date_frequency=2,date_specific=0",
        "i11_showIf": "date_repeat=1,date_frequency=3",
        "i8_min": "1",
        "i8_initValue": "1",
        "i14_label": "Target a specific day?",
        "i14_name": "specific",
        "i14_type": "Checkbox",
        "i15_label": "On the:",
        "i15_name": "specific_which",
        "i15_type": "Select",
        "i16_name": "specific_day",
        "i16_type": "Select",
        "i14_ok": "1",
        "i14_collapsed": 0,
        "i14_checkedValue": "1",
        "i14_columnWidth": 100,
        "i15_options": "+1=first\n2=second\n3=third\n4=fourth\n5=fifth\n-1=last",
        "i15_ok": "1",
        "i15_collapsed": 0,
        "i15_columnWidth": 50,
        "i16_options": "Days\n      0=Monday\n      1=Tuesday\n      2=Wednesday\n      3=Thursday\n      4=Friday\n      5=Saturday\n      6=Sunday\n+7=day\n8=weekday\n9=weekend day",
        "i16_ok": "1",
        "i16_collapsed": 0,
        "i16_columnWidth": 50,
        "i17_label": "End repeat:",
        "i17_name": "repeat_end",
        "i17_type": "Select",
        "i18_label": " ",
        "i18_name": "repeat_after",
        "i18_type": "Integer",
        "i19_label": " ",
        "i19_name": "repeat_date",
        "i19_type": "Datetime",
        "i17_options": "+0=never\n1=after X time(s)\n2=on date",
        "i17_ok": "1",
        "i17_collapsed": 0,
        "i17_columnWidth": 50,
        "i18_ok": "1",
        "i18_collapsed": 0,
        "i18_showIf": "date_repeat=1,date_repeat_end=1",
        "i18_inputType": "number",
        "i18_size": 10,
        "i18_min": "1",
        "i18_initValue": "1",
        "i18_columnWidth": 25,
        "i19_ok": "1",
        "i19_collapsed": 0,
        "i19_showIf": "date_repeat=1,date_repeat_end=2",
        "i19_inputType": "text",
        "i19_htmlType": "date",
        "i19_datepicker": 3,
        "i19_timeInputSelect": 1,
        "i19_dateInputFormat": "d/m/Y",
        "i19_size": 0,
        "i19_dateSelectFormat": "yMd",
        "i19_yearFrom": 1923,
        "i19_yearTo": 2043,
        "i19_yearLock": 0,
        "i19_defaultToday": 1,
        "i19_columnWidth": 25,
        "i16_label": " ",
        "i14_showIf": "date_repeat=1,date_frequency=2|3",
        "i15_showIf": "date_repeat=1,date_frequency=2|3,date_specific=1",
        "i16_showIf": "date_repeat=1,date_frequency=2|3,date_specific=1",
        "i17_showIf": "date_repeat=1",
        "i1_required": 1,
        "i1_requiredIf": "date_day=1",
        "i2_required": 1,
        "i2_requiredIf": "date_day=1",
        "i4_required": 1,
        "i4_requiredIf": "date_day=0",
        "i5_required": 1,
        "i5_requiredIf": "date_day=0",
        "i8_required": 1,
        "i8_requiredIf": "date_repeat=1",
        "i7_required": 1,
        "i7_requiredIf": "date_repeat=1",
        "i7_defaultValue": "1",
        "i15_required": 1,
        "i15_requiredIf": "date_repeat=1,date_frequency=2|3,date_specific=1",
        "i15_defaultValue": "1",
        "i16_required": 1,
        "i16_requiredIf": "date_repeat=1,date_frequency=2|3,date_specific=1",
        "i16_defaultValue": "1",
        "i18_required": 1,
        "i18_requiredIf": "date_repeat=1,date_repeat_end=1",
        "i19_required": 1,
        "i19_requiredIf": "date_repeat=1,date_repeat_end=2",
        "i20_label": "RRule",
        "i20_name": "rrule",
        "i20_type": "Textarea",
        "i20_ok": "1",
        "i20_collapsed": 4,
        "i20_minlength": 0,
        "i20_maxlength": 0,
        "i20_showCount": 0,
        "i20_rows": 2,
        "i20_columnWidth": 100,
        "i6_notes": "If active, the end date/time above will be used to determine the event’s duration",
        "i22_label": "Pattern",
        "i22_name": "recap",
        "i22_type": "DELETE",
        "collapsed": 0,
        "i20_showIf": "date_repeat=1",
        "i22_ok": 1,
        "i22_collapsed": 6,
        "i22_showIf": "date_repeat=1",
        "i22_minlength": 0,
        "i22_maxlength": 0,
        "i22_showCount": 0,
        "i22_rows": 5,
        "i22_columnWidth": 100,
        "i4_timeInputFormat": "H:i",
        "i5_timeInputFormat": "H:i",
        "i19_timeInputFormat": "H:i",
        "i3_description": "",
        "i3_notes": "",
        "i3_icon": "",
        "i3_textformatters": "",
        "i3_showIf": "",
        "i3_required": "",
        "i3_requiredIf": "",
        "i3_addClass": "",
        "i3_checkboxLabel": "",
        "i3_uncheckedValue": "",
        "i3_move": "",
        "i1_description": "",
        "i1_notes": "",
        "i1_icon": "",
        "i1_textformatters": "",
        "i1_requiredAttr": "",
        "i1_addClass": "",
        "i1_dateMin": "",
        "i1_dateMax": "",
        "i1_timeStep": "",
        "i1_timeMin": "",
        "i1_timeMax": "",
        "i1_timeInputFormat": "",
        "i1_placeholder": "",
        "i1_yearRange": "",
        "i1_move": "",
        "i2_description": "",
        "i2_notes": "",
        "i2_icon": "",
        "i2_textformatters": "",
        "i2_requiredAttr": "",
        "i2_addClass": "",
        "i2_dateMin": "",
        "i2_dateMax": "",
        "i2_timeStep": "",
        "i2_timeMin": "",
        "i2_timeMax": "",
        "i2_timeInputFormat": "",
        "i2_placeholder": "",
        "i2_yearRange": "",
        "i2_move": "",
        "i4_description": "",
        "i4_notes": "",
        "i4_icon": "",
        "i4_textformatters": "",
        "i4_requiredAttr": "",
        "i4_addClass": "",
        "i4_dateMin": "",
        "i4_dateMax": "",
        "i4_timeStep": "",
        "i4_placeholder": "",
        "i4_yearRange": "",
        "i4_move": "",
        "i5_description": "",
        "i5_notes": "",
        "i5_icon": "",
        "i5_textformatters": "",
        "i5_requiredAttr": "",
        "i5_addClass": "",
        "i5_dateMin": "",
        "i5_dateMax": "",
        "i5_timeStep": "",
        "i5_placeholder": "",
        "i5_yearRange": "",
        "i5_move": "",
        "i6_description": "",
        "i6_icon": "",
        "i6_textformatters": "",
        "i6_showIf": "",
        "i6_required": "",
        "i6_requiredIf": "",
        "i6_addClass": "",
        "i6_checkboxLabel": "",
        "i6_uncheckedValue": "",
        "i6_move": "",
        "i8_description": "",
        "i8_notes": "",
        "i8_icon": "",
        "i8_textformatters": "",
        "i8_addClass": "",
        "i8_max": "",
        "i8_move": "",
        "i7_description": "",
        "i7_notes": "",
        "i7_icon": "",
        "i7_textformatters": "",
        "i7_addClass": "",
        "i7_move": "",
        "i9_description": "",
        "i9_notes": "",
        "i9_icon": "",
        "i9_textformatters": "",
        "i9_required": "",
        "i9_requiredIf": "",
        "i9_addClass": "",
        "i9_defaultValue": "",
        "i9_optionWidth": "",
        "i9_move": "",
        "i10_description": "",
        "i10_notes": "",
        "i10_icon": "",
        "i10_textformatters": "",
        "i10_required": "",
        "i10_requiredIf": "",
        "i10_addClass": "",
        "i10_defaultValue": "",
        "i10_optionWidth": "",
        "i10_move": "",
        "i11_description": "",
        "i11_notes": "",
        "i11_icon": "",
        "i11_textformatters": "",
        "i11_required": "",
        "i11_requiredIf": "",
        "i11_addClass": "",
        "i11_defaultValue": "",
        "i11_optionWidth": "",
        "i11_move": "",
        "i14_description": "",
        "i14_notes": "",
        "i14_icon": "",
        "i14_textformatters": "",
        "i14_required": "",
        "i14_requiredIf": "",
        "i14_addClass": "",
        "i14_checkboxLabel": "",
        "i14_uncheckedValue": "",
        "i14_move": "",
        "i15_description": "",
        "i15_notes": "",
        "i15_icon": "",
        "i15_textformatters": "",
        "i15_addClass": "",
        "i15_move": "",
        "i16_description": "",
        "i16_notes": "",
        "i16_icon": "",
        "i16_textformatters": "",
        "i16_addClass": "",
        "i16_move": "",
        "i17_description": "",
        "i17_notes": "",
        "i17_icon": "",
        "i17_textformatters": "",
        "i17_required": "",
        "i17_requiredIf": "",
        "i17_addClass": "",
        "i17_defaultValue": "",
        "i17_move": "",
        "i18_description": "",
        "i18_notes": "",
        "i18_icon": "",
        "i18_textformatters": "",
        "i18_addClass": "",
        "i18_max": "",
        "i18_move": "",
        "i19_description": "",
        "i19_notes": "",
        "i19_icon": "",
        "i19_textformatters": "",
        "i19_requiredAttr": "",
        "i19_addClass": "",
        "i19_dateMin": "",
        "i19_dateMax": "",
        "i19_timeStep": "",
        "i19_timeMin": "",
        "i19_timeMax": "",
        "i19_placeholder": "",
        "i19_yearRange": "",
        "i19_move": "",
        "i20_description": "",
        "i20_notes": "",
        "i20_icon": "",
        "i20_textformatters": "",
        "i20_required": "",
        "i20_requiredAttr": "",
        "i20_requiredIf": "",
        "i20_addClass": "",
        "i20_stripTags": "",
        "i20_placeholder": "",
        "i20_initValue": "",
        "i20_move": "",
        "i0_label": "",
        "i0_name": "",
        "i0_type": "",
        "showIf": "",
        "themeOffset": "",
        "themeBorder": "",
        "themeColor": "",
        "columnWidth": 100,
        "required": "",
        "requiredIf": "",
        "addClass": ""
    }
}

 

Link to comment
Share on other sites

1 hour ago, bernhard said:

@monollonom the interface is nice, but also limited as far as I can see? Would the rule shown in the screenshot above be at all possible in your UI? Or what did you want to say with your post?

At first it was @szabesz’s suggestion that made me post this, because of the way things are handled in the calendar app: there’s no “In Months”, just “Each” and it only appears if the frequency is “Every month” and so on... I liked it this way because of how the ux “difficulty” (let’s say) is incremental.

But in the end I think your way of doing actually makes more sense (and you are right that it’s more limited in my case)... so you can dismiss my post 🙂

Spoiler

For completeness:

 

  • Like 1
Link to comment
Share on other sites

14 minutes ago, monollonom said:

For completeness:

Very well done! Looks great. What does it show if "every 1 day" is selected?

I also thought of hiding those settings based on the frequency dropdown, but I realised that they can be used in a combined way as I showed above. Not sure what to do about this. Sometimes it might be preferable to have more options, sometimes it might be better to have a slicker ui...

  • Like 1
Link to comment
Share on other sites

24 minutes ago, bernhard said:

What does it show if "every 1 day" is selected?

Completeness wasn’t there apparently lol. Basically “Every X day(s)” would show nothing:

1788939895_Screenshot2024-09-11at14_07_58.thumb.png.ca993352f83555d07759402f8a68d691.png

Hence why I like your solution better: it allows the user to quickly repeat every day, except for some. In my UI you would need to understand that you need to select the “month(s)” frequency, and select the days, but even then it’s inferior because you select the days’ number and not the day of week. I rewatched my video and you can actually do that using the “week(s)” frequency, but still!

24 minutes ago, bernhard said:

Sometimes it might be preferable to have more options, sometimes it might be better to have a slicker ui...

Don’t change a thing, you’re clearly on the right path!

(what do you input here?158210032_Screenshot2024-09-11at14_14_03.png.e546f06f98ce41692d69697334db48e8.png )

Edited by monollonom
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...