Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 09/13/2023 in all areas

  1. Hello! I know exactly what you are meaning. Well you have at least three possibilites: 1. Use the Fieldset Page in ProcessWire. With this you can combine fields into one Fieldset. I don't use this Fielset anmyore but if I remember correct you have to install it from the "Core Modules Tab" (under: /processwire/module/). You create a new field (name it for example fieldset_introduction) and then you add fields to it that you want to make use of (cool thing: you can reuse this fields in all other fieldset page fields and don't have to create a "body copy field" for each and everyone of them separetely!) And here is what it looks like on a page template. The fields shown above are aligned in a fieldset named "Introduction". You can add several Fieldsets this way and just put them in any order on your page template. 2. Use the Repeater Matrix Pro Field. This ones commercial but it makes handling and editing Sections on your pages much easier and more flexible! Cool thing is that you can define your content blocks in the field settngs and add the needed fields right there. Then on your page template you can select the type of content block (or name it section): After that it's super easy and comfortable to edit these sections and also change their order! 3. Use a third party page builder module like the RockPageBuilder from @bernhard. This module even beats the popular Repeater Matrix module in my opinion as it accelerates the creation and editing (and managing!) of content massively! Even better, it kind of combines the Repeater Matrix block-buidling functions with frontend editing which is a massive leap forward in terms of workflow optimization and customer happiness (cause it is also very easy to use for non-technically experienced users!).
    5 points
  2. With RockPageBuilder this would look like this: You can easily create new blocks by clicking on the plus after the last block and every block consists of one PHP file for the business logic (if needed), one PHP/LATTE file for the output markup and optionally one .less file for the stylings: If you do it smart then you can even reuse these blocks in other projects just by copy and pasting these files ? Compared to RepeaterMatrix which would also be a possible solution RockPageBuilder comes with great integration and a complete UI for frontend editing: So instead of using $block->headline to output the headline of the block you simply use $block->headline() and your headline will be frontend editable!! For non-text fields you can double click anywhere and the editing interface will be loaded in a modal. ? I've not done a single website without that module for a very long time ? It will be 49€ for a single site for early birds. I'm working on the docs and on the webshop at the moment, but there are already several people from the forum using it for real projects, so if you are interested just write me a PM or signup to rock monthly to get notified when sales start ?? https://www.baumrock.com/rock-monthly/
    4 points
  3. Hi, What's it all about I'd like to share some thoughts and approaches i took to meet the customers requirements when i built the website. Maybe someone has (or will have) similiar needs and can benefit from it, hopefully. I will focus mostly on technical stuff. And because of that i am not sure, if the showcase section is the place to post it (?). Its more like a testimonial. About the organisation/website https://vdp-ev.de (german language only) The Verband Deutscher Puppentheater e.V. (VDP) is the cultural policy respresentation for professional puppet stages in Germany. The VDP gives advice, shares knowledge and represents currently 140 member stages. They publish a magazine frequently, giving awards, workshops and organize events for their members. As the VDP was founded many years ago, they have lots and lots of contents, each with individual structural needs . So the first goal was to pack the vast of content in a good structure and make sure visitors has access to it as straight as possible. Therefor the grafical presentation is rather plain, not really stand out or comes with fancy stuff ?. Some random screens of the public website: Some random screens of the (secured) members marketplace (explanation s. below): Seeing no way to set skip links, here is a short plain TOC: 1. Building a marketplace for VDP members 1.1 Requirements and conceptual thoughts 1.2 Building the menu 1.3 Keep it maintainable 2. Publish selected members content on the public website 2.1 Clients request 2.2 Starting, first approach 2.3 Solution 1. Building a marketplace for VDP members 1.1 Requirements and conceptual thoughts Before the relaunch, members (they have a special user role) yet could login into the backend and edit their contents (data for their stage + add/edit productions). With help of @adrians AdminRestrictBranch they only see their own stage and productions. Those users have no access to other menu trees. With the relaunch the VDP wants to have something different/more elaborated: A secured member area (e.g. marketplace) where all members can share and swap ideas, informations, leading discussion related to their profession, offer or search for profession related material (like puppets, technical gear, services etc.) and some more. Just like some kind of a simple forum (with maintaining their own entries, s. below), but without the overhead of a standalone forum software. There was a (only grafical) approach how to do this by the former developer. His idea was to establish a secured member frontend where the loggedin user have (frontend) presentations to manage their own marketplace content, their stage and productions and even their profile a least. Well, some kind of reinventing the backend... Beside the fixed budged, i thought of a better way to do this. First, i wanted to seperate the marketplace output from the input strictly. Without real advantage member users shouldn't be forced to learn something they're already familiar with (Backend login and add/edit their stage and productions). Second, all marketplace content should live within the PW ecosystem, e.g. are template base pages that lives in the page tree. At least for administrators to see/edit... (s. below also). So, where to establish the CRUD actions for the members content of the marketplace section for each member user (with that special user role)? It all leads to adapt the PW Backend Menu. The Module CustomAdminMenus by @Robin S seems to give a good start for this. The setup for static pages is rather simple. But for my use case i had to deal with a much more dynamic approach. Luckily the module is hookable... I wanted to have two Menus: Mein Markplatz which displays all CRUD actions for the sections (Figuren, Bühne, Material, Tipps & Tricks a.s.o., s. image above) and Zum Markplatz Portal which links to the members marketplace overview page. 1.2 Building the menu First we get the current user content, populate the dynamic menu and build two menus at least. /** * Build Marketplace Admin Menu * * Backend interface for vdp members to add/edit pages marketplace * sections. * * Each member only will see entries that they created. */ $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); // Handle Menu 1 if($menu_number === 1) { // Get the current user $user = $event->wire()->user; // Hide admin menu except for vdp members if ($user->isSuperuser() || !$user->hasRole('theater')) { // Do not show the menu $event->return = false; } else { // init $hints_children = []; $offers_children = []; $blog_children = []; $discussions_children = []; // Get all hints for current user $hints = $event->wire()->pages->findRaw("template=members-marketplace-hint, created_users_id=$user.id", ['title', 'url', 'id']); // Allow to add a new hint // Adapt values to fit your environment $hints_children = [ [ 'icon' => 'plus-circle', 'label' => 'Neuen Eintrag anlegen', 'url' => $event->wire()->config->urls->root.'vdpadm/page/add/?parent_id=4232', 'newtab' => false, ] ]; // Build from results // Adapt values to fit your environment foreach($hints as $hint) { $hints_children[] = [ 'icon' => 'file-text-o', 'label' => $hint['title'], 'url' => $event->wire()->config->urls->root.'vdpadm/page/edit/?id='.$hint['id'], 'newtab' => false, ]; } // ... repeat for other marketplace sections ... // Build the menu $menu1 = [ [ 'icon' => 'thumbs-up', 'label' => 'Tipps und Tricks', 'url' => '', 'newtab' => false, 'children' => $hints_children, ] // ... repeat for other marketplace sections ..., don't forget the `,` ;-) ]; // Return the menu $event->return = $menu1; } } // Handle Menu 2 (Link to marketplace portal) if($menu_number === 2) { // Get the current user $user = $event->wire()->user; // Hide admin menu except for vdp members if ($user->isSuperuser() || !$user->hasRole('theater')) { // Do not show the menu $event->return = false; } else { $menu2 = []; // Return the menu $event->return = $menu2; } } }); FYI: Code is part of a ready.php file which lives in /site (s. also https://processwire.com/docs/modules/hooks/) After that the PW top header looks like this: If you click on Mein Markplatz, you get the verbose list*. *) The member user for the demo has no existing entries, so you see none (only the link to add a new entry). 1.3 Keep it maintainable As an administrator i must be able to identify which entry is related to which member. So i prepend the user name to pages names programmatically. This is done by a little helper hook. All relevant templates has a hidden field named `creator`, which gets populated with the user name on page save. To avoid overriding the value when a adminstrator edits an entry, its packed in a condition. Very important, because otherwise the assignment (entry <> member) gets lost! All entries edited by a administrator would have a new owner ?. Definitely not what we want... Btw. the user role `theater` in this example represents the special role for members mentioned above. /** * Utility hook for `Build Marketplace Admin Menu` * * Field `creator` is a hidden page field, that is populated * on page save with the username of the created user. Only related * to members marketplace pages. * * Prevent superuser to change `created user` while edit/save * the page for administration purposes. * * We need this hook on page save, because we want to show * the username within the admin page sidebar list ($page * only provide the ID of the created user). */ if (!$user->isSuperuser() && $user->hasRole('theater')) { $wire->addHookBefore('Pages::saved', function($event){ $page = $event->arguments(0); if($page->hasField("creator")) { // Get the current user $user = $event->wire()->user; // Populate template field for use in the admin sidebar list $page->creator = $user->name; // Save back to $page $page->save('creator'); } }); } FYI: Code is part of a ready.php file which lives in /site (s. also https://processwire.com/docs/modules/hooks/) The complete ready.php vdp-marketplace-demo-ready.php So this is it, mainly. As there are two ways to go after a successful login (Backend view to edit/add content or Frontend view of the members marketplace overview page) the members must decide where they want to go first. Btw, after the user is logged in, the navigation changes within the member frontend scope. So the user can switch between frontend and backend view each with one single click. 2. Publish selected members content on the public website 2.1 Clients request After the relaunch was online, the customer had another request: They wanted to let members decide if their entries (of particular sections of the member marketplace) should also be integrated/viewable on the public website. Visually identical to the views within the member marketplace. Those (public) pages should be available in the navigation and also be indexable by search engines. While this seems not quite a too difficult task at first glance, it turns out to be a bit more tricky. The reason are the restrictions of page paths when dealing with the (secured)members and public scope. Note: All sections in the members marketplace are applied as blog pages, e.g. each has a parent page and associated children. 2.2 Starting, first approach First, i add a conditional checkbox to all related marketplace templates, which was needed in any case. But than it gets a bit annoying. Having kind of a (public) placeholder template (and page based on it) and then iterating over the member blog entries was a dead end. The `children->url` path of course leads to the member section (at that time i wrote a post about this because i hoped this could be solved somehow) As i have to have a real template/page(s) for this (s. 2.1 why), it isn't possible to hook paths here. I realized, i had to find a complete other approach... 2.3 Solution Finally i set up clones for all relevant members section templates (e.g. parent + child template), built public available pages from it and mirror allowed* member entries to the appropriate public pages on a regular base. To avoid disruption, those public pages are readonly for editors. *) Entries with a particular option set (e.g. Beitrag auch auf der öffentlichen VDP Website anzeigen?, s. picture above) The synchronisation between the members area and the public clones is done by cronjobs for each section. Example sync file for one section (VDP Blog): <?php namespace ProcessWire; /** * Synchronize members < > public blog pages * * This file is meant to get executed by a cronjob * * 1. Synchronize (add, update, delete) * 2. Synchronize (delete) * 3. Log, Mail */ // bootstrap processwire, adapt to your path include("../index.php"); // init $d = array(); $o = array(); $totalcountUpdate = ''; $totalcountNew = ''; $totalcountDeleted = ''; // Get member blog items $members_marketplace_blog_items = $pages->find('template=members-marketplace-blog-item'); // Iterate over member blog items, save `name` foreach($members_marketplace_blog_items as $item) { $d[] = $item->name; } reset($d); /** * 1. Synchronize (add, update, delete) * * Perfom synchronisation based on existing member blog items */ // Iterate over members blog items for ($i=0; $i < count($d); $i++ ) { // Get member blog item by given name from the `d` array. $members_blog_item = $pages->findOne("template=members-marketplace-blog-item, name=$d[$i]"); // Get the related public clone (if exists) $public_blog_item = $pages->findOne("template=blog-vdp-item, name=$members_blog_item->name"); /** * Conditional actions * * 1. if * Update present page * * Verbose: Is yet present in public section && is * allowed to be displayed * * 2. elseif * Add new page * * Verbose: Is not present in public section && is * allowed to be displayed * * 3. elseif * Delete page * * Verbose: Is yet present in public section && is * not allowed to be displayed */ if ($public_blog_item->name == $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 1) { $public_blog_item->title = $members_blog_item->title; $public_blog_item->name = $members_blog_item->name; $public_blog_item->headline = $members_blog_item->headline; $public_blog_item->blog_date = $members_blog_item->blog_date; $public_blog_item->page_blog_tags_vdp_blog = $members_blog_item->page_blog_tags_vdp_blog; $public_blog_item->body = $members_blog_item->body; // Save page to get an ID for adding images, s.below $public_blog_item->save(); // Make sure we process an array $public_blog_item->of(false); // Delete images first to avoid duplication $public_blog_item->images_members_marketplace_entry->deleteAll(); // Add images foreach ($members_blog_item->images_members_marketplace_entry as $img) { $public_blog_item->images_members_marketplace_entry->add($img); } // Save page again (just for the sake of it) $public_blog_item->save(); // Save iteration for mail, s. below $totalcountUpdate = $i; } elseif ($public_blog_item->name != $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 1) { // Add new page $public_blog_item_add = new Page(); $public_blog_item_add->template = 'blog-vdp-item'; $public_blog_item_add->parent = '/wissenswertes-ueber-puppentheater/vdp-blog/'; $public_blog_item_add->title = $members_blog_item->title; $public_blog_item_add->name = $members_blog_item->name; $public_blog_item_add->headline = $members_blog_item->headline; $public_blog_item_add->blog_date = $members_blog_item->blog_date; $public_blog_item_add->page_blog_tags_vdp_blog = $members_blog_item->page_blog_tags_vdp_blog; $public_blog_item_add->body = $members_blog_item->body; // Save page to get an ID for adding images, s.below $public_blog_item_add->save(); // Make sure we process an array $public_blog_item_add->of(false); // Add images foreach ($members_blog_item->images_members_marketplace_entry as $img) { $public_blog_item_add->images_members_marketplace_entry->add($img); } // Save page again (just for the sake of it) $public_blog_item_add->save(); // Save itereation for mail, s. below $totalcountNew = $i; } elseif ($public_blog_item->name == $members_blog_item->name && $members_blog_item->options_members_marketplace_entry_publish_to_public == 0) { // Delete page $public_blog_item->delete(); } } /** * 2. Synchronize (delete) * * Delete outdated public blog item pages, e.g. do not * exists in the member section any longer. */ // Get all member blog items $_members_marketplace_blog_items = $pages->find('template=members-marketplace-blog-item'); // Iterate over member blog items, save `name` foreach($_members_marketplace_blog_items as $_member_item) { $o[] = $_member_item->name; } reset($o); // Get all public blog items $_public_blog_items = $pages->find("template=blog-vdp-item"); // Delete public blog item if not found in members array. foreach ($_public_blog_items as $_public_blog_item) { if(!in_array($_public_blog_item->name, $o)) { $_public_blog_item->delete(); $totalcountDeleted++; } } /** * 2. Log, Mail (delete) * * Log results in PW and send status mail */ // $log->save("cron", "Update public blog posts - New: $totalcountNew, Updated: $totalcountUpdate, Deleted: $totalcountDeleted"); // $emailTo = '<your-email-address>'; // $emailFrom = '<your-email-address>'; // $emailSubject = "VDP Blog | Cron | balance public to member sections "; // $header = ''; // $header .= "From: ".$emailFrom. "\r\n"; // $header .= "X-Mailer: PHP/" . phpversion(); // $message = ''; // $message .= "Am: ".date('d.m.Y H:i')."\n"; // $message .= "Aktualisierte Blog Beiträge: ".$totalcountUpdate."\n"; // $message .= "Hinzugefügte Blog Beiträge: ".$totalcountNew."\n"; // $message .= "Gelöschte Blog Beiträge: ".$totalcountDeleted."\n"; // mail($emailTo, $emailSubject, $message, $header); die(); ?> FYI: This example codefile can live anywhere, just adapt the path where your PW instance is located Example cronjob sync file: vdp-cronjob-sync-blog.php.zip Have fun ?
    2 points
  4. Truly an inspiring project! The technical solutions here are fascinating. I haven't touched a project with a custom backend for users (yet!) and I always wondered how to approach it.
    2 points
  5. That's pretty much the main reason. It would require me to use ProcessWire in a different way and I don't buy into that approach, especially with HTML Over The Wire (for example, htmx) giving me all the benefits of heavy JS-frontends without having to use them. Server-side rendering all the way. I've completely eliminated Node from my workflow and it feels great.
    1 point
  6. Hi @bernhard, oh, so far i was not aware of `$page->createdUser`,- stopped scrolling the $page API documentation at System section ;-). Might be rather helpful, as it possibly spares the need for the whole utility hook. I'll have a look... I tried to achieve this using hooks or segments as you suggested. But no luck, so i gave up at some point (time was already limited). Although i completely agree with you, the current approach seems to be rather stable. So i stick with it for now.
    1 point
  7. Hi @olafgleba thx for sharing and congrats for the great result ?? Not sure if I understand that correctly, but wanted to add that you might not need an additional field here. Not saying an additional field is bad, though ? You could either access the user that created the page via $page->createdUser or, if you need a different logic you could use $page->meta('youruser', 'yourvalue') instead of adding a field to that page and then use that for storage. Have you thought of using url hooks or url segments to show your content with custom logic at a custom/different location than the original url? You might save you from the hassle of keeping everything in sync then.
    1 point
  8. @monollonom Nice! If I call $myParentPage->parents()->rebuildAll() before $myParentPage->find('') This seems to fix the issue. Thanks! The performance seems to be great so far with about a 70-100 pages. Before I was using a recursion function like this as a workaround that would call itself for a number of times. Will investigate further wich version is more performant.
    1 point
  9. There wasn’t so much nesting in my case but I did have an issue I resolved using $pages->parents()->rebuildAll(). Maybe try this first? Otherwise I think you’d need to be more specific regarding your setup.
    1 point
  10. I've heard of Astro and a bunch of other JS heavy frontend frameworks where ProcessWire has to become an API for it to work. Hard pass for me for so many reasons. This article is exactly what I was looking for. Thank you for sharing it! It seems nesting is available on all the latest browsers, but I'd worry about people on old versions of Safari due to being on old versions of macOS (Macs seems to last a while), so yea I agree with using a build process for now if nesting is critical to the way you write CSS, which it is for me as well. Also, doing breakpoints in SCSS in much more intuitive than vanilla CSS.
    1 point
  11. I write vanilla css. Well thats a lie ?. I write scss, but only because of lacking css nesting support. I guess in 1-2 years there should be a browser-support over 95%. Nesting is the only scss-feature i use. Everything else is vanilla css. Compiling scss to css is done by ProCache. I'm not a real programmer and have design/art background. Therefore i try to keep my workflow as simple as possible. In my projects I have to develop some „unusual“ layouts and many times the designs change a lot during the development. Therefore over the years the following distribution of files has worked for me pretty well. _reset.scss _fonts.scss (@font-face and sizes as classes) _globals.scss (color-variables, animation-classes and other global classes) _main.scss (for body and main element if needed) _header.scss _footer.scss m_component-name.scss … p_template-name.scss … I don't use components, but my section/article tags have always a unique classname, which correspond with the related php/css/js files. For Example for a image-slider i would have there three files: m_image-slider.php m_image-slider.scss m_image-slider.js .m_image_slider { width: 100%; } Should there be a need to overwrite some styles on a „project“ template, I can do that in a file like p_template_project.scss body.template_project { .m_image_slider { width: 50%; } } And if there is a need to have two different layouts for the image slider on two pages with the same template, I use the page ID. body.id_1234 { .m_image_slider { width: 80%; } } Therefore my body tag has always these two classes: <body class="id_<?= $page->id; ?> template_<?= $page->template; ?>"> That workflow helped me a lot to save time. Most of the the only my section tag has a classname. .m_image_slider { h2 { } figure { img { } } } What I want to say is that css methods have their justification and use. I do not work in teams and as single programmer it is much clearer and flexible to develop my own small method.
    1 point
  12. This week there have been a few updates to the core on the dev branch, but nothing major. Next week I'll be bumping up the version to 3.0.227 and then likely merging back to the master/main branch again. While there aren't major updates relative to 3.0.226, that means there isn't a lot to test or break, so it makes sense to keep updating the main/master branch until we get into more significant updates on the dev branch. In addition to getting into more significant dev branch updates, I'm also planning to put out version updates on nearly all the Pro modules in the upcoming weeks. Thanks for reading and have a great weekend!
    1 point
  13. I am long-time used to writing bem classes. They feel natural to me. But earlier this year I was also looking for more modern alternatives (that could be used without build step). Here is a nice article by Dave Rupert I've found on the topic. Didn't try any of it (yet?) though.
    1 point
  14. Adding my grain of salt to this (I just faced this situation), you can also leave the Label field to the default "title" and change it temporarily (though you need to set it back once the inputfield is rendered): $wire->addHookAfter("InputfieldPage::getSelectablePages", function(HookEvent $event) { if($event->object->hasField != "yourField") return; $pages = $event->return; foreach($pages as $page) { $page->originalTitle = $page->title; $page->title = "Custom title based on $page->someField"; } $event->addHookAfter("Inputfield::render", function(HookEvent $event) use ($pages) { foreach($pages as $page) { $page->title = $page->originalTitle; } $event->removeHook(null); }); $event->return = $pages; }); (not sure though how this would behave in your setup @Pete)
    1 point
  15. Ha just had another case today where I needed this and forgot I'd already worked it out once nearly a year ago ? This happens to everyone right? ?
    1 point
  16. Hey, @teppo! I am back at this great module. Trying to make everything work as it should. I've read your conversation with @bernhard a number of times and think that a lot of issues discussed should make their way into the docs somehow. And answering the question quoted above (about the usage of custom page classes and controllers in Wireframe) would make another great page for Patterns and practices . The great docs are one of the main things that make Wireframe so attractive, as we can easily point to them when working in a team or passing a project to someone else. So keeping them up to date and adding more info is definitely as important (for someone who didn't write the framework in the 1st place))) as adding new features. I would participate in this process if there was a way to. At least I would fix some typos. But as cool as PW backend is it is not as good for open documentation. Is there a chance we can move the content creation to github and populate/update pages in PW via a script? I think someone already done this before...
    1 point
  17. For simple json outputs, you can use WireArray::explode and json_encode() or wireEncodeJSON() methods https://processwire.com/api/ref/wire-array/explode/ $myPages = $pages->find('template=basic-page'); // extract required fields into plain array $data = $myPages->explode(['title', 'created']); echo wireEncodeJSON($data);
    1 point
×
×
  • Create New...