Jump to content

bernhard

Members
  • Posts

    6,030
  • Joined

  • Last visited

  • Days Won

    292

Everything posted by bernhard

  1. Hey @gebeer thx for choosing RockPdf and thx for the great write up 😎 I agree with @gmclelland that the pdfs look very good! πŸ™‚ I'm wondering why you have append = true here? Some background for anybody interested: This flag is intended for multi-language use, where you might want to create multiple versions of one pdf (english, german, etc.). Using this flag you can foreach over all languages and then create the PDF in that language and append it to the file field in question πŸ™‚ Maybe that's why you are using it? Then this background info might be worth to mention πŸ™‚ This is a real gem! For anybody not aware of this see https://www.baumrock.com/en/processwire/modules/rockfrontend/docs/dom/
  2. 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!
  3. Done! Here is the link for everybody to submit a review πŸ™‚ https://www.softaculous.com/review/ProcessWire If you are short on time: https://www.softaculous.com/rate/ProcessWire Both without registration!
  4. Exactly. That's one of the advantages of this concept πŸ™‚ The UI is the tricky part... Yeah a draft is already there πŸ™‚ But there is more to it to cover all options. That's the next part to tackle 🀞
  5. 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.
  6. 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. 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. 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. 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. 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. 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. 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. 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: 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). 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 πŸ™‚
  7. @FireWire glad you found the issue. Could you please open a new thread for this and explain the benefits of filesystem cache and how you are using it? Then we can discuss there in an isolated environment which makes it easier to reply, link, refer to... Please also explain your use case as I've never used filesystem cache.
  8. Are your time settings correct? Maybe filemtime produces a wrong result or maybe time(). Those are compared and if one is wrong it could think the file changed
  9. One idea to solve this in a quite easy way: Add a hidden field to your template called "quantity_before_edit" Add a saveReady hook that calculates the difference between "quantity_before_edit" and "quantity" Use that delta to calculate the new "quantity" Example: Quantity is 10 User A visits the public website (quantity = 10) User B visits the backend (quantity = 10, quantity_before_edit = 10) User A orders 2 pieces and the page gets saved (quantity = 8 ) User B saves the page without editing the quantity (quantity = 10, quantity_before_edit = 10) saveReady hook kicks in: quantity = 10, quantity_before_edit = 10 ---> diff = 0 get current quantity from uncached page from the database, quantity = 8 add diff and write result to $page->quantity = 8 ideally show a warning to the user that a quantity has been saved that is different from his/her input page is saved, final result is quantity = 8
  10. 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: 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. 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: 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 hope the video answers that? πŸ™‚
  11. The only bullet proof solution is called "event sourcing". The first 4 minutes will explain the concept very well and will answer your question from a theoretic point of view πŸ™‚ But it's not really easy to implement πŸ˜…
  12. As @teppo mentioned in the issue this is possible to do this and I think this is the most robust way of writing code for page reference fields that work consistently and are not going to break if someone changes field formatting: foreach($page->get('my_pageref_field[]') as $p) ... if($page->get('my_pageref_field.first')) ...
  13. Ok got it working! I think that's quite cool and has a lot of potential. Does that code example make sense @poljpocket ? class HelloWorld { ___greet(salut = "hello", what = "world") { return `${salut} ${what}`; } } const helloWorld = ProcessWire.wire(new HelloWorld()); // shows hello world console.log(helloWorld.greet()); // shows hi there console.log(helloWorld.greet("hi", "there")); // add BEFORE hook ProcessWire.addHookBefore("HelloWorld::greet", (event) => { event.arguments(0, "hallo"); event.arguments(1, "welt"); }); // shows hallo welt console.log(helloWorld.greet()); // shows hallo welt console.log(helloWorld.greet("servas", "oida")); // add AFTER hook ProcessWire.addHookAfter("HelloWorld::greet", (event) => { console.log(event.arguments()); event.return = "hi universe"; }); // shows ['hallo', 'welt'] // shows hi universe console.log(helloWorld.greet()); And another one showing hook priority: class PrioDemo { ___greet() { return "hello world"; } } const prio = ProcessWire.wire(new PrioDemo()); ProcessWire.addHookAfter( "PrioDemo::greet", () => { console.log("second"); }, 20 ); ProcessWire.addHookAfter( "PrioDemo::greet", () => { console.log("first"); }, 10 ); ProcessWire.addHookAfter( "PrioDemo::greet", () => { console.log("third"); }, 30 ); // shows // first // second // third // hello world console.log(prio.greet());
  14. Haha you don't have to. I appreciate the input and questions. It shows I have to be more careful with the explanations. The terms "frontend" and "backend" are very confusing in that context as they refer to two things. Backend could either be the pw admin area or the server implementation, in our case php. Frontend could be the websites public frontend or it could be the JS implementation on the frontend (public website) or the backend (pw admin). I try to be more careful with the wording in my explanations once I have something working. At the moment I'm fighting with modifying arguments from within the addHookBefore...
  15. Didn't feel like that, just thought you misunderstood the request from what you wrote. It's really not about the backend. It has nothing to do with the backend. That's why I still don't understand your answers and your example using fetch() as that's a totally different story. My "sold out" wording was misleading in that regard, I'll edit that to avoid future confusion, thx. Nothing. It really doesn't matter. It's absolutely not related to the backend at all. It's just about having the convenience and power of applying the same concept that we have in the backend for stuff on the JS side (most likely the pw admin area). You mean customization via dom element attributes? That might work for their use case but is something very different than what I propose. A very powerful concept of hooks are before hooks with event.replace = true. I don't think that is possible with the snipcart way. But I might be wrong. Admittedly I took just a quick look πŸ™‚ (edit: need a real world example why before hooks are super useful? see here) As an example where dom-attributes make absolutely no sense or are totally limited: My module RockGrid uses tabulator.info to render tables. Rows are basically PW pages. So the backend only provides the endpoint to get a json with page objects aka table rows. Everything else happens on the frontend and from within JS. I only have a single dom element <div id='RockGrid-123'></div> With hooks my clients could modify any part of the grid just by hooking into the relevant method. A lot can be done with callbacks and tabulator has an internal event bus that can be used for that, but I think hooks are a lot more powerful and feel a lot more familiar when you know pw well. I might come up with better examples. Thx for your input.
  16. Good point. I think you could hook BEFORE Pages::clone and do exactly that instead of instructing the users to so manually. Note that you need the $event->replace = true; flag so that the original clone process does not kick in after your new page has been created. Something like this (untested): <?php $wire->addHookBefore('Pages::clone', function($event) { $page = $event->arguments('page'); if($page->template != 'your-template') return; $p = new Page(); $p->template = $page->template; $p->parent = $page->parent; $p->title = $page->title; $p->save(); $event->replace = true; // should not be necessary but just to be sure wire()->session->redirect($p->editUrl()); }); Of course that would mean that any clone would wipe all data. But you could add a checkbox to that page that disables the hook and therefore would work as a normal clone.
  17. @poljpocket thx for your question. It sounds like you totally misunderstood the idea of my proposal, so maybe the explanation was not good. I'll try it in other words: Imagine you are building a module that stores monetary values in the database. They are stored as numbers (eg "123.45") in the db, but you likely want to present them to the user in a different way. Now there could be hundreds of different formats the enduser might want to see and that would mean a lot of options for you as the module developer to support. In RockCommerce for example I need the flexibility to modify prices based on the users need (eg customers/website visitors could see different prices based on their region and tax settings. See https://www.hetzner.com/storage/storage-box/ for example where you can choose the tax setting in the header and then you get different prices. My proposal is to bring hooks to the JS world of ProcessWire as well, as we all know how powerful and useful these hooks can be! In the example you as a module dev would only have to implement a hookable "___format()" method instead of the "format()" method without underscores and you'd only have to instantiate the class via ProcessWire.proxy(...): class MyMoneyModule { ___format(num) { return "€ " + num; } } let demo = ProcessWire.proxy(new MyMoneyModule()); // this will output € 123.45 console.log(demo.format(123.45)); ProcessWire.addHookAfter("MyMoneyModule::format", (event) => { event.return = "Hooked format: " + event.return; }); // this will output "Hooked format: € 123.45" console.log(demo.format(123.45)); ProcessWire.addHookBefore("MyMoneyModule::format", (event) => { event.return = "****"; event.replace = true; }); // this will output "SOLD OUT" console.log(demo.format(123.45)); I hope this makes things clear πŸ™‚
  18. I think this is perfectly fine, but why not add this as a method in your class to get rid of the hook? // in your pageclass public function tools() { // get tools based on page name $toolsClass = ... // return instance of tools return new $toolsClass(); } // usage $pages->get(...)->tools()->foo(); The benefit is that your IDE will understand your code and if you add return types etc. it will help you with code completion.
  19. Thx for sharing! Yeah that's basically the way I have been building things as well for a long time. But adding all the fields and getting every single detail done correctly is so much work, so I decided to finally do it better in a reusable way that has a good and compact ui for clients and does not rely on a multitude of fields. What do you mean by hiding past occurrences? In my case I'm creating events via rrule.js and the start date is always the first event page's date. So recurring events will always be in the future (relative to the first event). This is the UI for creating events and that table shows the items to be created once the user clicks on "create events". That view is really nice as it updates instantly, so anyone should get a very quick understanding of what all the settings do: The idea is to get all the necessary tools for handling events with as minimum effort as possible. So I've already built the RockDaterangePicker field last month. The next step was to add a calendar view. I first added that to the frontend, but then I realised how much effort it is to setup all necessary ajax endpoints and handling all clicks and all that, so I decided to move that into the backend. If anybody needs to show these events on the frontend, this will be as easy as a foreach + page selector or just adding a simple fullcalendar, which is also easy as long as you don't have to deal with managing events (edit, move, change etc). And then I realised that when having all that ready in the backend it might be the right moment to also tackle recurring events. So at the moment the concept is that you add a calendar field to one parent page. There you define which pages to show as events (which will likely be the children of the parent page, but could be any other selector or hook as well). Then for the events all you have to do is to add the daterange field which will be created during installation of the module. This field will tell the calendar where to show events (on which day and time) and it will also save all the info whether the event is part of a series etc...
  20. Great to hear that! I'm also very motivated as this has been an issue for me (and I guess many of us) for a long time. Things are really going well so far. I had some troubles today with timezone issues (events starting at 10:00 suddenly started at 11:00 from one month to another etc.) and also with the performance of creating recurring events. Turned out I set a generic pagename and PW had to append a unique suffix for every created page and that got slower and slower as pages under that parent grew in count. I've just quick-fixed that by setting a random name via uniqid() and the performance is quite impressive imho (real world speed on my macbook air with ddev): How cool is the live progress bar??? 😎 It works with realtime feedback from the server using SSE. No polling. No sending hundreds of requests to the server πŸš€ I also wanted to update the grid above the progress bar, but that also turned out to slow things down a lot, so I turned that off for now. I have to look into that tomorrow. I think it should be an easy fix. But that's it for today πŸ™‚
  21. Absolutely true, thx! I think you are right and I'll not add this. Instead I'll try to make the editing interface via FullCalendar as smooth as possible. I think that's the best common ground that every installation will have. Once they have added their holidays to ther calendar they will see them in the month view and then they can move/clone events to the desired dates by drag&drop. That's already possible by just clicking on the trash icon before creating recurring events 😎 Yep, and I've made good progress so far πŸ™‚ What you mention will be supported. That's how it will work (or as mentioned one can exclude items before creation) πŸ™‚
  22. PagePaths is part of the core, just not installed. In your ProcessWire backend go to Modules > Core and install "Page Paths" module from the list. @Robin S I guess your module could install Page Paths automatically, no?
  23. Thanks! Yeah I've been there several times but never really managed to get something solid. Great!! I'll send you a copy as soon as I have something usable. Wow, that will be very helpful for my project! Thank you. The company behind is even Austrian 😍 I can think of adding "holiday" as an option to the rrule interface. There one could select "Austria" and then I could add a callback that loops over all rrule dates (eg "every day until 1.1.2025") and filters out non-holiday days, which would result in only holiday dates in the events grid of to-be-created events.
  24. That's a very slick interface, congrats @Stefanowitsch Unfortunately to support all possible options there are some more options and this is what I came up with so far: This seems to work for almost all use cases that I could think of so far. The interface could even be improved by hiding options that don't make sense (but it's not so easy to define which they are and when). The only case I don't have a solution so far is "every sunday or holiday at 20:00" Anybody knows or has a good idea how to support holidays as recurrence rule? That's obviously a challenge, because holidays are very specific by region... My best idea so far is to add a "clone" feature to the month calendar view where the client could just drag and drop the event (a guided tour through a cave) from sunday to a holiday and click "clone" instead of "move". That way they could quickly add all events for all sundays and then drag them over to all the holidays...
Γ—
Γ—
  • Create New...