Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Stefanowitsch last won the day on September 13 2023

Stefanowitsch had the most liked content!

Contact Methods

  • Website URL

Profile Information

  • Gender
  • Location
  • Interests
    Web Design, Web Development, Music

Recent Profile Visitors

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

Stefanowitsch's Achievements

Sr. Member

Sr. Member (5/6)



  1. @bernhard I have a question about the AJAX/HTMX Endpoints feature: Is it possible to get access to the $page API variable inside my endpoint file to "see" from which page the request was sent? When submitting a form I want to include the title of the page form where the form was submitted into the E-Mail subject line. The only way I see right now is to add the page ID as a parameter to the endpoint URL.
  2. @bernhard do you think this is something the RockPageBuilder can handle?
  3. That is an interesting idea to handle fetching data. Would you mind showing us a short code example? I am planning to do the same in a project. Besides from that the the page you built is very avant-garde! It's kind of cool to see that designers go unusual and experimental ways in webdesign. It reminds me of the early days of the internet. Bedsides from that I have to admit - I also had my struggles understanding how navigating this website actually works 🙂
  4. I think thats also what I've been looking fore. Where do I have to put this config object?
  5. I am glad that it's working for you! I did a quick test with repeater matrix items and as you described it seems not to work with that. I don't use the Repeater Matrix field anymore (instead the RockPageBuilder from @bernhard) but I haven't tried the autocomplete search with that module either!
  6. @bernhard While the new Drag & Drop feature (with the cross icon) works on the RPB Blocks itself it seems not work with repeater items inside the blocks anymore. Can you verify this behaviour? EDIT: ooooops. I just read @netcarver post and your reply on that one. So it will be fixed with the next release.
  7. This error completely disappeared after I changed the PHP ini settings to: open_basedir: none Before that I got all kinds of "open_basedir restriction in effect" error messages.
  8. @Roope I am using this module on PHP 8.1.2 and just received this error message: PHP Deprecated: Implicit conversion from float 521638.00000000006 to int loses precision in /home/users/xxx/site/modules/EmailObfuscation/EmailObfuscation.module:211 The module seems to work normally though. Is this something to be concerned about?
  9. @Robin S I have a question: On the latest sites that are using this module all of a sudden the image URLs get crawled by google and they appear in the search console (they are not indexed, though). This is a bit of a strange behaviour, I've never seen this on my older websites. Does this have anything to do with the way this modules handles the delayed image generation?
  10. Yes it somehow makes sense. I thought if this checkbox is unchecked it will stop sending error mails at all.
  11. Since upgrading Tracy Debugger I always get this message here when logging into the backend: Tracy Debugger "Email Sent" flag has been set. Clear it to continue receiving further emails +1 I find the feature very handy to receive error messages, but I don't always want that so I disabled this checkbox here in the module settings: Anyway after that I always get the message above on all admin pages. Is there an option to disable this behaviour?
  12. Back in the days when almost everything was build via tables in HTML you could actually use Photoshop to export your layout directly as HTML code. I used this for newsletter templates in the past. But beware, no one would ever design a page layout using tables anymore (at least I hope so...). Creating good designs is one thing and converting these designs in to a functional and responsive website is still really a piece of handcraft IMHO. So to answer your question: I use no AI helpers to turn those design files into working code. Too often you have to think around the corner "does this layout work on smaller viewports? How should the elements re-arrange, what elements should be hidden?" etc. etc. There is no way any AI can do that for me or for my clients - yet 😉
  13. In this tutorial I want to show you how to set up a simple ajax-driven auto-complete search within ProcessWire. Something that looks like this: Requirements: 1. Use the Fieldtype Cache to create a search index For fast and easy search queries we will create a search index field. You can read more on this here: https://processwire.recipes/recipes/set-up-search-index-with-fieldtypecache/ First of all, go to Modules and install a Core module called FieldtypeCache. Add a new field “search_cache” and select “Cache” as it’s type. Save the field. On the Details tab choose the fields you want to make searchable. Save again. Add “search_cache” to all templates you want to include in your search results. Optional but recommended: use the “Regenerate Cache” option found from the Details tab of the field to make existing content instantly searchable. In my case i only want to search inside body and title fields, so I included those fields in the settings of the search cache field: 2. Install the Pages2JSON module We want to make ajax requests to the search template and those results should be returned in JSON format so that our java script can process the results inside the auto-complete dropdown. Therefore we make use of this module here: https://processwire.com/modules/pages2-json/ In the module settings we define what data will be included in the JSON object that is returned. Remember that this is the data that we want to display in our auto-complete dropdown. So for my case I only want the title and the url field. Now let's start: 1. Setting up the search template Set up a template file called „search“. Then create a page using this template. On most ProcessWire installations this is already the case and this template exits in the file system. This template will handle the search queries and list all the results for us and that in two ways: - you can send a search query and the search result page will list all the results (as you would expect) - you can send an ajax search query and the result page will return the results as a json object to be processed within javascript and displayed in the frontend in real-time search.php: <?php if($config->ajax) { // Return search results in JSON format $q = $sanitizer->selectorValue($input->get->q); $results = $pages->find('search_cache%=' . $q);; // Find all pages and save as $results header("Content-type: application/json"); // Set header to JSON echo $results->toJSON(); // Output the results as JSON via the toJSON function return $this->halt(); } ?> <main> <div class="uk-container"> <?php // look for a GET variable named 'q' and sanitize it $q = $sanitizer->selectorValue($input->get->q); // did $q have anything in it? if($q) { // Find pages that match the selector $matches = $pages->find('search_cache%=' . $q); // did we find any matches? ... if($matches->count) { echo "<h2>We found $matches->count results:</h2>"; echo "<ul class='uk-list uk-list-square'>"; foreach($matches as $match) { echo "<li><a href='$match->url'>$match->title</a>"; echo "<div class='summary'>$match->summary</div></li>"; } echo "</ul>"; } else { ?> <h2>No results found.</h2> <div uk-grid class="uk-flex uk-flex-center"> <div class="uk-width-1-1 uk-width-1-2@m"> <?= $files->render('elements/_searchbox'); ?> </div> </div> <? } } else { ?> <h2>Search:</h2> <div uk-grid class="uk-flex uk-flex-center"> <div class="uk-width-1-1 uk-width-1-2@m"> <?= $files->render('elements/_searchbox'); ?> </div> </div> <? } ?> </div> </main> Explanation: This part here at the top of the template handles the requests that are send via ajax. This is the important part for later on. <?php if($config->ajax) { // Return search results in JSON format $q = $sanitizer->selectorValue($input->get->q); $results = $pages->find('search_cache%=' . $q);; // Find all pages and save as $results header("Content-type: application/json"); // Set header to JSON echo $results->toJSON(); // Output the results as JSON via the toJSON function return $this->halt(); } ?> What this does: check if the current request is an ajax request if so, search inside the search cache field return the results in JSON format then quit processing the rest of the template (we don’t want to render any markup in that case!) Everything below this part is the normal search template logic. If you send a search request via a form somewhere on the website you want to be redirected to the result page and all the results will be listed on the page just like you would expect. 2. Make the search „work“ Create a template file called "_searchbox.php" (this file is also included in the search template code above, so adjust your paths/names accordingly). To make search requests you want to include this search form anywhere on your page. _searchbox.php: <div class="search-wrapper uk-padding uk-text-center"> <h4>Searchbox</h4> <form class="searchform uk-position-relative uk-flex" method="get" action="<?= $pages->get("template=search")->url ?>"> <input class="uk-input" id="searchInput" name="q" type="search" aria-label="Suchen" autocomplete="off" placeholder="z.B. Bagger"> <button class="uk-button uk-button-primary search-button" type="submit"> <span class="uk-visible@s">Search</span> </button> <div id="suggestions" class="uk-box-shadow-medium"> </div> </form> </div> With this simple search form you should now be able to do a basic search that leads you to a search result page. 3. Make the ajax-search „work“ Now comes the interesting part. We will add in a java script snippet into our just created _searchbox.php that sends ajax requests to the search template page while we are typing into the search intput field and it will display the results in a nice little dropdown. Feel free to adjust the code to your needs! <script> document.addEventListener("DOMContentLoaded", () => { const searchForm = document.querySelector('.searchform'); const searchUrl = searchForm.getAttribute('action'); const searchInput = document.getElementById('searchInput'); const suggestionsDiv = document.getElementById('suggestions'); let selectedSuggestionIndex = -1; // close the auto-complete container when clicked outside the element document.addEventListener('click', function(event) { if (!suggestionsDiv.contains(event.target)) { suggestionsDiv.style.display = 'none'; } }); searchInput.addEventListener('input', () => { const searchText = searchInput.value.toLowerCase(); // Immediately Invoked Function Expression (IIFE) (async () => { try { const response = await fetch(searchUrl+"?q="+searchText, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (response.status === 200) { const data = await response.json(); showResults(data); } else { console.log(response.status); console.log(response.statusText); } } catch (error) { console.error(error); } })(); function showResults(data) { // Show suggestions only if the input has at least two characters if (searchText.length >= 2) { const suggestionHTML = data.map(item => { // Highlight the matching characters using a <span> element with a CSS class const highlightedTitle = item.title.replace( new RegExp(searchText, 'gi'), match => `<span class="highlight">${match}</span>` ); return `<li class="suggestion"><a href="${item.url}">${highlightedTitle}</a></li>`; }).join(''); // Array to string conversion suggestionsDiv.innerHTML = ''; // Clear the suggestions if input length is less than two characters // create list and append search results const suggestionList = document.createElement("ul"); suggestionList.classList.add('suggestion-list', 'uk-list'); suggestionList.innerHTML = suggestionHTML; suggestionsDiv.appendChild(suggestionList); selectedSuggestionIndex = -1; // show the results suggestionsDiv.style.display = "block"; } else { suggestionsDiv.innerHTML = ''; // Clear the suggestions if input length is less than two characters } } }); // Event listener for arrow key presses searchInput.addEventListener("keydown", function (event) { const suggestions = document.querySelectorAll(".suggestion"); if (event.key === "ArrowDown") { event.preventDefault(); selectedSuggestionIndex = Math.min( selectedSuggestionIndex + 1, suggestions.length - 1 ); } else if (event.key === "ArrowUp") { event.preventDefault(); selectedSuggestionIndex = Math.max(selectedSuggestionIndex - 1, -1); } else if (event.key === "Enter") { event.preventDefault(); if (selectedSuggestionIndex === -1) { searchForm.submit(); } else { window.location = suggestions[selectedSuggestionIndex].querySelector('a').href; } } // Highlight the selected suggestion suggestions.forEach((suggestion, index) => { suggestion.classList.toggle( "selected", index === selectedSuggestionIndex ); }); }); }); </script> Keep in mind that you need some CSS styes to make it look good and actually work. These are my styles(in LESS format) but feel free to write you own stylesheet for this. search.less: .search-wrapper { position: relative; background: @secondary-blue; h4 { color: @primary-blue; } .highlight { color: @primary-red; } } #suggestions { display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 10; background: @white; .suggestion-list { margin: 0; li { transition: background-color 150ms ease-in-out; a { padding: 10px; display: block; text-align: left; &:hover, &:focus, &:active { text-decoration: none; } } &:hover, &:focus, &:active, &.selected { background: @secondary-blue; } } li + li { border-top: 1px solid @secondary-blue; } } } That's it! Again feel free to adjust all of the code to your needs. If you have any suggestions how to achieve things a bit easier just let me know.
  14. "Everything" in ProcessWire is a page. So for example I have a "Global Settings" Page (which is actually a page!) in my backend here: Inside this page you can add whatever fields you like to store data that you want to use "globally" on your website. For example text blocks, or repeaters that contain page references to use as a footer menu: In my ready.php file have this global variable that acts as a page reference to the settings page: // Global Settings Page as API variable $wire->wire('globalSettings', $pages->get('template=global-settings')); Then on any template file I can access a field on that global settings page like this: <?= $globalSettings->body; ?>
  15. As far as I know this is a default behaviour on Macs and/or iOS devices. Maybe this plugin can help? https://github.com/lazd/iNoBounce
  • Create New...