Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


schwarzdesign last won the day on June 6

schwarzdesign had the most liked content!

Community Reputation

112 Excellent

About schwarzdesign

  • Rank
    Full Member

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Hi @BitPoet, wow, thanks for that great mini-tutorial! I haven't used the JSON data type at all yet, but this looks super interesting. Probably a bit overkill for my current project, but that is definitely something that would be worth considering for more individual searching requirements, or possibly a search module. I'll keep that in mind 🙂
  2. I'm working on a search page for a complex site where the search term is searched for in a couple dozen fields, including fields nested inside repeaters and repeater matrixes. While it's easy to get a list of pages that contain the search term somewhere, the result doesn't include the information which field matched the search term. I was hoping that there's an easy way to find out which field matched the find query. Of course, I could iterate over all fields that I'm searching through and check whether they match the search term with $page->matches (something like the solution I posted here), but is there an easier way?
  3. Thanks, but I don't think hooking after the page render will work, since I want to conditionally disable the render cache, so at this point it's too late ..
  4. I just spent some time trying to get a hook in a site/init.php file to work. What I wanted to do was disable the template/page cache based on the value of some field of the current page. I tried following @Soma's example here: Unfortunately, this doesn't work at all for me, since the hook gets called with no arguments. I tried this: wire()->addHookBefore('Page::render', function (HookEvent $event) { bd($event->arguments(0)); bd($event->arguments(1)); }); But both just evaluate to null. So I dug deep to find the PageRender::renderPage method and tried to go through there. I finally got it to work using this rather obscure syntax: wire()->addHookBefore('PageRender::renderPage', function (HookEvent $event) { $page = $event->arguments(0)->object; $disableCache = $page && $page->hasField('cta_display'); if ($isValidPage) { $event->arguments(0)->setArgument(0, ['allowCache' => false]); } }); I'd like to know why the Page::render hook doesn't receive any arguments? Was there some change regarding this since Soma's post? Since the version linked above doesn't work at all for me. What would be the 'correct' way to accomplish this? I'd appreciate any insights. Thanks! ProcessWire Version: 3.0.130 PHP Version: 7.2
  5. @dragan Referenced to the page that is being searched. I think Robin's explanation is pretty accurate. For example, I have five service pages, but only three of them are referenced from different pages (in a page reference field). I want to write a query that will retrieve those three services; the problem here is that numReferences is not a real field, but a computed property. @Robin S Thanks for the explanation! That's what I assumed - I guess it would be possible with a subquery, but I agree that the incurred performance hit would make it a bad tradeoff for selectors. Your module looks ingenious, I'll try it out for my usecase!
  6. ProcessWire added the numReferences property in 3.0.107, which returns the count of all pages having a reference to the page object. However, apparently it's not possible to use this property inside a selector. I'm trying to find all pages that are referenced at least once: $pages->find('template=service, numReferences>0'); This throws an error: "Field does not exist: numReferences". Is there another way to filter by the number of references? Of course, I could manually filter the results of the find query, but that feels overly complicated. Ideally, I would also like to filter the number of references from a specific field; that is, find all pages that are referenced at least once in one specific page reference field. Is there a way to do this? I guess supporting numReferences in selectors would be a feature request - if so, is it feasible? Thanks!
  7. @dragan Thanks for the tip, I fixed this alongside with a couple of other issues. The tool was throwing JavaScript errors that I couldn't figure out. For some reason one of my non-Vue components wasn't being transpiled by babel at all, so Google had trouble with the syntax. I have now rewritten the component as a native Vue SFC and added some more polyfills. Now it's finally working for me, and the mobile-friendly checker shows the rendered page correctly ... Hopefully it will improve the indexing situation 🙂
  8. @charger Oh I see 🙂 Well, frontend routing is completely handled by Vue, so there are no existing routes (as in, provided by ProcessWire). The only routes ProcessWire knows about are the different API endpoints. One of the screenshots in my post shows the URL segments configuration for the API template. Besides that, @dragan's explanation is spot on; since there are only a handful of API endpoints, those are configured manually. It's not inside the vue-router, since that one is only concerned with routes the visitor sees in their browser; I just wrote a little API class that takes a list of endpoints and made an instance available globally through the Vue prototype. For reference, this is the original unminified code with the endpoint definitions: export const api = new Connector( `https://architekturfuehrer.koeln/api/v${config.API_VERSION}/`, { objects: { path: 'objects', }, object: { path: 'objects/{identifier}', }, taxonomies: { path: 'taxonomies', }, walks: { path: 'walks', }, pages: { path: 'pages', } } ); As for the frontend routes, they are also configured natively inside the Vue app, here's an excerpt: import ObjectList from './pages/ObjectList.vue'; import SingleObject from './pages/SingleObject.vue'; export default new VueRouter({ // ... routes: [ { name: 'objects', path: '/objekte/:taxonomy([-_.a-zA-Z0-9]+)?/:term([-_.a-zA-Z0-9]+)?', component: ObjectList, }, { name: 'object', path: '/objekt/:object([-_.a-zA-Z0-9]+)', component: SingleObject, } }, // ... ], // ... });
  9. I just published another update that makes full use of the Add to Home Screen functionality in Chrome (though the app is installable in Android Chrome, Android Firefox, iOS Safari and regular Chrome). In particular, there's a new page that explains how to install the PWA on different devices, with a button that triggers the system dialogue for installing the PWA on supported browsers (only Chrome at the moment). This was a bit difficult to implement since there's little common ground between browsers in this regard at the moment, so let me know if you find any bugs or other errors! @charger ProcessWire only handles specific paths, everything else is routed to the frontend app and handled by the router. A normal ProcessWire installation uses the .htaccess rules to redirect all requests to it's index.php file. Meanwhile, the Vue SPA uses one single index.html as it's entry point and performs all further routing inside the visitor's browser. Since I want most visitors and paths to go to the frontend app, I modified the .htaccess file ProcessWire comes with to only forward specific routes to ProcessWire, and route all other requests to the Vue app: # .htaccess # Requests to the root domain without a path should go to the Vue app (index.html) instead of ProcessWire (index.php) DirectoryIndex index.html index.php index.htm # This additional RewriteCond comes right before the main RewriteRule from ProcessWire # It lets ProcessWire handle only calls to specific paths (in this case, the admin url (/cms), the API endpoint and the sitemap which is generated server-side) RewriteCond %{REQUEST_URI} (^|/)(api|cms|sitemap) RewriteRule ^(.*)$ index.php?it=$1 [L,QSA] # Everything else is routed to index.html, so the Vue app will receive the request and the vue-router can show the appropriate page # Redirect index.html to / RewriteRule ^index\.html$ / [L,R=301] # Redirect everything else to the app FallbackResource /index.html
  10. @Moebius @dragan @bernhard Well, Google's crawlers can run JS, they even updated the Chromium version their crawlers run on a couple of weeks back, so it should have no problem with the app. architekturfuehrer.koeln ist also an entirely new domain we set up for the app, so there was nothing in the index to begin with. We'll see how the coverage is progressing going forward. I did built a sitemap to help with indexing the site by the way: https://architekturfuehrer.koeln/sitemap. This one is created server-side, though ...
  11. Thanks for the feedback everyone, I just made some updates to the app! You may have to fully clear the cache to see the changes immediately, as with the service worker the caching is pretty aggressive. @bernhard I have tweaked the functionality of the back button. The app now keeps track of the page history independently of the browser history API and only displays the back button if there are at least to items in the history stack. So you shouldn't see the back button on the first page visit, regardless of which route you're on. @tpr @bernhard For now, I have just disabled scroll wheel zoom for most of the embedded maps; this way, the map won't interfere with the normal page scrolling. The map can still be zoomed with the buttons to the left, and scrool zooming activates after clicking inside the map. Pinch zooming on touch devices should also work as normal. The only exception is the map page (architekturfuehrer.koeln/karte) which can always be zoomed with the scroll wheel; but that page should always fill the entire viewport with nothing to scroll below that, so it shouldn't be an issue there.
  12. @tpr Thanks! I see your point; I recently wrote a little snippet for leaflet to only activate mouse scrolling after clicking anywhere inside the map, maybe that would make sense here too. I'll put in on the to do list!
  13. @bernhard It's a little trick you can do with Google Chrome: In the responsive view mode, select one of the iPhone models in the responsive mode settings bar. In the flyout menu on the upper right, there's an option "show device frame". Only works with some of the devices, I've used "iPhone 6/7/8 Plus". If you take a screenshot (via the same flyout menu) while in responsive design mode with the show device frame option turned on, the device frame will be included in the screenshot.
  14. @netcarver Thanks! @bernhard Thank you for the QA 😀 Regarding the "back" button, I tried to make it more intelligent, but I got stuck on the history API not having a reliable way to check the last items. I thought about turning it off only on the homepage, but I wanted to have a way to go back to the last page if you go e.g. from an object detail page to the homepage and want back to the object. I'd have to track the last history states manually, since the Vue Router just wraps around the history API and doesn't keep a memory of itself. I'll put that back on the to do list! As for the share button, we only have that on the object pages in the subnavigation; it would take up to much space too have it on the bottom all the time. Maybe we can put it at the end of every page, or in the navigation. Though it's only supported in Chrome on Android and Safari at the moment, and most browsers have their own native sharing button, so it wasn't the most important feature to us. @pwired Thank you so much! 🙂
  15. We recently rebuilt the Architekturführer Köln (architectural guide Cologne) as a mobile-first JavaScript web app, powered by VueJS in the frontend and ProcessWire in the backend. Concept, design and implementation by schwarzdesign! The Architekturführer Köln is a guidebook and now a web application about architectural highlights in Cologne, Germany. It contains detailled information about around 100 objects (architectural landmarks) in Cologne. The web app offers multiple ways to search through all available objects, including: An interactive live map A list of object near the user's location Filtering based on architect, district and category Favourites saved by the user The frontend is written entirely in JavaScript, with the data coming from a ProcessWire-powered API-first backend. Frontend The app is built with the Vue framework and compiled with Webpack 4. As a learning exercise and for greater customizability we opted to not use Vue CLI, and instead wrote our own Webpack config with individually defined dependencies. The site is a SPA (Single Page Application), which means all internal links are intercepted by the Vue app and the corresponding routes (pages) are generated by the framework directly in the browser, using data retrieved from the API. It's also a PWA (Progressive Web App), the main feature of which is that you can install it to your home screen on your phone and launch it from there like a regular app. It also includes a service worker which catches requests to the API and returns cached responses when the network is not available. The Architekturführer is supposed to be taken with you on a walk through the city, and will keep working even if you are completely offline. Notable mentions from the tech stack: Vue Vue Router for the SPA functionality VueX for state management and storage / caching of the data returned through the API Leaflet (with Mapbox tiles) for the interactive maps Webpack 4 for compilation of the app into a single distributable Babel for transpilation of ES6+ SASS & PostCSS with Autoprefixer as a convenience for SASS in SFCs Google Workbox to generate the service worker instead of writing lots of boilerplate code Bootstrap 4 is barely used here, but we still included it's reboot and grid system Backend The ProcessWire backend is API-only, there are no server-side rendered templates, which means the only PHP template is the one used for the API. For this API, we used a single content type (template) with a couple of pre-defined endpoints (url segments); most importantly we built entdpoints to get a list of all objects (either including the full data, or only the data necessary to show teaser tiles), as well as individual objects and taxonomies. The API template which acts as a controller contains all the necessary switches and selectors to serve the correct response in <100 lines of code. Since we wanted some flexibility regarding the format in which different fields were transmitted over the api, we wrote a function to extract arbitrary page fields from ProcessWire pages and return them as serializable standard objects. There's also a function that takes a Pageimage object, creates multiple variants in different sizes and returns an object containing their base path and an array of variants (identified by their basename and width). We use that one to generate responsive images in the frontend. Check out the code for both functions in this gist. We used native ProcessWire data wherever possible, so as to not duplicate that work in the frontend app. For example: Page names from the backend translate to URLs in the frontend in the form of route parameters for the Vue Router Page IDs from ProcessWire are included in the API responses, we use those to identify objects across the app, for example to store the user's favourites, and as render keys for object lists Taxonomies have their own API endpoints, and objects contain their taxonomies only as IDs (in the same way ProcessWire uses Page References) Finally, the raw JSON data is cached using the cache API and this handy trick by @LostKobrakai to store raw JSON strings over the cache API. Screenshots
  • Create New...