Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by psy

  1. Around 3yrs ago I got excited about Progressive Web Apps (PWAs) and found a site that magically created an offline experience with a few clicks. It worked until it didn't. Had to backtrack and remove the PWA code on lots of sites due mainly to exceeded offline cache storage causing problems. My JS skills weren't, and still aren't, up to the level of expertise needed to rectify. I still did the manifest.json thing, reduced the network load wherever possible and got some great Google Lighthouse performance results by tweaking PW especially with ProCache. 3yrs is a long time in web life. If 1 dog year = 7 human years, then 1 web year must be 10+ human years. I delved back into PWAs recently and Google Workbox in particular. The biggest hurdle I had to overcome was Google's own Workbox docs. Forget the "Import" statements and the CLI easy PWA method recommended in the docs. They don't (or I could not get them to) work with ProcessWire. They're geared to web apps like React that have a build process that publishes to a directory eg 'app'. ProcessWire doesn't work that way. So here's what worked for me: Create a template. I called mine "offline" that may only have one page, no children and does not have a trailing slash at the end of the URL. Create a page, add fields as necessary, eg 'body' and apply the 'offline' template and save as a child of "Home" Change the page url to 'offline.html' - this is the default page name for Workbox. Easier to change this than fiddle around overriding the Google Workbox defaults. For extra comfort, I made the page name 'Unique' and 'Hidden' from lists and searches. Ensure the workbox service worker javascript is NOT appended to the offline.html page On all other pages append the following to the <body> tag. I'm using Regions so this goes in my <region id="regFooterScripts"> in _main.php <?php if ($page->name != "offline.html") : ?> <script> if ('serviceWorker' in navigator) { let scrUrl = '<?=$pages->get('/')->httpUrl?>sw.js'; window.addEventListener('load', () => { navigator.serviceWorker.register(scrUrl, {scope: '/'}).then(registration => { console.log('Service Worker registered: ', registration) }).catch(registrationError => { console.log('Service Worker registration failed: ', registrationError) }) }) } </script> <?php endif; ?> Next, by hook or by crook, get the workbox library into a directory of your PW project. You can use the CLI 'copyLibraries' feature. See https://developers.google.com/web/tools/workbox/modules/workbox-cli for more info. Do NOT use any of the other CLI features! You can also download the library from gitHub. Didn't go there so please don't ask for help. I used the CLI method & saved the files to site/templates/scripts/workbox. Now you can start building your sw.js file which is saved in root directory. Here's mine using the Google Workbox docs & recipes. The big thing to remember is NOT to "Import" anything despite what the docs say. When you configure workbox to use your local copy of the library, all importing is done for you by using the 'workbox' namespace. importScripts('/site/templates/scripts/workbox/workbox-v6.0.2/workbox-sw.js'); workbox.setConfig({ modulePathPrefix: '/site/templates/scripts/workbox/workbox-v6.0.2/', globIgnores: ['processwire/**.*'] // change according to your PW admin URL }); // Include offline.html in the manifest workbox.precaching.precacheAndRoute([ {url: '/offline.html', revision: null } ]); workbox.routing.registerRoute( ({url}) => url.pathname.startsWith('/processwire/'), // change according to your PW admin URL new workbox.strategies.NetworkOnly() ); workbox.recipes.googleFontsCache(); workbox.recipes.pageCache(); workbox.recipes.staticResourceCache(); workbox.recipes.imageCache(); workbox.recipes.offlineFallback(); These are all the basic workbox features & recipes including offline.html and will probably need updating as time goes by. Up to you to add/modify/delete as you like and outside the scope of this tutorial. Good luck & have fun with Google Workbox PWAs in ProcessWire sites.
  2. You can use padding/margins to tweak but first define your rows and columns so that they overlap where required. In the example below I have a text block partially overlapping an image. On small screens, the text overlaps at the bottom and on larger screens, the text is on the left and overlaps the image on the right.
  3. Kevin Powell has a good tutorial using grid columns and rows to overlap content.
  4. psy

    Get ready to party!

    Lighthouse results for home page. Best practices & speed pulled down by annoying chat applet (forgot to mention that js - not my code or choice). Not sure of server spec. Shared hosting account. So, not under 100ms in this scenario but I'm happy when Google is happy πŸ™‚ Thanks for the tip. Will get image from client πŸ™‚
  5. psy

    Get ready to party!

    @flydev πŸ‘ŠπŸ» πŸ™‚ The home page has no caching. It's pure HTML & CSS including the gallery slider above the footer. CSS is such a powerful beast and HTML now has image lazy loading. The images in the slider, subsets of party packs, list of suburbs & featured customer love change on every page load which is a testament to the power & speed of PW
  6. psy

    Get ready to party!

    Thanks @flydev πŸ‘ŠπŸ» and I'll check what's happening in Safari (the new IE πŸ˜†) Edit: Scroll bar works on my MAC with Safari 14.0.1 so not sure why it's not working for you
  7. psy

    Get ready to party!

    Client happy with Happy Monkey πŸ˜‰
  8. psy

    Get ready to party!

    A little more to the back story... Client's biggest target audience is children's parties hence the look. The only time I said an absolute "NO" to client was when client said use "Comic Sans" font. There are some boundaries I will not overstep! Heading font is Google 'Happy Monkey'. 🀣
  9. This site has been a long time in the making. Back story: Was first developed in PW 2.something many years ago. Around 5yrs ago, it underwent a major upgrade. The look was very 90's and the booking page had around 50 input fields on the one page. See screenshots. Original dev, who did a great job with web technology at the time, went AWOL Business was sold to a new owner New owner engaged me to revamp the site. I laugh & laugh & laugh. If only I knew then what I know now! It was crucial that the years of historical data be kept and accessible Busy period is November to February (Aussie summer). And then came COVID. Victoria was the state hardest hit with C19 in Australia and lockdowns were harsh. Definitely no parties. The possible product configurations, hire rates & rules (all with 'except for this one') had me melt into a puddle at times. Pricing is shown on product pages only when a valid venue postcode & event date entered and can change depending on the surface type (eg concrete attracts a $50 surcharge) One of the biggest things that tripped me up was the previous dev combined data/module logic with presentation HTML. I had to rewrite all the old code to adapt to the new layout. Would have been much easier to have logic & presentation separated. A while ago I chose to stop using CSS frameworks to dig deep into how CSS really works. Much of what I have learnt is in the site frontend code. The site is fully responsive and the only JS used is on the View Booking page when a customer alters the booking item qty. There is NO frontend presentation JS, including the dropdown & mobile menus. Where it stands today: There are still some things to do, including implementing a very sensible change recommended by @ryan. In addition to the frontend, there are a number of new backend/admin reports Victoria is coming out of C19 hell and getting back to normal The site is live and online bookings are rolling in New modules added: ProCache FormBuilder + StripePayment + Page Break CampaignMonitorAPI ProFields - Functional Fields ProFields - Repeater Matrix RockMarkup2 Database Backups Upgrades SeoMaestro TracyDebugger Page View Statistic Big shout out 'Thank you' to all the PW devs who responded when I reached out for help in the forum. Check out the new look www.aaronsamusements.com.au
  10. @PCuser Glad to help. Suggest editing your original post and inserting [solved] to the start of the title 😊
  11. @PCuser there many ways to sanitise data using $sanitizer, eg pageName, digits, text, camelCase, etc, which is why you need to let $sanitizer know which method to use. They're all listed in the documentation. 😊
  12. @PCuser you need to let Sanitizer know what kind of clean-up to do, eg: $sanitizer->pageName($sportspage->title) will convert the title text to lowercase and replace spaces with hyphens to make it suitable for use in a URL (or HTML id attribute). See what other $sanitizer methods are available at https://processwire.com/api/ref/sanitizer/ PS: Page titles can change and also not be unique. I prefer to use a letter first (HTML ids must start with a letter) then use the page ID, eg s1234. Alternatively, you could use the existing page name rather than the title.
  13. @joe_ma Maybe something like (untested): <?php $content .= '<main class="front">'; // Container for circles $content .= '<ul class="topCircles" role="navigation">'; // nav list // Output of 16 li elements with links at the chosen postions $items = $page->children("limit=16, sort=sort"); // PageArray limited to 16 items. Sorted according to template/page children settings foreach ($items as $item) { if ($item->front_position == $item->index() { $content .= '<a href="' . $item->url . '">' . $item->title . '</a>'; } $content .= '</li>'; } $content .= '</ul> </main>'; // Ende Liste und Container From the PW API Docs: https://processwire.com/api/ref/page/index/
  14. @joe_ma not sure if it's the issue but seems to me you're using a PHP array functions on a ProcessWire PageArray object - different beasts Is not a PHP array. It is a ProcessWire PageArray.
  15. Those subpages are now linked to your home page and you change your home page template to show what fields you want. For example, if your page reference field is called 'featured', in your home page template you can choose what fields you need to show from the subpage. <?php foreach ($page->featured as $apartment) : if ($page->featured->first->id == $apartment->id) : // it's the first one ?> <div class='featured first'> <h2><?=$apartment->title?></h2> <p>Bedrooms: <?=$apartment->bedrooms?></p> <p>Bathrooms: <?=$apartment->bathrooms?></p> <p>Price: <?=$apartment->price?></p> </div> <?php else: // it's not the first one ?> <div class='featured'> <h3><?=$apartment->title?></h3> <p>Bedrooms: <?=$apartment->bedrooms?></p> <p>Bathrooms: <?=$apartment->bathrooms?></p> <p>Price: <?=$apartment->price?></p> </div> <?php endif; endforeach; ?>
  16. Hi Brandy and welcome to ProcessWire There are many ways to achieve what you want. Hardest part is deciding which method works best for you and you client, eg: On your home page template you could have a Page reference field that allows your client to choose the featured projects and make the first the "main" project. For output, loop through the chosen page references and ensure the main project (page reference Project #1), gets different, more prominent styling. Use Tags on Project pages, eg "Home page featured" & "Home page other". Then in your home page template, use a selector to get the featured projects and loop through as above Based on info you supplied, I'd go with option 1 Hope this helps
  17. Just tested and it works a treat πŸ™‚ When a frontend user enters their event date, in _init.php: <?php if (!empty($input->post->availability)) { $availability = $sanitizer->date($input->post->availability, 'Y-m-d'); $sameDayBookings = $pages->find("template=booking, booking_date=$availability, booking_status!=pending|cancelled",['loadOptions'=>['autojoin' => true, 'joinFields' => ['booking_items']]]); $cache->save('sameDayBookings', $sameDayBookings); } This query takes no time at all. Next, in my custom module: <?php /** * @param Page $item - product page * @return int|void * @throws WireException */ public function itemAvailability(Page $item) { if ($item instanceof NullPage) return; $itemBookingCount = 0; $otherBookings = $this->wire('cache')->get('sameDayBookings'); if ($otherBookings->count > 0) { // there are other booking on the same event date foreach ($otherBookings as $otherBooking) { foreach ($otherBooking->booking_items as $otherBookingItem) { if ($otherBookingItem->booking_item->id == $item->id) { $itemBookingCount = (int)$itemBookingCount + (int)$otherBookingItem->booking_qty; } } } } $itemsAvailable = (int)$item->qty - (int)$itemBookingCount; return $itemsAvailable; } Query times for this function went from an average of 3.5sec to under 0.01 seconds per item and under 1sec to render the page listing multiple items with their availability. Happy dance time! Thank you all!
  18. Grateful for all your suggestions and you gave me an idea. Didn't get a chance to test today but will try tomorrow. It's a conglomeration of your suggestions. in _init.php prepended to all pages (or maybe refined to only the pages on which it's needed ?), collect all bookings for the day, regardless of individual item ids and cache it. Historical bookings won't change and without the repeater stuff, will be quick; instead of going back to the db for all calls, run the individual item queries on the cached page array - at most 20 items Will report back πŸ™‚
  19. @dragan No question you ask is ever dumb! I should have made it clearer. Each item is a page, a product page. The qty is an integer field and refers to how many units of that product are owned by the client. The $limit is set to that number. The query searches through other bookings for that item on the nominated day, and on each match, subtracts the booking_qty from the item qty, to return how many are left for hire on that date. When all are booked out, the front-end customer cannot add it to their cart to prevent over-booking. A count wont do it as other bookings may have booked 2 or more units of the same item on that day.
  20. @BillH Thanks, I tried RockFinder3 a while ago. It's certainly a great module however I couldn't figure out how to get it working with Repeater fields. It's perfect with direct relation fields, eg PageReference but Repeaters are different beasts. Not sure if RF3 handles '.' separated fields in the selector, eg "booking_items.booking_item.id" either?
  21. I have a $pages selector that works great in giving me the requested info. However it's incredibly slow. Added to this, the request may be called up to 15 times on a particular page for 15 different listed items. <?php // in my custom module... // retrieves qty of items already hired on a particular day to determine availability for new bookings // booking_items is a repeater field and booking_item are repeater pages // I already have the $item and the $eventDate // Query is searching through 5K+ bookings and at least 3x that many booking_item repeater pages $limit = $item->qty; $selector = "template=booking, booking_items.booking_item.id=$item, booking_date=$eventDate, booking_status!=pending|cancelled, limit=$limit"; $pp = $this->wire()->pages; $otherBookings = $pp->find($selector); Each item query can take between 2 to 8 seconds 😬 What can I do to speed up this query? TIA psy
  22. Had an extreme example to deal with recently. Multiple field replacements for various fieldtypes. May not be the best approach but worked for me. Private function is in a custom module function. <?php /** * Takes a page field, eg 'body' as a template and replaces tags eg {age} with the same field value * from the supplied data page * @param $tplPage * @param $tplField * @param $dataPage * @param string $startTag * @param string $endTag * @param array $other * @return string|string[] * @throws WireException * @throws WirePermissionException */ private function _compileFieldTags ($tplPage, $tplField, $dataPage, $startTag = '{', $endTag = '}', $formatDate = false, $other = []) { $allowedFieldtypes = [ 'FieldtypeText', 'FieldtypeTextarea', 'FieldtypeInteger', 'FieldtypeFloat', 'FieldtypeDatetime', 'FieldtypeToggle', 'FieldtypeCheckbox', 'FieldtypePage' ]; $replacementNames = []; $replacementValues = []; // Sort out what to do with each inputfield type foreach ($dataPage->fieldgroup as $replacement) { if (!in_array($replacement->type, $allowedFieldtypes)) continue; switch ($replacement->type) { case 'FieldtypeDatetime': $fldData = $formatDate == false ? $dataPage->$replacement : $dataPage->getFormatted($replacement); break; case 'FieldtypePage': $fldData = $dataPage->$replacement->title; break; case 'FieldtypeCheckbox': $fldData = $dataPage->$replacement == true ? "Yes" : "No"; break; default: $fldData = $dataPage->$replacement; break; } $replacementNames[] = $replacement->name; $replacementValues[] = $fldData; } // Prepare replacement arrays foreach ($dataPage as $k => $v) { $replacementNames[] = $startTag . $k . $endTag; $replacementValues[] = $v; } $replacementNames['url'] = $startTag . 'url' . $endTag; $replacementValues[] = $dataPage->httpUrl; $result = str_ireplace($replacementNames, $replacementValues, $tplPage->$tplField); return $result; }
  23. Trying to upgrade TD to the latest version and I get this: Fatal Error: Uncaught WireException: No download URL specified in wire/modules/Process/ProcessModule/ProcessModule.module:1072 Happens both on the Modules->New and Settings->Upgrades with the upgrade module. I could upload the zip but that's not the issue. Reason for upgrade was another error message: Fatal error: Uncaught Exception: Serialization of 'Closure' is not allowed in [no active file]:0 Stack trace: #0 {main} thrown in [no active file] on line 0 which auto-magically began appearing. I'm not serializing any data and the error disappears when I turn off TD. Any ideas on what's happening here?
  24. @Knubbi that's a question for Ryan in the ProDrafts forum πŸ™‚
  25. If it's only content, there's also https://modules.processwire.com/modules/version-control/
  • Create New...