Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


ryan last won the day on March 6

ryan had the most liked content!

Community Reputation

19,264 Excellent

About ryan

  • Rank
    Hero Member

Contact Methods

  • Website URL

Profile Information

  • Gender
  • Location
    Atlanta, GA

Recent Profile Visitors

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

  1. I'm getting a look at this thread linked from the PW Weekly, where it looks like this topic has been discussed. BitPoet has an example where named arguments are in the format like "{user}" (if I understood it correctly) and I really like that. It would provide for an option to have named arguments without having to specify what would be in it... just "any valid PW page name characters". That sounds useful. I'll add support for it, in addition to the named arguments support mentioned in the blog post. We can support that without interfering with other regular expression features by having it convert that to a PCRE capture group.
  2. ProcessWire 3.0.173 adds several new requested features and this post focuses on one of my favorites: the ability to hook into and handle ProcessWire URLs, independent of pages— https://processwire.com/blog/posts/pw-3.0.173/
  3. The core dev branch commits this week continue to work on feature requests, and the plan is that the version in progress (3.0.173) is and will be focused entirely on added feature requests. While a few requested small features have been committed to the dev branch this week, there are also still two more in progress that aren't quite ready to commit, so those will likely be in next week's commits. Once they are in place, we'll also bump the version to 3.0.173. Following that, I'd like to have 3.0.174 focused on resolutions from the processwire-issues repo, and 3.0.175 focus on PRs. That's the plan for now anyway. It might be a good rotation to keep going. In the next couple of weeks I'm also likely to wrap up the client project that's kept me pretty busy recently, though it's all been ProcessWire-related and fun work thankfully. If you've also been busy building new sites in ProcessWire any time recently, please add them to our sites directory if you get a chance. I hope you all have had a great week and likewise have a great weekend!
  4. @thetuningspoon After save actions don't participate in a delete action. But I can add a hook for that purpose: ProcessPageEdit::redirectAfterDelete(). I'll have it in there in 3.0.173 and you can hook before that method to perform your own redirect before the page editor does.
  5. This week I've been developing a client site, but also working through some of the feature requests. I'm going through them in order of "thumbs-up" on GitHub and trying to focus more on those that I can get through reasonably quickly. For a summary of what's been added, be sure to see the dev branch commit log. There's still a few more I'd like to add before bumping the version to 3.0.173, so will save that for next week. One of the requests was for the ability to add custom after-save actions in the Page editor. These are the "Save + Exit", "Save + Add New", etc., dropdown actions that you see on the Save button in the page editor. This is something that's already supported, but not formally documented. So I wanted to quickly go through a couple examples of how to do that here, as it is kind of useful and fun. Let's start with a "hello world" example to keep it simple, then we'll move on to a more practical example. Say we want a "Save + Say Hello" dropdown action in our page editor Save button. We need one hook to add the action, and another to process it. These hooks could go in your /site/ready.php file or your /site/templates/admin.php file, or in a module. First we'll want to hook ProcessPageEdit::getSubmitActions() to add our custom "hello" action: $wire->addHookAfter('ProcessPageEdit::getSubmitActions', function($event) { $actions = $event->return; // array of actions indexed by name $page = $event->object->getPage(); // page being edited // add a new action that says hello after saving page $actions['hello'] = [ 'value' => 'hello', // value for action that you will check 'icon' => 'hand-spock-o', // icon to show in action, excluding the 'fa-' part 'label' => '%s + Say Hello', // the '%' is replaced with 'Save' or 'Publish' 'class' => '', // optional class if you need different styling for button/link ]; $event->return = $actions; }); To process our action, we'll need to add a hook to ProcessPageEdit::processSubmitAction(): $wire->addHookAfter('ProcessPageEdit::processSubmitAction', function($event) { $action = $event->arguments(0); // action name, i.e. 'hello' $page = $process->getPage(); // Page that was edited/saved if($action === 'hello') { $notice = new NoticeWarning("Hello! You edited page $page->path"); $notice->icon = 'hand-spock-o'; $event->notices->add($notice); } }); That's all there is to it. That part where I created the $notice above could just as easily been $this->warning("Hello!..."); but I wanted to add a custom icon to it, which is why I created the Notice manually. Many of the built-in after-save actions perform a redirect to another location, such as adding another page, exiting back to the page list, viewing the page on the front-end, etc. If you have a need to perform a redirect after the save, use the $event->object->setRedirectUrl($url); method. This is preferable to calling $session->redirect(); yourself, as it is handled by the page editor after it has finished everything it needs to do. What if you just want to remove one of the existing actions? For instance, maybe you want to remove the "Save + Add New" action. That action has the name "add", so we can remove it like this: $wire->addHookAfter('ProcessPageEdit::getSubmitActions', function($event) { $actions = $event->return; // array of actions, indexed by name unset($actions['add']); $event->return = $actions; }); If there's another you'd like to remove, I'd recommend using TracyDebugger and bd($actions); so you can see and identify all the actions that are present from your hook. Now let's get to a more practical example. Let's say that you are using ProCache and you want to add a save action to automatically prime the cache after performing the save. By "prime the cache" I mean have it perform a page-view on the front-end that makes it save a new cache file for the page. Here's how you could do that: $wire->addHookAfter('ProcessPageEdit::getSubmitActions', function($event) { $actions = $event->return; // array $page = $event->object->getPage(); // page being edited $procache = $event->wire('procache'); if(!$procache) return; // if procache not installed, do not add action if(!$page->isPublic()) return; // page not public is also not cacheable if(!$page->viewable()) return; // page not viewable has no template file if(!$procache->isPageCacheable($page)) return; // page not setup for cacheing $actions['cache'] = [ 'value' => 'cache', 'icon' => 'fighter-jet', 'label' => '%s + Cache', // Save + Cache 'class' => '', ]; $event->return = $actions; }); …and our hook to process the action: $wire->addHookAfter('ProcessPageEdit::processSubmitAction', function($event) { $action = $event->arguments(0); // action name, i.e. 'cache' $page = $process->getPage(); // Page that was edited/saved if($action === 'cache') { $http = new WireHttp(); $response = $http->get($page->httpUrl); if($response) { $size = wireBytesStr(strlen($response)); $event->message("Cache primed for page $page->path ($size)", "nogroup"); } else { $this->warning("Error caching page: " . $http->getError()); } } }); Note that we don't have to clear the cache file here because that's something that ProCache has already done prior to our hook above being called. Thanks for reading and have a great weekend!
  6. A couple weeks ago there were a lot of ideas about page builders and different approaches in ProcessWire. Some really interesting ideas actually, and great proof of concepts as well. The conversation started moving faster than I could keep up with, and I think Teppo's summary in ProcessWire Weekly 351 is a great overview, if you haven't yet seen it. These are so great in fact, that I don't think anything I build could live up to what some of you have already built. In part because my focus is more server-side than client-side development, and I can clearly see most of these page builders are much more client-side based than I have expertise in (though would like to learn). Whereas some of you are clearly amazing client-side developers; I'm blown away by what's been built, and how quickly it happened. One struggle I have with page builders is that I don't have my own good use cases for them, with the projects that I work on. Others here (like Jonathan) already have a lot of good use cases. Clearly there are a lot of benefits. I'm fascinated and enthusiastic by all of it, but can't envision how or if I'd use such a tool in my own projects. And having experience with (and particularly close use cases for) a tool is important in hitting the right notes in building it. Likely page builder support belongs in ProcessWire in some form or another, and ProcessWire can be an excellent platform for it. But the actual page builder needs to be designed and built by the people that have expertise in the tools needed to build it, and by the people that will actually use it. I think it should be a community collaboration. For anything needed to support this on the PHP side, count me in. I don't think I've got the background to "lead" the development of the page builder, but I do think I've got plenty to contribute to it. I'd also like to continue building out existing tools like Repeater/Matrix to further support page builder type use cases. I'd like to rebuild and move the auto-save and Live Preview features from ProDrafts into the core, which should also help support some of these page builder cases. Whether these will eventually apply to the larger page-builder project, I don't know, but they'll be useful either way. What are the next steps? Who's interested in working on this? Let's keep the conversion going and move forward with it.
  7. @LMD Thanks, it looks like a last minute optimization screwup on my part, changing a $page->set() to a $page->setForced(), when there was bug in $page->setForced() preventing it from populating $page->settings when it should. I have pushed a fix for it on the dev branch. Please let me know if you continue to see any errors.
  8. In that case it would have to use the existing find() method, which does that: performs a find() to find the page IDs and template IDs, breaks them into different groups by template, and then performs query(ies) to then load the pages with the appropriate autojoin fields for the templates. The new find method used by findJoin() and "field=..." option is a lot simpler so it does all of it in 1 query, which is possible because you are specifying the fields to autojoin (rather than the template, so it doesn't need to figure that part out). I don't yet know if there is a downside, as I've never tried to autojoin-all before. I'm going to look into it though. But I think there are potential downsides, as 1 big query with a lot of joins can sometimes be slower and harder for MySQL to cache than a lot of simple queries. MySQL also has limits on how many joins it can perform in a query and how much data can be concatenated from a multi-row table, so it's likely that autojoin of all fields might be less reliable in some cases. I guess I see autojoin as a good optimization for some (or many) cases where it's found to be reliable and increase performance, but not all cases, and not something to use by default.
  9. @adrian Sounds like a good idea to me. I think it would be possible if your selector specifies which template(s) are in use, so it knows what fields it can autojoin, rather than attempting to autojoin all fields in the system. I can look into it further. Though should also mention that not all fields support autojoin, so it wouldn't technically be possible to autojoin "all", though it could accomplish it by using autojoin to load what it can and then manually load the rest, so it could still deliver pages to you with all of the fields populated and no further loading necessary. Not sure if this applies to your case or not, but wanted to mention that if doing this for just the current/page being rendered, then it wouldn't be worthwhile. The efficiency benefits in autojoin come into play when lots of pages are being loaded. @Zeka It's a similar option in terms of what it does, but also quite different. The new "field=..." option (or findJoin method) uses an entirely different page finding method that performs the find and load in the same query. (This is possible since you are specifically telling it what fields to join). Whereas the options.loadOptions.joinFields you mentioned is just an option to the pages.getById() method that occurs after the find() has already taken place, and it's really more intended as an internal option rather than a public API one. The newly added feature can do the same thing more efficiently, and easily enough that I thought it belonged in the public API. One other difference to mention is that the options.loadOptions.joinFields adds to the default autojoin fields for the loaded pages rather than overrides it. So it doesn't give you the level of control that the new option does unless you don't have any fields with the autojoin flag in your system. No changes here. There's only so much that can be joined in one query per field, so autojoin is not always possible, especially on multi-value (FieldtypeMulti) fields with lots of values. It should be possible on most Page reference and options fields though, so long as they don't have huge amounts of selections. If there's need for it though, I may be able to have fields (where applicable) store a cache of data (like FieldtypeCombo does) that can be autojoined. Since you specified a field that does not exist, I think you are seeing the "no fields specified: load all" behavior here. Probably it would be better if it threw an Exception or just returned a blank result set. Autojoin isn't very useful for the current page, like the one you are rendering. There isn't likely going to be a worthwhile difference in overhead between autojoin fields and load-on-demand fields when rendering the current page being viewed. The difference becomes worthwhile when working with lots of pages that all have the same field. For instance, think of the page-list in the admin—having the title field autojoin is useful there. For most people, they probably don't ever even need to think about autojoin. But for those of us interested in optimization, it does offer some worthwhile improvements in the right situations. I wouldn't bother with trying to autojoin your SEO fields since you would only use those rendering the page being viewed. This kind of micro-optimization can take up your time without delivering benefits you'll ever feel. Though if you wanted to load them all in one chunk, a Combo field would be a great way to do that.
  10. This week ProcessWire gained powerful new tools for finding pages and controlling how they are loaded. If you like to maximize performance and efficiency, you’ll like what 3.0.172 brings— https://processwire.com/blog/posts/find-faster-and-more-efficiently/
  11. This week we have a couple of useful new $pages API methods. They are methods I've been meaning to add for awhile but hadn't found the right time. The need coincided with client work this week, so it seemed like a good time to go ahead and add them. I'm doing a lot of import-related work and needed some simple methods that let me work with more page data than I could usually fit in memory, and these methods make that possible. These won't be useful to everyone all the time, but they will be useful in specific cases. The first is $pages->findRaw() which works just like the $pages->find() method except that it returns the raw data from the matching pages, and lets you specify which specific fields you want to get from them. For more details see the findRaw() documentation page. There's also getRaw() method which is identical to findRaw() except that it returns the raw data for just 1 page, and like get(), it has no default exclusions. The next method is $pages->getFresh(). This works just like $pages->get() except that it gets a fresh copy of the page from the database, bypassing all caches, and likewise never itself getting cached. ProcessWire is pretty aggressive about not having to reload the same page more than once, so this method effectively enables you to bypass that for cases where it might be needed. Again, probably not something you need every day, but really useful for when you do. More details on the getFresh() documentation page. I'm going to likely cover these new $pages API methods in more detail in a future blog post, along with some better examples, so stay tuned for that. I noticed there's some pretty great conversion and examples happening in the posts on page builders here, but have been so busy this week I haven't had a chance to dive in, but I'm really looking forward to doing so. Thanks and I hope you all have a great weekend!
  12. This week I've had to work on a client project (and that continues for the next week or two), but also have some good progress in the next development branch version of ProcessWire (v3.0.171), which includes the addition to two feature requests: The Repeater Fieldtype (and likewise RepeaterMatrix) was updated to improve support for the "depth" option by introducing something called "family friendly mode". This makes the "depth" setting behave in a manner where items with more indent are considered children of items with less indent (parents). So when you click and drag to move a parent item, it brings along the children too. This works whether changing the location or the indent of the parent. In addition, it enforces the parent/child relationship so that there can't be more than one level of indent between parent and child. So you couldn't (for example) have a parent-to-grandchild relationship in the repeater items. A useful case for this is when using repeaters in page builder contexts, like those Jonathan Lahijani demonstrated with RepeaterMatrix. In fact, the settings in this "family friendly" mode were his idea. More to come here too. To enable "family friendly mode", go to your Repeater or RepeaterMatrix field settings and make sure the "depth" setting is greater than 1. When greater than 1, the family-friendly option will appear as a toggle you can enable. The other thing added to the core this week was a FieldtypeDecimal module that has been requested for awhile. I was planning on making it a setting of FieldtypeFloat, largely to avoid interfering with the existing 3rd party FieldtypeDecimal module. But a few people indicated they'd prefer it to be separate. After testing on an installation that already had the 3rd party FieldtypeDecimal installed, I found it didn't cause any problems, so that alleviated my concern. To be safe, the new core FieldtypeDecimal module uses the same exact setting names as the 3rd party one, so that hopefully they should remain compatible. In my case, I found that deleting my /site/modules/FieldtypeDecimal directory, and then doing a “Modules > Refresh” enabled the core one to take over with any existing decimal fields. However, until we have reports that the same works well for others, I would suggest backing up and confirming it yourself to be safe. It's not often we intentionally introduce module name collisions, and I don't think we've ever done it in the direction where a new core module overlaps with an established 3rd party one. But in this case, it does seem to facilitate the transition a bit. That's all for this week. I've been enjoying and am looking forward to continuing with our roadmap discussions hopefully next week. Have a great weekend!
  13. This week I've been focused primarily on keeping up with the conversation about requests for 2021 and beyond, as well as doing related research here. We are starting to narrow in on ideas and plans, but there's more to cover still. Most of the conversation is happening in the PW 3.0.170 thread and last week's update thread. If you want to see what our preliminary roadmap looks like, Teppo did an amazing job covering it in the ProcessWire Weekly #348. I don't recall where it was or who asked, but I remember someone asking whether things like "thumbs up" quantities on GitHub issue reports and feature requests are paid attention to. And the answer is, not really so far. That's because GitHub doesn't display this info anywhere other than on the actual issue report. And even then, I often don't notice it. But this week I've been looking into it more and see that we can sort GitHub reports by quantity of thumbs-up votes. (Maybe the rest of you knew about this, but I guess I'm slow when it comes to GitHub). So it turns out that these thumbs-up votes aren't just social media fluff, they are actually very useful on issue reports and feature requests. Usually I cover these things newest-to-oldest, but now I'm going to start going through in order of votes, as this seems to make a lot more sense. Sorry I didn't catch this useful feature earlier. Please use the thumbs-up votes on GitHub for issue reports and feature requests that you value the most, and going forward, that will help us to cover them in a useful order. Next week I'm booked on a client project (working in PW), so it may be quiet for another week in terms of core updates, but then will be back to wrapping up our roadmap and ProcessWire development. Thanks for reading this far and have a great weekend!
  14. I'll have to think about it a bit more to be certain, but most likely it isn't a problem. And what's in your screenshot above definitely isn't a problem. If we get into 100s of these kinds of templates then I think that's where I might start to worry and think we have to benchmark and find alternatives... but that's also the point where someone has gone well beyond a simple builder.
  15. @flydev 👊🏻 Thanks for posting that, I've learned something new about editor.js. My experience with it is mostly limited to their own demo, so I'm really just looking at it from an outsider perspective. The tool is more powerful than it looks at first. I was particularly impressed with your integration of ProcessPageEditImageSelect. Nice work. It looks like you are pretty far along with this module and it also looks like it will be a great tool for PW users. When do you think you'll be releasing it? Interesting, what you mean exactly ? From what I can tell, editor.js is one big input element, kind of like a rich text editor, even if it also isn't. I think this is also compelling, and what makes one draw comparisons to CKEditor. So editor.js could be represented by a PW Inputfield, but it couldn't itself contain other PW Inputfields (as far as I understand it). Whereas something like a Repeater is an Inputfield that contains other Inputfields. If a block in a builder can be represented by a group of Inputfields, then that means that the types of blocks you can build can be very flexible and modular. The benefit being that it expands the means by which you can extend it. On the flip side, it also means that it's not something you'd ever compare to an RTE because it's just completely different. Editor.js seems to find a nice middle ground between RTE and block builder. It does appear that every block type is defined by a PHP class, but I'm not sure if that also corresponds to a template (?). Templates are a resource best limited to not use any more than necessary (since they all have to be loaded on every request), which is why RepeaterMatrix limits itself to 1. Though maybe something like block types aren't huge quantities, I don't really know. Such a solution would need to come with several predefined block types, just as editor.js does. I think this builder focus is important, otherwise it's becomes too abstract, at which point something like editor.js with plugins becomes a clearer answer. So I'm hoping that people could get up and running by selecting existing block types rather than having to always create them. Then they can create them when-and-if they need too. When it comes to code definition, it comes with its own benefits, and I think it's fine so long as the built-in defaults/types are strong enough to answer the most common needs. Getting into this level of stuff is where the RepeaterMatrix upgrades should help. But this level of complexity I think would hinder a simple builder for the larger audience. For this particular project, I think it's best if it focuses exclusively on content tools and doesn't get into layout and style aspects. This is one of the things Jonathan and I talked about. I'm investigating options for alternate storage methods of repeater items, such as the ability to save all the content JSON encoded in the 'data' column of the repeater table (rather than saving in separate pages). It would be an option you can choose in the field settings. I'm going to try and make it happen though it's possible I may hit a roadblock, so will have to get further along before I know for sure. I think editor.js is also an answer to what many people are looking for, so we have to be careful not to lose sight of what it does well. While there are things to learn from it, I agree it probably doesn't answer quite enough of what we are looking for. Good ideas. Improvements to RepeaterMatrix are part of the plan. But that's going to be a separate project. The $page->meta() method exists for this purpose, at least from the API side. Though the values are still stored in the DB, but not as field data, just as flexible unstructured data stored for each page. The Combo field might offer potential here too, as you can bundle as many inputs as you want within a single Combo field. Though it's still technically stored in field data, and still as searchable as any other field data. But without the being a separate dedicated field and having a separate DB table for each component, etc.
  • Create New...