Jump to content

Leaderboard

Popular Content

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

  1. 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 ahead
    1 point
  2. The problem: how to refresh a page's frontend content in real time without a page refresh when the page has been edited in the backend. This is an alternative approach to the one discussed in the topic below. The above topic attempts to solve the problem using SSE. The solution in this topic instead relies on Local Storage and htmx. There is no ajax polling. Please note that auto-refresh here refers to a page refresh during the same session and in the same domain. It is not cross-site. The solution can be summarised as follows: Use a hook in ready.php to inject custom JavaScript to the ProcessWire backend admin, specifically in ProcessPageEdit. This can be a JavaScript file or inline script. The purpose of this file is to update a given Local Storage key after a page has been edited. Instead of listening to the Save Button click, the script just checks on load if a certain hidden input whose value matches the page currently being edited is present. This hidden input is placed inside ProcessPageEdit form using a hook as detailed below. The hook in this step #1 can be: $this->addHookAfter('AdminTheme::getExtraMarkup', null, 'hookAddCustomScriptsToAdmin'); In ready.php, add a hook to listen to saveReady(), e.g. $this->addHookAfter('Pages::saveReady', null, 'hookNotifyFrontendOfBackendPageSave');. When the page is saved, the handler method hookNotifyFrontendOfBackendPageSave will check if there are changed fields. It will then write to a temporary session variable for the current page with the names of the fields that have changed. On page load/reload, there is a hook in ready.php that monitors ProcessPageEdit, i.e. $this->addHookAfter('ProcessPageEdit::execute', null, 'hookAddMarkupToNotifyFrontendOfBackendPageSave'); The handler hookAddMarkupToNotifyFrontendOfBackendPageSave will check if there is a session variable set in #2 indicating that the page has had recent changes in the fields names set to that session variable. If this is true (that there have been changes), this hook will simply append the hidden input in #1 to the ProcessPageEdit form. The JavaScript in #1 will detect the presence of the hidden input. It will then amend the Local Storage value in #1. It cannot simply set the value to the ID of the page being edited as that will not be construed as a change. It will instead append a timestamp to the id of the page as well and set that as the value. This will enable a listener in the frontend, described in the following steps, to detect that change. In the frontend, we have a simple JavaScript file that listens to changes to the Local Storage storage Event. The frontend also has a simple hidden input in the template file whose pages we want to refresh. This hidden input has htmx attributes. Its trigger is a custom event 'autorefreshpage'. It will listen for this event before firing a message to the server. This hidden input also has hx-swap=none as nothing will be swapped to it. Instead, the server will send back results of changed fields in various markup with hx-swap-oob=true. That will enable swapping markup in any parts of the frontend html in one go. When the Script at #4 detects that a given Local Storage value has changed, it first checks if the page ID for that Local Storage value matches the page ID of the page currently being viewed in the frontend. If the answer is yes, it will fire the custom event autorefreshpage. This will make htmx fire a message to the server whose only value is the page ID it has been passed. On the server, in home.php, we have code that is listening to ajax requests. If it detects an ajax request, it checks if the page ID that htmx sent matches an available session that was recently updated. This would be the temporary session detailed in #2 above. If a match is found, it calls a function in _func.php that will set variables for fields for that page. These use partial templates (optional). These partial templates are for different outputs of the page, e.g. images, headline, etc and all their markup have hx-swap-oob='true' in appropriate places. All these content is sent back as one output back to htmx. It will read the hx-swap-oob and find the markup to replace in the DOM using the html IDs of the markup with the hx-swap-oob. This approach allows the developer to have maximum control over what to send back to htmx and over the markup. In this step, we also delete the temporary session, ready for the next updates. The result is as demonstrated below. A simple and effective DOM swap that places very little strain on the server. I'll clean up the code for this and upload to GitHub. This approach will not work with Save + Exit and similar :-).
    1 point
  3. $config->ajax works exactly the same for POST and GET requests. It tells you whether the request is a "normal" or an XMLHttpRequest , independent of the request type. Why would you think that those URLs are stupid? They are a conventional way of telling PW which set of pages to retrieve. Other frameworks are using this convention, too. They consist of 2 parts: {pagNumPrefix}: configured through $config->pageNumUrlPrefix, defaults to "page"; tells PW that this is not a regular URL but a "paginated" one {pageNum}: number of page, in other words: number of set of pages to retrieve; setStart() is using this number From these 2 PW is able to figure out which pages from a PaginatedArray it should return. Let's look at your example code in posts.php and see how that relates to PW terms and conventions: /** @var WireInput $input */ // Default limit $limit = isset($_GET['limit']) ? $_GET['limit'] : 12; // $_GET['limit'] translates to the limit= partof the $pages->find selector // Default offset $offset = isset($_GET['offset']) ? $_GET['offset'] : ($input->pageNum - 1) * $limit; // $_GET['offset'] translates to $input->pageNum -1 * $limit // $queried_posts = array_slice($posts, $offset, $limit); // translates to /** @var PaginatedArray $posts */ $posts = $pages->find("template=video, limit={$limit}, start={$offset}, sort=-date"); // if URL is /page2, $posts will contain 12 pages starting from page at 12th index You can already see how much simpler the code becomes when using "stupid" URLs :-) Now let's look at the etymology of the word "stupid": It originates from the Latin word "stupere" which means "to be amazed or stunned". Isn't it amazing how simple pagination can become when doing it the PW way? I personally am really stunned by that fact :-) Of course you could do it in a less amazing way. PW is flexible enough to allow that. But then you would have to calculate and pass the information about $limit and $offset back and forth manually in your ajax call. You can pass this inside an object, just like your example code does: let queries = { 'offset' : offset, 'limit' : limit }; jLoad.get('<?php echo $config->urls->templates ?>posts.php', queries, function(data)... Actually jQuery translates this to a request URL with GET parameters like /site/templates/posts.php?offset=0&limit=2 In PW this URL would not work because you cannot call site/templates/posts.php directly (see https://processwire.com/talk/topic/20300-run-independent-php-files-within-processwire/ ) You can solve that by including posts.php for AJAX calls inside your template (videos.php) if($config->ajax) include_once("posts.php"); This is not all there is to do. Inside your JS you would have to keep track of the offset. How would your JS know which offset we are on? You could pass the offset as data attribute somewhere in your HTML and then retrieve it in JS. You could also send it together with the response as JSON. You could store it as cookie. Many possibilities here. But still making everything more complicated than it has to be. When using a #load-more button with an amazing URL like /videos/page2, in your JS you just have to update the "2" to "3" so the amazing URL reads /videos/page3. By the way, the infinite-scroll plugin does exactly that. If you want to do it yourself and you have a link that points to the next page like <a id="load-more" href="/videos/page2">Load more</> You could use this JS to update href to 'videos/page3' var elem = $("#load-more"); var path = elem.attr('href'); var pathParts = path.split('/'); var thisPage = pathParts.pop(); var pageNum = parseInt(thisPage.match(/[0-9]+$/)); var pagePrefix = thisPage.slice(0, thisPage.match(/[0-9]+$/).index); var nextPage = pagePrefix.concat('', pageNum + 1); pathParts.push(nextPage); var newPath = pathParts.join('/'); elem.attr('href', newPath); Is this nice? No. Could it be improved? Certainly. While in the above there certainly are some code samples that you could use to achieve what you want, my intention was more to show that it is a lot more work to go this route. Look at my initial example to use with infinite-scroll plugin and how simple and intuitive that is. Sorry if I cannot (or rather don't want to) supply you with a readymade copy/paste solution and sorry if I might sound sarcastic. I'm just not seeing the point in reinventing the wheel of pagination for the xth time over. I don't see the advantage of a custom implementation for your use case over that which PW provides out of the box. If it is just the amazing URLs like page2, page3 that bug you, try to think of them like a tool that you can use to your advantage. And neither the site visitor nor search engines need to see them. As for site visitors, they would only see them in the browsers' dev tools. And search engines only if you allow them to (think of rel=nofollow). If you still want to do it your own way then I wish you the best of luck and happy coding :-)
    1 point
  4. So i tried the other way around and formatted a date using the IntlDateFormatter. This thread helped me find my way: https://stackoverflow.com/questions/71874030/local-language-without-strftime-deprecated-but-with-date So imagine we want to output "today" like this (formatted for german) Mittwoch 8 Juni 2022 This was the old way to to it: echo strftime('%A %e %B %Y', strtotime('now')); Since this will be deprecated in PHP 8.1 and removed in PHP 9 now we have to make use of this instead: $fmt = new IntlDateFormatter('de_DE', IntlDateFormatter::FULL, IntlDateFormatter::FULL ); $fmt->setPattern('EEEE d LLLL yyyy'); echo $fmt->format(strtotime("now")); The only thing that freaked my out about this was the weird formatting pattern. At first sight it makes no sense (to me), but the ICU documentation offers a big list of available options for formatting the date in any way you could imagine: https://unicode-org.github.io/icu/userguide/format_parse/datetime/ If you are looking for a "one liner solution" then this seems to be the right way (https://stackoverflow.com/questions/12038558/php-timestamp-into-datetime) echo IntlDateFormatter::formatObject( new DateTime('@' . strtotime("now")), "EEEE d LLLL yyyy", 'de_DE' ); EDIT: Beware that timezone settings get ignored when you work with timestamps. This might get you into some trouble. My solution was to format the timestamp into a date string: echo IntlDateFormatter::formatObject( new DateTime(date("d.m.Y", $timestamp)), "EEEE d LLLL yyyy", 'de_DE' );
    1 point
  5. Hi @dragan, Yes that's a very good advice, I always do this kind of manipulation in a local environnement. Maybe I'm comparing to much Processwire with others CMS/CMF. Maybe the "caches" table that contains the system registry entries should never be cleared manually. (caches table system registry) Modules.info Modules.site/modules/ (This is the one causing the Fatal PHP error, after "caches" table is manually cleared and the system is not generating it automatically) Modules.wire/modules/ ... and so on If @ryan design it that way, I understand :). But I think it would be nice, if the rebuild caches table's content script can generate the content for this one too "Modules.site/modules/". Thank you,
    1 point
×
×
  • Create New...