Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 06/26/2022 in all areas

  1. This week core updates are focused on resolving issue reports. Nearly all of the 10 commits this week resolve one issue or another. Though all are minor, so I'm not bumping the version number just yet, as I'd like to get a little more in the core before bumping the version to 3.0.202. This week I've also continued development on this WP-to-PW site conversion. On this site hundreds of pages are used to represent certain types of vacations, each with a lot of details and fields. Several pages in the site let you list, search and filter these things. When rendering a list of these (which a lot of pages do), it might list 10, 20, 100 or more of them at once on a page (which is to say, there can be a few, or there can be a lot). Each of the items has a lot of markup, compiled from about a dozen fields in each list item. They are kind of expensive to render in terms of time, so caching comes to mind. These pages aren't good candidates for full-page caches (like ProCache, etc.) since they will typically be unique according to a user's query and filters. So using the $cache API var seems like an obvious choice (or MarkupCache). But I didn't really want to spawn a new DB query for each item (as there might be hundreds), plus I had a specific need for when the cache should be reset — I needed it to re-create the cache for each rendered item whenever the cache for it was older than the last modified date of the page it represents. There's a really simple way to do this and it makes a huge difference in performance (for this case at least). Here's a quick recipe for how to make this sort of rendering very fast. But first, let's take a look at the uncached version: // find items matching query $items = $pages->find('...'); // iterate and render each item foreach($items as $item) { echo " <!-- expensive to render markup here ---> <div class='item'> <a href='$item->url'>$item->title</a> ...and so on... </div> "; } That looks simple, but what you don't see is everything that goes in the <div class="item">...</div> which is a lot more than what you see here. (If we were to take the above code literally, just outputting url and title, then there would be little point in caching.) But within each .item element more than a dozen fields are being accessed and output, including images, repeatable items, etc. It takes some time to render. When there's 100 or more of them to render at once, it literally takes 5 seconds. But after adding caching to it, now the same thing takes under 100 milliseconds. Here's the same code as above, but hundreds of times faster, thanks to a little caching: // determine where we want to store cache files for each list item $cachePath = $config->paths->cache . 'my-list-items/'; if(!is_dir($cachePath)) wireMkdir($cachePath); // find items matching query $items = $pages->find('...'); // iterate and render each item foreach($items as $item) { $file = $cachePath . "$item->id.html"; // item's cache file if(file_exists($file) && filemtime($file) > $page->modified) { // cache is newer than page's last mod time, so use the cache echo file_get_contents($file); continue; } $out = " <!-- expensive to render markup here ---> <div class='item'> <a href='$item->url'>$item->title</a> ...and so on... </div> "; echo $out; // save item to cache file so we can use it next time file_put_contents($file, $out, LOCK_EX); } This is a kind of cache that rarely needs to be completely cleared because each item in the cache stays consistent with the modification time of the page it represents. But at least during development, we'll need to occasionally clear all of the items in the cache when we make changes to the markup used for each item. So it's good to have a simple option to clear the cache. In this case, I just have it display a "clear cache" link before or after the list, and it only appears to the superuser: if($user->isSuperuser()) { if($input->get('clear')) wireRmdir($cachePath, true); echo "<a href='./?clear=1'>Clear cache</a>"; } I found this simple cache solution to be very effective and efficient on this site. I'll probably add a file() method to the $cache API var that does the same thing. But as you can see, it's hardly necessary since this sort of cache can be implemented so easily on its own, just using plain PHP file functions. Give it a try sometime if you haven't already. Thanks for reading and have a great weekend!
    3 points
  2. I looked at my HTML output today and all this chaotic whitespace triggered my OCD. This module simply hooks into Page::render and removes whitespaces from frontend HTML with a simple regex replace. I mostly put this together for cosmetics. I like my View source neat and tidy. This is not well tested yet, please open an issue if you run into problems. GitHub: https://github.com/timohausmann/MinifyPageRender
    1 point
  3. @joe_g That's right, it doesn't look like there's any reason to provide #maincontent for your situation. When populating markup regions, specify just the region you want to replace, append, prepend, or delete; not a structure of markup regions. Though you did have that case above where you were trying to a direct child to it: <div class="p-8"> outside of your _main.php, so in that case you would, or you could do a <div class="p-8" pw-append="maincontent">...</div> or <div id="maincontent"><div class="p-8">...</div></div>. I recommend adding <!--PW-REGION-DEBUG--> somewhere in your _main.php markup (like at the bottom), and that'll be automatically replaced with some markup that tells you everything your Markup Regions are doing and when it can't find where to place something, etc. Most of my sites have something like this below, and it's handy to look at when you aren't sure why something isn't working, etc. <?php if($config->debug && $user->name === 'ryan'): <div class"uk-container uk-margin"> <!--PW-REGION-DEBUG--> </div> <?php endif; ?> Another tip is that if you ever find Markup Regions are having trouble finding the closing tag for some specific element, you can tell it specially by marking it with a hint comment <!--#maincontent-->. Though I also just find it visually helpful, so I tend to do it whether for markup regions or not. <div id="maincontent"> ... </div><!--#maincontent-->
    1 point
  4. Thanks for getting back about this! Here is a test (using tailwind css). _main.php (global) <body> <div id="maincontent"> </div> </body> _testregion.php (appended to template 'test' under files tab) <div id="testregion" class="border border-red-600"> default value from _testregion.php </div> test.php (actual PW template) - note the nested divs, how testregion is inside maincontent. <div id='maincontent'> <div class="p-8"> <div id="testregion"> should be inside red border? </div> </div> </div> What I'm trying to do is to apply my own "markup pattern" called testregion to an arbitrary position, but it doesn't do what I expect. Instead of getting "Should be inside red border" inside the red boder, i get _testregion.php appended after the output of _main.php. The result looks like this: However, it does work if I specify the innermost region in test.php, like <div id="testregion"> should be inside red border? </div> That means i can't match arbitrary regions that are inside each other, it would always have to be the innermost region(s)? This means that the site structure (the markup hierarchy) has to be entirely defined inside _main and I can never specify some markup that is simultaneously a parent and child. Maybe this is a good thing, I'm not sure. But this is what got me before.
    1 point
  5. Hi @bernhard Impressive project. It's a little jealous that I can't do that yet. Tell me please, why did you use RockForms here when you have the PW Form Builder?
    1 point
  6. @joe_g No, you can specify this for each template if you want to. Edit a template and see the "files" tab. Or you could just use your _main.php as a controller that loads different layout files depending on some field value or condition that you determine. Sure you can. Use PHP include(), $files->include() or $files->render() with whatever files or order you want. Regions can be replaced, appended, prepended, removed, etc. as many times as they are output. You can do it individually for templates, see my response for #1. Though I usually structure my templates in a way that the _main.php is the "base" that's generic enough to not have ever needed to define alternate files. I think markup regions are the best way myself. Once you start using them a lot, I suspect you'll find you don't want to use anything else. That's been my experience at least. I don't know exactly what you mean by nested regions. You'd have to provide more details on what you are doing before we could say if you are using something in an intended way or not.
    1 point
  7. I've also been using this great module on another WP conversion project to do the same thing: https://processwire.com/modules/custom-paths/ I'm not 100% sure if it checks for uniqueness (probably) but since everything's being imported by a script I wrote I know they are unique for now.
    1 point
  8. I am still working on this one. It works great and is quite flexible now. I made a couple of improvements. Now the data to style the items is stored directly on the item with $page->meta(). This way, cloning pages and page reference fields work out of the box. Also there is no need for hidden fields anymore. The style panel now supports adding custom classes and assigning styles to them. These classes can be used globally on all pages (a css class is also a page). The style panel now supports selecting html tags to style tags globally across the whole site. I did a lot of refactoring to make the code cleaner and more efficient. There are still things where I am looking for feedback before releasing an open beta for this module: Sharing blocks and migrating pages I played with the core ProcessPagesExportImport and the migrator module and it seems that it's not exporting the meta() data, which is a bummer. So for now I might not be able to have a migration feature out of the box. Maybe someone else can take a look at this, but it seems to be too much hassle for me now. Importing/exporting the whole database with ProcessDatabaseBackups works as expected though. Also I did develop some block modules that you can install optionally, these will create the needed templates and fields for that block and give you a quick way to start working with PageGrid (without the need to create your own templates or install a site profile), more on that later. (Item pages are based on a block template). Structure of PageGrid items/pages I decided to always store the items under the admin page in a seperate parent, instead of making the parent customisable (like PageTable is doing). So there is a clear separation between pages for your site and item pages. PageGrid then creates a structure looking like this (makes it easy to access the pages): Admin – PageGrid Items – Home items (name: for-1213) – Item Editor – Item XY – Another Page items (name: for-1315) – Item Group – Item XY – Item XY – Item Editor The structure of these pages is an exact representation of the PageGrid items/pages (sorting, child/parent relation, hidden/published). While working with PageGrid via the UI this is not so important, but I think it will make interacting with the item pages via API more flexible and clean. Adding a page to any of the item pages via API or the backend also adds the page to PageGrid. If you delete a page containing a PageGrid field, the items also get deleted. You can also sort via API (or manually under the Admin page) and the changes are reflected for the items as well. The connection is via a name like for-1213, where 1213 is the ID of the page holding the PageGrid field (inspired by repeaters). I like this setup because it gives you a lot of options to access items via API and keeps the pagetree clean. Also it will make it easier to handle migrations for pages containing a PageGrid field in the future (just import/export the page and the item page container). One downside, though, is that the methods of PageTable like $page->pt->add($newpage) or $page->pt->remove($newpage) are no longer working (PageTable is using a PageArray that is saved as the field value). Also this will not work with multiple PagGrid fields on one page (A PageGrid field can however have a page reference field to another page containing a PageGrid field, as well as having nested pages, so I see no benefit of supporting multiple fields). You can add a page to the item parent like this: // Add an PageGrid item via API $itemParent = $pages->get("for-$page->id"); $itemParent->add($newpage) I am not decided if my approach is better or if I should just stick with the PageTable methods (keeping the items stored as PageArray inside the field). Since PageTable can only have one parent that you select in the field settings, all items will than be stored under the same parent, which is a bit messy. Not sure if any of that makes sense to you ? Any feedback is welcome. More info and website is coming soon!
    1 point
  9. When we released ProcessWire 3.0.200 one of the biggest differences from prior master/main versions was that PW now only includes 1 profile: the site-blank profile. This means there's probably going to be more people starting with it than before. Yet it is so minimal that I thought it deserved a bit of a walkthrough in how you take it from nearly-nothing to something you can start to build a site around. Everyone will have a little bit different approach here, which is one reason why the blank profile is a useful starting point. In this new post, here are some steps you might take when starting a new site with the blank profile— https://processwire.com/blog/posts/starting-with-the-blank-profile/
    1 point
  10. Because the projects I did lately didn't need forms or needed some more complex ones and I used FormBuilder for that. I find nette forms a little complicated to understand and modify. The PW forms api is a lot cleaner (at least to me, being familiar with PW). Other solutions are: Custom HTML/PHP Forms Forms API FormBuilder (commercial module)
    1 point
  11. Hi @fliwire, welcome to the forum. This module is intended as proof-of-concept or for testing purposes of experienced developers and not for production use. I'm not using it any more, so I'd recommend you look for another solution (we have some, see the "how to search" link in my signature). Thx
    1 point
  12. If anybody might wonder. This is how to setup permissions for a sub-page of a processmodule: As easy as adding the permission to the nav item! If you want the permission to be created/deleted on module install/uninstall you also have to add it in the "permissions" array: $info = [ 'title' => 'ProcessProjects', 'summary' => 'ProcessModule to manage all Projects', 'version' => 1, 'author' => 'Bernhard Baumrock, baumrock.com', 'icon' => 'thumbs-up', 'permission' => 'projects', 'permissions' => [ 'projects' => 'Run the Projects Management Module', 'aggregate' => 'Create Aggregated Reports', ], 'page' => [ 'name' => 'projects', 'title' => __('Projekte'), ], 'nav' => [ [ 'url' => '', 'label' => __('Projekte'), ],[ 'url' => 'mails', 'label' => __('E-Mails verwalten'), ],[ 'url' => 'reports', 'label' => __('Berichte verwalten'), ],[ 'url' => 'aggregate', 'label' => __('Aggregierten Bericht erstellen'), 'permission' => 'aggregate', ], ], ]; Make sure to logout/login, otherwise you won't see the changes in the menu! If you call the ProcessModule's page directly you will instantly get the result of the changed permissions: Whereas in the menu it is still there until you logout+login: @szabesz you asked for that in the blog comments...
    1 point
  13. Ok, I got some updates on this module. I wanted to implement support of multiple forms on one page because I need to add a newsletter subscription form on one page using my module and right now this is not possible. Unfortunately this task is not trivial and I hope to get some help by you guys ? maybe @tpr @Robin S or @gebeer have already done something similar? Please see the explanation of the issue in the Nette Forum: https://forum.nette.org/en/30969-multiple-forms-on-one-page-standalone-version Edit: seems to be solved - got a reply instantly in the nett forum ? BTW: I decided to remove the branding from the module ?
    1 point
  14. Up until now not one of the many softwares on github that I have been using even had branding attached. With pro modules the user knows exactly what to expect: buy and then use. With free modules that ask for money to remove brandings, the user could feel tricked. I'd rather prefer that for the same reason mentioned above. I happily pay for pro modules and use them a lot. I totally respect the way you see things in this matter and it is your freedom as a module developer to handle things like you do. Just wanted to share my thouights and feelings about it and explain some negative views people might have on your approach, especially since it is the first time I come across this in the PW module world. Enough said. Thank you for everything you're giving to the community and keep up the great work!
    1 point
  15. I see your points - I started that way too. Then I realized it's much easier to handle multi-language strings like success message, etc so I added a GUI. I still think this is an improvement, I can make adjustments much easier in the admin when needed. But it's not a requirement, you can use it purely from code too. The form process part looks like this: if ($nfh->checkSuccess()) { $nfh->saveToPage($nfh->getTitle('name, email')); $nfh->sendAdminEmail(); $nfh->getResponse(); exit; } Here saveToPage, sendEmail, sendAdminEmail methods can have parameters so you don't need to use the corresponding admin page. If there's a page for it, then adding a form is as easy as $nfh = addForm(1066); echo $nfh->form; // or $nfh->renderForm() The module loads the form and form field files from page ID 1066 name. Anyway, I think I'll clean up it a bit and share as I see there's a need for it.
    1 point
×
×
  • Create New...