Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 10/15/2024 in all areas

  1. Hi @Stefanowitsch thx for your interesting question ๐Ÿ™‚ I think you have two options: Create a central event calendar and manage events from there Add the calendar field to the "group" template and then manage events as children of that group, eg "AA 12 Schritte" would show a calendar and there you create "January Meetup", "February Meetup" etc. I think the module should work for you already as it is. It has two hookable methods for modifying the events that the calendar shows on each admin page and the data that is used for each single event: Maybe we need to add another hook for the endpoint that creates recurring events, as this is at the moment hardcoded to use the current page as parent of new events, but that should not be a big deal. I think you could also combine both options: Have a calendar for every group where you manage the group's events. And have one central calendar that shows all events combined. Maybe that would be the easiest way to go, because on the combined calendar you'd have to code something that tells the calendar which group the created recurring events belong to. If you have a calendar directly on the group page then this is not necessary. But I'm happy to help you with anything you need and I'm sure we'll make it work ๐Ÿ™‚ BTW: You have a design issue on your page at a special resolution:
    2 points
  2. I got great input from @BrendonKoz and @monollonom (and others) in the thread about RockCalendar development, so I thought I'd start a thread for RockCommerce too, as this module is finally taking shape ๐Ÿš€ Today I implemented automatic image changing when a color option of the product is changed. This is so great, I had to share that!!! ๐Ÿ˜ Now what is so great about this? The implementation! It will work with ANY frontend. Really. No matter if you are using Tailwind, UIkit, Bootstrap, whatever. All you have to do is to add a simple hook: // set image according to selected option ProcessWire.addHookAfter("RcVariationOption::enable", (event) => { // get the RcVariationOption instance const option = event.object; // get the image number from client input that is stored in rc-image attribute const image = parseInt(option.$rcattr("rc-image")); if (isNaN(image)) return; // find the thumbnail link and click it const index = image - 1; const link = document.querySelector( ".shop-gallery [uk-slideshow-item='" + index + "'] a" ); if (link) link.click(); }); Isn't that JavaScript code, you might ask? YES! ๐Ÿ˜Ž I have totally refactored the JS implementation over the last week so that RockCommerce now uses the powerful concepts of Hooks that we know from PHP but in the JS world!! (See Let's bring hooks to JavaScript) I am in contact with Ryan to add this to the core once it is tested more. But so far it is really a pleasure to work with on both sides: First, when developing RockCommerce all I had to do to make the above shown hook possible is to make the enable() method of the RcVariationOption hookable by simply renaming it to ___enable() Second, it's also great for anybody using this module, as you can not only LISTEN to those events but you can also totally change the return value or even completely replace it's implementation. For example this is what I use to show custom prices based on the taxrate of the user visiting the website: ProcessWire.addHookAfter("RcProduct::priceTotal", (event) => { // get the money object // it's not a float, because 0.1 + 0.2 !== 0.3 in binary world! const money = event.return; // return the initial price plus the added tax event.return = money.times(1 + RockCommerce.getTaxrate() / 100); }); I've only been working with this new concept for a few days, but I already love it and can't believe I haven't implemented before! The next part to tackle is the cart ๐Ÿคฏ๐Ÿ˜… I'll keep you posted! PS: Anybody having an e-commerce project coming up or anybody interested in beta testing? Let's get in touch!
    1 point
  3. Hey all! I've been creating new block/widget buttons for the current project I'm working on and wanted to share them in case they may be useful to others. They're SVGs intended to complement the style of buttons that come with RockPageBuilder. This post has been updated with a link to a Github repository containing all of the buttons currently available. New buttons have been added. Existing buttons have have been tweaked for quality and consistency. Download from or fork the Builder Buttons Github repository Rather than keep this post up to date with every button that is added, visit the Github repo to see the most current example and download/clone buttons for use in your projects. Buttons include: Accordion Announcement Articles Audio Bios Call To Action Card Over Image Code/Embed Events Image Image Carousel Image Mosaic Image Roll List Lists Products Reviews Video Weather Preview (not in order of list) If you find them useful, let me know what you think!
    1 point
  4. Thanks. Well, ok, then I can only guess that WhatsApp was not working primarily due to a too heavy image - the Waypoint agency branding page (one you tried) is the only one on which I have optimized the og:image PNG (down from over 1MB to about 500KB). For the Meta share debugger errors - again, thanks to everyone for giving it a look. I will clean up all errors you've pointed out and update the post in case any of those fixes end up also making the debugger error go away.
    1 point
  5. Hi @bernhard, I just installed RockSettings for the first time on PW 3.0.210. The project is still using CKEditor. So I got warnings for the TinyMCE fields that got installed through the RockSettings migration. So I need to override those. I'm doing this in site/migrate.php /** @var RockMigrations $rm */ $rm = $this->wire->modules->get('RockMigrations'); $rm->watch(__FILE__, 0); // note priority set to 0 to execute before /site/modules/RockSettings/pageClasses/SettingsPage.php which has priority 1 $rm->migrate([ 'fields' => [], 'templates' => [ 'rocksettings' => [ 'fields' => [ 'title' => [ 'collapsed' => 4, ], 'rocksettings_contact' => [ 'collapsed' => Inputfield::collapsedNo, 'inputfieldClass' => 'InputfieldCKEditor', ], ... The migration takes no effect, because /site/modules/RockSettings/pageClasses/SettingsPage.php has priority 1 and site/migrate always has 9999, no matter what I set in the watch call. So the RockSettings migration always overrides my mygration in site/migrate.php Here's the CLI output with $rm->watch(__FILE__, 0); ddev exec php site/modules/RockMigrations/migrate.php Refresh modules ##### SORTED WATCHLIST ##### Array ( [#9999] => Array ( [0] => /var/www/html/site/migrate.php ) [#1] => Array ( [0] => /var/www/html/site/modules/RockSettings/pageClasses/SettingsPage.php ) ) ### Migrate items with priority #9999 ### Watchfile: /site/migrate.php Load /var/www/html/site/migrate.php ### Migrate items with priority #1 ### Watchfile: /site/modules/RockSettings/pageClasses/SettingsPage.php => RockSettings\SettingsPage::migrate() --- Trigger RockMigrations::migrationsDone This specific to site/migrate.php After moving my migrations to site/modules/Site.module.php, the priority is recognized ddev exec php site/modules/RockMigrations/migrate.php Refresh modules ##### SORTED WATCHLIST ##### Array ( [#1] => Array ( [0] => /var/www/html/site/modules/RockSettings/pageClasses/SettingsPage.php [1] => /var/www/html/site/modules/Site/Site.module.php ) ) ### Migrate items with priority #1 ### Watchfile: /site/modules/RockSettings/pageClasses/SettingsPage.php => RockSettings\SettingsPage::migrate() Watchfile: /site/modules/Site/Site.module.php ----- Migrate Module Site ----- Migrate Site --- Trigger RockMigrations::migrationsDone Also after moving the migrations to Site::migrateSettingsPage like mentioned in the docs. BTW the docs are currently not linked on https://www.baumrock.com/en/processwire/modules/rocksettings But even if tmy migration is run after the RockSettingsPage one, the 'inputfieldClass' => 'InputfieldCKEditor', takes no effect. If I set it manually via GUI, it gets reset to InputfieldTinyMCE after next migration. This is because inputfieldClass can't be overriden in template context. Before installing the TinyMCE fields in your RockSettingsPage, you could check if the module is available. If not, use InputfieldCKEditor. That would be great. I have sent you the adjusted site/modules/RockSettings/pageClasses/SettingsPage.php
    1 point
  6. Hey @gebeer thx a lot for your fix! Please download v2.2.2 ๐Ÿ™‚ The missing docs are also added to my website! ๐Ÿ˜Ž
    1 point
  7. Thanks for reporting this issue!! ๐Ÿ˜ƒ I have fixed this bug yesterday, but I have not bumped up the module version, because I am optimizing the code at the moment. So please replace the current file on Github with yours and everything should be fine. Best regards
    1 point
  8. In the last couple of weeks Iโ€™ve been to several cities in Spain, France and Italy. Iโ€™d never been to any of those countries before (or Europe for that matter), so it was an exciting trip. Though the goal was for my kids to broaden their horizons and experience other parts of the world, as well as spend time with my parents and family. We got back this week and have been recovering from jet lag (another thing Iโ€™d not experienced before). The 6 hour difference was no problem getting there, but coming back, itโ€™s a little harder to adjust! Next week I turn 50 years old (ugh), and then the following week Iโ€™m back in Europe again, except this time in the Netherlands on a bike trip with a client, and without my kids and parents. Iโ€™m not sure Iโ€™ll be able to do many core updates during the 10 day trip but do expect to have internet access this time, so will at least be online regularly and hope to be here in the forums. After that trip, I wonโ€™t be traveling again for a long time, and the focus will be on getting our next main/master version out. I noticed this week that @Robin S is now beating me as our most prolific module developer, with 72 modules! Great job and thanks for all the great modules Robin S.!
    1 point
  9. I'm pretty sure Ryan could arrange a trip around Europe without paying a cent for accommodation. Here are some photos of our little town: https://kepguru.hu/hatterkepek/cimke/tata just in case they start to think about such a trip :P
    1 point
  10. This week thereโ€™s new $pages->saveFields() and $page->saveFields() methods on the core dev branch. You might already be familiar with the $pages->saveField($page, $field); method which lets you save one field from a page, or $page->save($field); which does the same. This is useful when you only need to save one field from a page, rather than the entire page. Now we have a plural version of that method, which lets you specify multiple fields on a page to save: $pages->saveFields($page, [ 'title', 'body', 'summary' ]); Below is the same thing, but on a $page object, so you don't need to specify a $page argument: $page->saveFields([ 'title', 'body', 'summary' ]); You can also use a string if you prefer: $page->saveFields('title,body,summary'); In my case, I needed this method for a project I'm working on, and I also needed it to save without updating the 'modified' time or user, which you can achieve by specifying the 'quiet' argument. Though note, the 'quiet' argument is available for all the page saving methods and has been around a long time. But I'm not sure how widely used it is, so I'll mention it. $page->saveFields('title,body,summary', [ 'quiet' => true ]); This week the API methods for Select Options fields have also been updated to add more convenience for getting, adding and removing options to an existing selection. Let's say we have an Options field of checkboxes named "colors". Each selectable option has an ID, optional value, and title. Now you can get, add, or remove by any of those properties. Previously you had to work directly with SelectableOption instances, which wasn't as convenient. // add by title of option 'Blue' $page->colors->addByTitle('Blue'); // remove the option with value 'orange' from colors field $page->colors->removeByValue('orange'); // get SelectableOption instance of option with title 'Red' $red = $page->colors->getByTitle('Red'); echo "ID, value, title: $red->id, $red->value, $red->title"; // check if colors has an option with value 'purple' if($page->colors->hasValue('purple')) { // it has purple } The methods added to SelectableOptionArray (not yet reflected in linked docs page) include: getByID($id) getByValue($value) getByTitle($title) addByID($id) addByValue($value) addByTitle($title) removeByID($id) removeByValue($value) removeByTitle($title) That's all for this week. There likely won't be any core updates next week, as I'll be out of town again. Thanks for reading and I hope that you all have a great weekend and great week ahead.
    1 point
  11. Today while working on RockCalendar I had the need to change the database schema of my daterange fieldtime to add support for recurring events. I didn't know how to do it, so I had to do some research and that took quite some time. Here's the solution that I found in FieldtypeComments.module. When developing the fieldtype you can add a database schema like this and PW will take care of creating the table and columns for you: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; return $schema; } This is quite easy, but I wanted to add another column and tried this: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; return $schema; } No luck. You will get an error that column "bar" does not exist. Ok, so we have to modify the table somehow... But we also have to make sure that this is only done once. How to we do that? The solution is to save the schema version to the field and use that to compare versions and conditionally update the schema: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; $schemaVersion = (int) $field->get('schemaVersion'); $updateSchema = true; $table = $field->getTable(); $database = wire()->database; if ($schemaVersion < 1 && $updateSchema) { try { if (!$database->columnExists($table, 'bar')) { $database->query("ALTER TABLE `$table` ADD bar " . $schema['bar']); } $field->set('schemaVersion', 1); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } return $schema; } And maybe at a later point you want to add another column "baz": public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'timestamp NOT NULL'; // the from timestamp $schema['foo'] = 'timestamp NOT NULL'; $schema['bar'] = 'timestamp NOT NULL'; $schema['baz'] = 'timestamp NOT NULL'; $schemaVersion = (int) $field->get('schemaVersion'); $updateSchema = true; $table = $field->getTable(); $database = wire()->database; if ($schemaVersion < 1 && $updateSchema) { try { if (!$database->columnExists($table, 'bar')) { $database->query("ALTER TABLE `$table` ADD bar " . $schema['bar']); } $field->set('schemaVersion', 1); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } if ($schemaVersion < 2 && $updateSchema) { try { if (!$database->columnExists($table, 'baz')) { $database->query("ALTER TABLE `$table` ADD baz " . $schema['baz']); } $field->set('schemaVersion', 2); $field->save(); } catch (\Throwable $th) { $this->error($th->getMessage()); $updateSchema = false; } } return $schema; } ๐Ÿ˜Ž
    1 point
  12. That's a very good question ๐Ÿ˜… I think that should work as well. One benefit I see with the approach above is that we can define the $schema[] array in one place and then reuse this for the ALTER TABLE statement. Ok you could extract that array into a dedicated method as well, then you'd have basically the same with a Module::upgrade() approach. Another thing is that I find it easier to read like this: if($schemaVersion < 2) ... compared to this: if(version_compare($oldVersion, $newVersion) < 1) ... I never know which operator to use here ๐Ÿ™‚ And module versions can be confusing, as they can be integers like 104 or version strings like 1.0.4 And last but not least this approach is what I found in FieldtypeComments which is built by Ryan, which should be argument enough ๐Ÿ˜„ But thanks for the question! If you want to try another route please let us know what you find out ๐Ÿ™‚
    1 point
  13. Introduction Automated PDF production is a quite common task for ProcessWire developers. PW does not offer core tools for that. There are some external modules that we can utilize. In this showcase I would like to share my considerations for the tool of choice and my experiences working with it. The task at hand PDF production for product pages on an existing ProcessWire installation acniti.com is a Japan based company that specializes in nanotechnology solutions for gases in water. They have developed technologies to create nanobubbles in water, which can change the properties of water and improve dissolved gas levels through super saturation. Their founder and CEO has chosen ProcessWire for his website and has developed the site himself. He tasked me to add functionality for creation of downloadable PDFs from available product data. The client was forwarded to me from Bernhard because of time constraints on his side. I really appreciate that. The core requirements in detail: Produce separate PDF files for multiple languages, including LTR languages Design a template: cover page, images page, content pages, specs pages Cache PDFs Minimal impact on existing template code Tool choice: RockPdf module I had done some PDF generation before, using mPDF library directly. While there are other options available for ProcessWire (Pages2PDF, MakePDF) I decided to use Bernhard's RockPdf module since it seems the most feature rich and well maintained one. It is also based on the quite popular mPDF library. Reasons for choosing RockPdf Auto-reloading of PDFs in concert with RockFrontend This can save a lot of time (and strain on the F5 key), since the PDF is automatically reloaded every time a code change is made. All it requires to achieve that is one line of code: $pdf->save(__DIR__ . "/tmp/{$filename}.pdf", preview: true); Easy font loading I needed to load different fonts depending on the content language. Here is, how I did that RockPdf CSS utility classes They really came in handy, since they are specifically targeted at mPDF, using pt units. I could easily set some base colors with $pdf->setLessVars([ 'col-muted' => '#dddddd', 'col-primary' => '#0b54a2', ]); The utility classes that I mostly used are those for widths, margins and paddings. They are quite handy for setting up the layout of the PDF. Easy file saving to PW field For caching the created PDFs I utilized a RockPdf convenience method to save the files to a field on the product page $pagefile = $pdf->saveToField( page: $page, field: self::PDF_FIELD, filename: $filename, append: true, ); Implementation Modular approach for minimal impact on existing code I created two modules: Main module ProductPdf: non-autoload, holds all logic/templates for generating PDFs and required markup (download button) Module ProductPdfHooks: autoload, hooks: Page(template=product)::render displays PDF for download Pages::saved(template=product|product-type) creates PDFs in all languages and saves them to field for caching Re-usage of existing template logic There was quite a lot of logic (some of it rather complex) already available in the project that I wanted to utilize. Mainly for specs table creation. I had to do some minimal refactoring of original code. Then I was able to include that logic into my module without having to duplicate it. Benefits of this approach: Minimal impact on existing code Easier to maintain Challenges Limitations of mPDF library mPDF is not good at modern CSS features. It is quite limited in terms of CSS support. I had to do some workarounds to make it work with the layout I needed. Different approach to styling Although RockPdf's utility classes helped a lot, I still had to do some inline styling. Display of complex tables Display of tables in particular was a pain point since mPDF does a lot of automatic adjustments to column widths and distribution that I needed to disable in order to get the desired results: // Ensures that tables retain their original proportions $mpdf->keep_table_proportions = true; // And adding autosize="1" attribute to tables. Page headers, footers, margins and page breaks The RockPdf module docs have some great examples for setting up headers and footers, margins and page breaks. I used those to set up the layout of the PDF without having to read too much into the mPDF docs. Minimal impact on exisiting code base This was overcome by the modular approach I described earlier and it worked out really nice. The only addition to the original product template file for rendering the download button, was calling a static method from my module: <?= ProductPdf::renderDownloadbutton($page) ?> That button requests the page URL with a query parameter. The display of the PDF for download is handled through a Page::render hook PHP DOM manipulation of existing markup necessary Since I reused existing logic for constructing specs tables, I needed to add some inline styles and change some URLs on the fly. I used native PHP DOMDocument for that. There is a feature in the RockFrontend module that offers DOM manipulation tools with a nice API. I would have loved to use those but at the the time of working on this project, I just was not aware of their existence. The result Product pages on acniti.com now have a download button that allows the user to download the PDF of the product page in their language. See it live here The PDF is loaded from the cache field on the page, which is updated every time a product is edited and saved. If no cache file exists, the PDF is created on-the-fly and cached for future use. It is presented to the user in a new browser tab for viewing and downloading. The PDFs feature a clean layout / design which corresponds to the acniti branding. Cover page: Content pages: Specs table: Feedback from the client The client has a lot of experience with ProcessWire which one can see from looking at their website at acniti.com. He gave me great feedback on the project: Erik van Berkum, acniti LLC Lessons Learned and conclusion PDF creation in PHP is still not an easy task. The most popular library, mPDF, has some restrictions related to CSS that can make it tedious to work with. Especially when it comes to more complex layouts and tables. Using the RockPdf module for the task was a great choice. Its API is very well designed, offers a lot of conveniences and is well documented. Bernhard responded quickly to my questions and provided great support. In conclusion, the ProcessWire ecosystem offers great tooling for PDF creation that makes life for developers more enjoyable :-) Future considerations Would I use this approach again? Well, that depends mainly on the requirements. For this task the chosen tooling worked very well and I am happy with my choice. But for more complex layouts/designs that require modern CSS features, I would prefer rendering PDFs through Chromium browser using puppeteer or a self-hosted gotenberg service.
    1 point
  14. 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/
    1 point
  15. Oh excellent, thanks! Iโ€™ve been asking myself this, but been too lazy to actually figure it out instead of just changing the table manually for project-specific modules ๐Ÿ˜„
    1 point
  16. In case anyone else* (*so that would be the future me then, when I've forgotten o_O) wants to know how to do this, I worked it out, it's probably in the docs but for the life of me I could not find it (sorry doc writers). You have a hanna code called my_hanna_snippet and you want to output it from within a template, not from the Admin i/f. $hanna = $modules->get('TextformatterHannaCode'); echo $hanna->render("[[my_hanna_snippet]]"); Hope this helps someone/me in the future
    1 point
  17. Do you get paid for the sites that you would with PW? When you come to the forums to get help, do you limit your questions purely to development work that you are doing for free? I originally developed PW to help us all create better sites in less time, and with more fun. I'm hoping that PW is helping others to be more competitive in all ways, including financially. But recognize that PW did not come into existence on its own. Years worth of time and money has gone into making ProcessWire happen. If you are using ProcessWire to develop sites you get paid for, then you are profiting from ProcessWire. And that's fine with me, no ROI is expected or wanted--I've never asked anyone for anything. But it is disheartening to hear a user make a statement with the implications yours makes. Form Builder is not about making a profit. I don't expect that I will ever make enough on it to offset the actual time investment on it. My hope is that eventually it will be something where the community and myself have split the cost to create. If I wanted a profit, I would go make a Form Builder for WordPress or Drupal where the user base is large enough for that potential to exist. Form Builder is a tool that wouldn't exist if I had to fully self fund it. It's also an experiment to determine if I can reduce my client workload and substitute some of it with ProcessWire-related development that benefits all of us. But I can't substitute something that supports my family with something that doesn't. Form Builder is here to benefit you, not me. If you build sites for a living (or even a hobby) it's going to pay for itself the first time you use it. If you previously spent half a day building a form, now you can spend minutes and get a better, more secure and capable result that can do all sorts of things with the results it collects. Also want to note that Form Builder is something completely different from the original subject of this thread and I don't view them as similar products at all. Likewise, Form Builder is completely different from something like Zend Form or others like it. One does not preclude the use of the other and we should all keep more than one tool in our forms toolbox. I fully support Clinton's project and any others that benefit forms in ProcessWire. Forms are one of the most diverse and important aspects of web development. I feel very confident about the value of Form Builder in your toolbox, so have made it 100% refundable if you find it isn't for you (this type of return policy is pretty rare with digital products).
    1 point
  18. I think commercial modules are appropriate and should be encouraged. I suspect there are a large number of us that utilize Processwire (and other open source products) to make a living. I think collectively we are fortunate (and spoiled) by the myriad of software available at no cost. Processwire has made a massive impact on my productivity and ability to deliver complex products. If Ryan can develop some commercial modules that allow him to spend more time developing PW (and make a profit on all the work he's put into this truly amazing system), I am happy to support that effort โ€” and if there's a place to donate to the cause directly, I'll be glad to do that as well.
    1 point
ร—
ร—
  • Create New...