Leaderboard
Popular Content
Showing content with the highest reputation on 12/28/2022 in all areas
-
how friendly is the processwire community?5 points
-
Hello all, I wanted to share a proof of concept approach on how we can have an infinite scroll pattern without writing Javascript. We can achieve this with htmx. What you will get An overview page with a list of posts (PW pages). On initial page load 5 posts will be shown. When scrolling down and reaching the last post, another 5 posts will be loaded via AJAX in the background and appended after the last post until no more pages are found. Prerequisites You are using the delayed output strategy, with a _main.php appended. Just like using the default site profile when installing ProcessWire You are using markup regions, in my _main.php I have a div#content that will be used for the output of the posts Inside site/config.php $config->useMarkupRegions = true; Inside site/templates/_main.php <!-- main content --> <div id='content'> </div> For script loading I am using a custom $config->bodyScripts FilenameArray Inside site/config.php $config->bodyScripts = new FilenameArray(); Inside site/templates/_main.php before the closing </body> tag <?php foreach ($config->bodyScripts as $file) : ?> <script src="<?= $file ?>"></script> <?php endforeach ?> </body> PW page structure for this tutorial In the page tree I have a parent page "Posts" with template "posts". All child pages of that page have template "post" In the template "posts" settings in the "URLs" tab, check "Allow Page Numbers" and save. Needed for pagination. When viewing the page "Posts" all logic happens inside site/templates/posts.php site/templates/posts.php <?php namespace ProcessWire; // posts.php template file // add htmx js from site/templates/scripts $config->bodyScripts->add($config->urls->templates . 'scripts/htmx.min.js'); $limit = 5; $posts = $pages->find("template=post, limit={$limit}"); $lastPost = $posts->last(); $nextPageUrl = $page->url . $input->pageNumStr((int) $input->pageNum() + 1); $hxAttributes = array(); $hxAttributes[] = 'hx-get="' . $nextPageUrl . '"'; $hxAttributes[] = 'hx-trigger="revealed"'; $hxAttributes[] = 'hx-swap="afterend"'; ?> <?php if (!$config->ajax) : ?> <section pw-append="content" class="posts" hx-headers='{"X-Requested-With": "XMLHttpRequest"}'> <?php endif ?> <?php foreach ($posts as $post) : ?> <article class="post" <?php if ($post == $lastPost) echo implode(' ', $hxAttributes) ?>> <header> <h3><?= $post->title ?></h3> </header> </article> <?php endforeach ?> <?php if ($config->ajax) return $this->halt() ?> <?php if (!$config->ajax) : ?> </section> <?php endif ?> And that is all there is to it. Not a single line of Javascript, thanks to htmx. I followed the infinite scroll pattern from the official htmx examples. Now let's break the code down into easily digestible chunks // add htmx js from site/templates/scripts $config->bodyScripts->add($config->urls->templates . 'scripts/htmx.min.js'); This adds site/templates/scripts/htmx.min.js to our custom $config->bodyScripts FilenameArray so it will be loaded in _main.php. You can get the script here from unpkg.com. $limit = 5; $posts = $pages->find("template=post, limit={$limit}"); Sets our pagination limit to 5 and loads the correct set of posts. $lastPost = $posts->last(); Saves the last post of each set. We use this later to determine whether the htmx attributes should be rendered. $nextPageUrl = $page->url . $input->pageNumStr((int) $input->pageNum() + 1); We are building the link to the next "page" with the next set of posts. Will result in something like "/posts/page2", "/posts/page3" etc. $hxAttributes = array(); $hxAttributes[] = 'hx-get="' . $nextPageUrl . '"'; $hxAttributes[] = 'hx-trigger="revealed"'; $hxAttributes[] = 'hx-swap="afterend"'; Define our htmx attributes as an array. They will be added to every last post's HTML. Note the hx-get attribute which will be the URL for the AJAX call in the background. That request is triggered whenever the last post becomes visible inside the viewport while scrolling down. hx-swap afterend tells htmx to append the next batch of posts after the last post. <?php if (!$config->ajax) : ?> <section pw-append="content" class="posts" hx-headers='{"X-Requested-With": "XMLHttpRequest"}'> <?php endif ?> // and <?php if (!$config->ajax) : ?> </section> <?php endif ?> Renders the wrapping section tag only on initial page load which is a none AJAX request. Note the hx-headers='{"X-Requested-With": "XMLHttpRequest"}'. This adds an additional header to all AJAX requests with htmx. We need this header so ProcessWire understands that it is an AJAX request. Otherwise $config->ajax would always return false. See https://htmx.org/attributes/hx-headers/ for more info <?php foreach ($posts as $post) : ?> <article class="post" <?php if ($post == $lastPost) echo implode(' ', $hxAttributes) ?>> <header> <h3><?= $post->title ?></h3> </header> </article> <?php endforeach ?> Render each posts's HTML. If it is the last post, also render our htmx attributes. For brevity in this example I only output the post title. <?php if ($config->ajax) return $this->halt() ?> For AJAX requests stop execution of the template file and everything that follows. This prevents appending of _main.php for ajax calls. So we only get the desired HTML for the list of posts and no head, footer etc. Summary Compared to other approaches, htmx lets us control all our AJAX logic with a few html attributes. Really neat and concise. Easypeasy. I like that and will surely use an approach like this in future when infinite scroll is needed. What I like in particular is how easy this is implemented with ProcessWire's powerful pagination capabilities. If you have the same page structure, the code in site/templates/posts.php is working out of the box as is. I have this running on a standard PW multilang site profile with additions/amendments mentioned above under "Prerequisites". Here's a visual of the result:3 points
-
Question: is processwire better than wordpress? Answer:3 points
-
ChatGPT will teach us something ... We really need a ProcessWire Conference ?2 points
-
Thanx @gebeer for sharing! As a side note, anyone implementing infinite scroll, be aware that a simple and basic implementation can lead to very bad UX, see for example: https://builtin.com/ux-design/infinite-scroll So it is a lot of work to implement it from scratch if one wants to provide something that is not pita for the user.2 points
-
Hey Ryan, Thanks for replying and confirming my assertions above, and also thanks for identifying your intentions of its purpose. That was what I figured it was for (primarily cleaning pasted content from other rich text editors). I could see some end-users copying and pasting from other websites, or even other owned properties of theirs and, if sharing a CSS theme, expecting a similar output. To be frank, I don't even think I would want that to work, but I wanted to test just to be sure how versatile the Pastefilter was, and what its limitations were. As for pattern matching attributes or values, I can't currently imagine a necessity for that particular use-case, and until there is one out there by someone, I don't think there's any need to even consider building it into core. Regardless, I'm pretty stoked for this additional functionality for cleaning pasted content!1 point
-
Merry Christmas, Happy Hanukkah, and Happy Holidays! The plan is to release a newyear main/master core version. I'm not currently aware of any showstopper issues specific to the current dev branch, but if you are aware of any, please report them in the GitHub issues repo (or reply to an existing issue report), as the dev branch will soon become the master branch. There's not yet a hard deadline, so whether it's end of December or the 1st week of January, we'll have a new release out shortly. The dev branch is very stable right now (likely more stable than the master branch) so it's a good time to get a new version out. I've also been holding off on several larger updates to the dev branch in anticipation of this merge to master and new release, so I've likewise been holding back on any commits that would add more to test. Once we get a new main/master release out there, the dev branch will then get InputfieldTinyMCE merged into it and there will be some JS library updates, along with other larger updates, and those that would need dev branch testing. Thanks for reading and I hope that you have a great holiday weekend and week ahead1 point
-
@BrendonKoz An attribute definition can either define allowed attributes or allowed values, but not both in the same definition. So you are right that a[href|id] and a[target=_blank] can't be merged into one definition, at the moment; they should be two definitions. You can do a[href|id] but if you preferred it you can also just write them separately: a[href],a[id]. Regarding things like data attributes or wildcards on classes: keep in mind this is just for filtering externally pasted content, and this is not a content filter for the editor itself. So hopefully data attribute and wildcards aren't needed as copied/pasted content. The reason for pastefilter is largely to reduce the pasted content to its roots, the base semantic HTML tags preferably without any attributes (excepting things like an href attribute on an <a> tag). If your need is to allow things like pasting in Font Awesome icon tags and such, then I would suggest just not using the Pastefilter. Either that, or use the code/source editor dialog and paste your tailored markup in there. I see the Pastefilter as being useful for our clients that primarily copy text out of Word (or other editor) and pasting it in. These folks often don't know the difference between paste and paste-as-plain-text, so they can get themselves in trouble, or they can create editor markup that is full of proprietary garbage. Pastefilter helps to prevent that issue. The default configuration of TinyMCE's content filter is a little more open than that of CKEditor's, which I think many people like, but when it comes to externally pasted content, it can be more problematic. So that's where Pastefilter helps out, but it's also completely optional and I'm guessing there will be as many instances where people don't need it as those that do.1 point