FireWire Posted August 3 Posted August 3 One of the things that I have to work around in a current site is lots of dynamic content. Content in blocks may change depending on values from other pages, dates/times, or updates from external sources such as APIs. Many of these are automated via hooks. My example is events and activities. Events can become active, tickets go on sale, be cancelled, end, etc. This is a block for "Featured Activities" Activities are selected by a person editing a page. They can choose as many activities as desired but only 3 are shown on the page If the date of an activity passes, or tickets are sold out, it is removed from the featured activities (automatically via hook) When one is removed another is rotated in (automatically via hook) If there are no more featured activities (all of the dates have past, or tickets are sold out) the block should no longer be output to the page. These are featured on many pages across the site with a lot of automation and updating them manually would be a very terrible thing 🤣 I have a handful of blocks that do stuff like this. The idea is a shouldRender() method for Block classes. By default it returns true but may be overridden to control whether a block will show up on the page. <?php /** * Whether this block should be rendered. Called before a block is output to the page. * @return bool */ public function shouldRender() { return true; } I have something like this cobbled together already in my code but I have to add an if statement to all of the block views that need this. I also use it for things like image galleries that shouldn't render if the image field is empty. It would be really nice native feature 😎 1
bernhard Posted August 3 Posted August 3 Hey @FireWire thx for the idea/suggestion! Sounds good to me! The only thing to consider would be to also account for hidden blocks. Blocks already have an _mxhidden flag/attribute which is used both for rendering/not rendering the block but it is also used in the helper methods like getBlockNum() or getBlockIndex(). The latter is the base implementation where it accounts for that flag. So maybe all we need to do is to move this: foreach ($items as $item) { if ($item->_mxhidden) continue; if ($item->_temp) continue; if ($item->id === $this->id) return $i; $i++; } Into a dedicated method as you suggest? 1
FireWire Posted August 3 Author Posted August 3 @bernhard My approach was very basic. <?php /** * Whether this block should be rendered. May be optionally implemented by blocks to control front-end visibility * @return bool */ public function shouldRender() { return true; } /** * Render this block * @return string */ public function renderBlock() { if (!$this->shouldRender()) { return ''; } // rest of method... } It works in practice as a function that does not modify RPB's existing behavior and the Inputfield UI takes precedence. Since this visibility is controlled by the blocks and not the user, the developer can work with this state. These may serve some edge cases, but it illustrates the philosophy of separating what is declared in the UI vs. in the block. <?php namespace ProcessWire; /** @var Page $page */ /** @var FeaturedActivities $block */ ?> <section class="rpb-foo <?=$block->classes()?>" <?=alfred($block)?>> <!-- You can create "grouped" behavior --> <?php if ($block->nextBlock()->type === 'Bar' && $block->nextBlock()->shouldRender()): ?> <p>Here's a little something extra</p> <?php endif ?> <!-- ...rest of block code --> <!-- Conditional design still possible --> <?php if ($block->isLastBlock() || !$block->nextBlock()->shouldRender()): ?> <span class="rounded-bottom-corners"></span> <?php endif ?> </section> The un-rendered will exist with an ID because the UI and RPB's core behavior dictates that the block exists and is available, but blocks maintain control of rendering separately.
FireWire Posted August 3 Author Posted August 3 @bernhard Forgot to mention that the first block of code is added to Block.php and is a method provided just for child blocks to override if they need. This way RPB will not render a block if that method returns false independently of the Inputfield UI for hide/unpublished state, which I think is what your for loop checks for. If I'm misunderstanding your for loop example then that could be my problem. A block can be published and unhidden, but the block can determine if it will render with its own logic.
FireWire Posted August 3 Author Posted August 3 Here is what I'm currently doing: <?php class MyBlock extends Block { // ...rest of block /** * Determines if this block should render on the page * @return bool */ public function shouldRender(): bool { // For a field that displays featured activities. This is actually much more complex in real life // and keeping this in a shouldRender() method makes the template much cleaner return $this->featured_activities->where('date>=now&is_sold_out=false')->count(); // Sometimes blocks are saved in an incomplete state // Because ProcessWire doesn't have live field dependencies, an event has to be selected, page saved, // then activies in that event can be selected. This block shouldn't render until all of the information is complete. return !!$this->title && !!$this->event?->id && $this->featured_activities->count(); } // ...rest of block } View file: <?php namespace ProcessWire; /** @var Page $page */ /** @var MyBlock $block */ // This could be eliminated by making shouldRender() a method in Block.php if (!$block->shouldRender()) { return; } ?> <section class="rpb-myblock <?=$block->classes()?>" <?=alfred($block)?>> <!-- Block markup --> </section> If shouldRender() is defined in Block.php then it would be available everywhere. Otherwise to maintain consistency I have to do this in every block: <?php class AnotherBlock extends Block { // ...rest of block /** * This could be eliminated by making shouldRender() a method in Block.php */ public function shouldRender(): bool { return true; } // ...rest of block }
bernhard Posted August 4 Posted August 4 14 hours ago, FireWire said: If shouldRender() is defined in Block.php then it would be available everywhere. Otherwise to maintain consistency I have to do this in every block: What I often do in such situations is to add this via hook: $wire->addHookMethod('Block::shouldRender', function($event) { $event->return = true; }); Then you have a shouldRender() method for all blocks that returns true by default and you can implement custom logic for each block that needs it. But I'm wondering... Maybe it would be a better idea to implement a DefaultBlock class that everybody can implement that RPB picks up and uses to extend every block on? You could then place your shouldRender() there and you could place anything else there as well. I'm a bit concerned about adding shouldRender() as it's really simple for your situation but if in the core I'm quite sure I'll get support requests why shouldRender() does not account for helper methods like getBlockNum() or isEvenType() etc... What do you think? I think having a DefaultBlock class would be nice and should help in your situation as well? 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now