Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 08/16/2021 in all areas

  1. Sure... If you haven't read deeply into htmx, the main premise is that the server is the single source of truth regarding both data and markup, i.e. whole application state. If we need to update either data or markup, the server handles that. We just need to tell it what action to take. Validation has to pass, of course, before the server will oblige ?. Below is a quick example that demonstrates updating the markup using htmx based on user actions. Only 'remove' locations is demonstrated in this example. I wasn't sure whether your app shows the user both their removed and added locations and whether they can reset the session to have all locations reloaded afresh. It doesn't matter much as it wouldn't change much of the logic in the example. Secondly, note that this example makes use of alpine.js and tailwind css just for the pizzaz. These are not required by htmx. To let ProcessWire recognise htmx requests, please refer to this thread. Depending on the approach you take from there, you might not even need a JavaScript file! In the example below, we do have a JavaScript file just because we want to use alpine.js (for notifications) and we need htmx to talk to alpine.js. We also need the JavaScript file to tell htmx to add XMLHttpRequest to its request headers so that ProcessWire's $config->ajax will understand the request. First, let's see a demo then we'll see how easy our work is using htmx. Just for this demo, I have included htmx (and alpine.js and tailwind css via their respective CDNs [in production, you want to purge your tailwind css ?]) in my _main.php. Inside the template I am using for this demo, I have the following code. Here, I have removed the tailwind classes used in the demo so we can focus on htmx. Note that you don't need a dedicated template file for this to work. It will work with any template. As long as htmx is loaded in the page view and your template (in this case, the current page's template) is listening to ajax requests. In the template file, the main htmx magic happens here: <?php namespace ProcessWire; $out = "<a hx-post='./' hx-target='#locations' hx-vals='{\"location_add_id\": \"{$page->id}\"}' hx-include='._post_token' hx-indicator='#locations_spinner_indicator'>Add</a>" . "<a hx-post='./' hx-target='#locations' hx-vals='{\"location_remove_id\": \"{$page->id}\"}' hx-include='._post_token' hx-indicator='#locations_spinner_indicator'>Remove</a>"; echo $out; Let's go through the htmx attributes: hx-post This tells htmx where to send its ajax request. In this case, we are sending it to the same page we are viewing (./) and are using POST. We could have used hx-get if we wanted to (GET). hx-target This tells htmx which markup to replace/swap. The default is to replace the markup of the element from which htmx was called. However, hx-target can be used to specify the element to replace. In this example, we are replacing the whole listing so we target its wrapper element which has the id locations. hx-vals This is optional but we need it in our example. We want to tell the server which location (ID) has been added/removed. If we had a form element, we could have used it for this instead. Since we don't have one, we are telling htmx to process the (JSON) value of hx-vals and send that together with its request. Note: the escape slashes are so we can have raw, valid JSON in the attribute as required by htmx. hx-include This is also optional but important in our case. It tells htmx to include input elements found via the selector in this attribute in its ajax request. In our example, we are telling htmx to include the CSRF token we set on the server together with its request. hx-indicator Also optional. Tells htmx to show/hide this element to show the user that something happened. In this case we use a spinner. We could have used a progress indicator as well, .e.g., if we were uploading a file. That's it really! No event listeners, no handlers! We can add (and I did add one), event listeners on htmx events in order to do something after the event. In this example, htmx fires a custom event which alpine.js is listening to in order to show notifications after the DOM has settled. The notification type (success, error, etc) and the message are all coming back from the server but not as JSON. The markup to update the page is also coming back from the server. htmx receives it and plugs it into the DOM per the hx-target (also see hx-swap) attribute value. In this example, we are updating the whole listing. If we wanted, we could update just the location that was removed, e.g. add it back but with some removed 'indicator', e.g. greyed-out. In this example we use anchor tags as htmx triggers. For htmx, it doesn't matter; button, div, p, li, whatever valid HTML element would work. You already have a backend logic that's working for you but I show an excerpt of mine here, for completeness. Inside my-template-file.php (the template file for the template for the current page, in this example), I have the following code: <?php namespace ProcessWire; if ($config->ajax) { // check CSRF if (!$session->CSRF->hasValidToken()) { // form submission is NOT valid throw new WireException('CSRF check failed!'); } // ................ more code // e.g. check removed locations in the session, etc // get previously removed locations (keeping things in sync) $removedLocations = $session->get('removedLocations'); // REMOVING LOCATION if ((int) $input->post->location_remove_id) { $mode = 'remove'; $notice = "Removed"; $id = (int) $input->post->location_remove_id; } else if ((int) $input->post->location_add_id) { // ADDING LOCATION $mode = 'add'; $notice = 'Added'; $id = (int) $input->post->location_add_id; } // ................ more code // e.g. check if we really have a page by that id, SET $noticeType, $options and update session 'removedLocations' etc // build final content //----------- // @note: buildLocationsCards() is a function that does what it says on the tin. We use it in both the ajax response here and also below, in non-ajax content, when the page loads // the $options array contains a key 'skip_pages_ids' with an array of IDs of locations (pages) that have been removed in this session. We skip these in buildLocationCards(). $out = buildLocationCards($page, $options); // @note - here we always return one input only // we use the values in this input in JS to pass event details to alpine.js to show the correct notification and the notification message. $out .= "<input type='hidden' id='location_notice' name='location_notice' data-notice-type='{$noticeType}' value='{$notice}'>"; echo $out; $this->halt(); } // NON-AJAX CONTENT BELOW.... That's all there is to it ?. I am happy to share the full code if anyone wants to play with this further.
    3 points
  2. I would like to do this, but right now I just don't have the time. The module is in a state where it needs a significant amount of cleanup before it would be usable for people who are not myself. For example, right now it forces you to install practically all the pro modules (FormBuilder, Matrix, ProCache, etc.) as they are dependencies. Secondly, I've hardcoded various things in there at the moment (due to time constraints) that would have to be cleaned up. Third, right now it's kind of in a frankenstein state of how it works with various CSS frameworks. The goal is to make it work with UIkit3, Bootstrap5, or Tailwind2 (or none at all), but I have to finish making it work with each and do the proper OOP (right now it's a mess). Also, the builder related stuff is constantly changing but nearly settled after 2 years (I've had to re-develop builder pages on a site 10 times once, I don't wish that on anyone). Also keep in mind it forces the creation of a bunch of general fields which I would suspect might be a turn-off if you have a purist approach. For example, I have a text field called "IP Address" that gets created. There's not really a choice in whether you can keep it or not. If you delete it, it will get recreated on a re-install or update. I do wonder though if I released if it would take on a life of its own through contributions (even clean-up related work initially). The vision of this module is to be "the best of ProcessWire" and from that it's highly opinionated. Meaning, it forces the use of Markup Regions and setting() vs other approaches. It uses a built-in menu builder based on repeaters (as opposed to something like MenuBuilder module). It relies heavily on the Mystique field for the builder options. I will make a video eventually.
    2 points
  3. Hi, With the deprecation of Instagram's API and therefore the end of the Instagram Feed module, I've developed a replacement module which uses the Instagram Basic Display API: https://github.com/nbcommunication/InstagramBasicDisplayApi To use this module you'll need: ProcessWire >= 2.7 A Facebook Developer account Access to the Instagram user account you wish to use Prior to installation, you'll need to create a Facebook app. The app you will create uses the User Token Generator for authentication - it does not need to be submitted for App Review (and therefore stays in Development mode). The README contains full instructions on how to create and set up the app and also how to use the module. The primary reason for this module's development was to retain functionality on existing websites that use the Instagram Feed module. To assist with upgrading, this module replicates some methods provided by Instagram Feed. I've already upgraded a couple of sites and it was quick and painless ? Cheers, Chris
    1 point
  4. Hello @ all I want to share a new module with you, which makes the creation and validation of forms easy. Take a look at the following example of a simple contact form: // A very simple example of a contactform for demonstration purposes $form = new Form('contactform'); $gender = new Select('gender'); $gender->setLabel('Gender'); $gender->addOption('Mister', '0'); $gender->addOption('Miss', '1'); $form->add($gender); $surname = new InputText('surname'); $surname->setLabel('Surname'); $surname->setRule('required'); $form->add($surname); $name = new InputText('name'); $name->setLabel('Name'); $name->setRule('required'); $form->add($name); $email = new InputText('email'); $email->setLabel('E-Mail'); $email->setRule('required'); $form->add($email); $subject = new InputText('subject'); $subject->setLabel('Subject'); $subject->setRule('required'); $form->add($subject); $message = new Textarea('message'); $message->setLabel('Message'); $message->setRule('required'); $form->add($message); $privacy = new InputCheckbox('privacy'); $privacy->setLabel('I accept the privacy policy'); $privacy->setRule('required')->setCustomMessage('You have to accept our privacy policy'); $form->add($privacy); $button = new Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if($form->isValid()){ print_r($form->getValues()); // do what you want } // render the form echo $form->render(); This piece of code creates a simple contact form and validates it according to the validation rules set. Inside the isValid() method you can run your code (fe sending an email) Highlights: 30+ validation types Support for UiKit 3 and Bootstrap 5 CSS framework SPAM protection Highly customizable Hookable methods for further customization Multi-language You can download and find really extensive information on how to use at https://github.com/juergenweb/FrontendForms. Please report errors or suggestions directly in GitHub. Best regards and happy testing ? If you have downloaded the module in the past I recommend you to uninstall the module completely and install the newest version 2.1.14. There are a lot of changes in the new version, so please test carefully.
    1 point
  5. It worked! I don't know how I missed that. I was pretty sure I checked the path that was generated with docroot, but I guess I won't forget it next time. I am leaving it for a few hours now to test with my actual script and hopefully it's all good. Thanks for the quick tips, guys!
    1 point
  6. You're right, I think the vulnerability was only able to be exploited on systems where Adminer had been left in a publicly accessible location.
    1 point
  7. Work continues on the next master/main version here as we close out older issue reports and finish minor tweaks and adjustments. Eight issues were resolved this week. That might not sound like much, but with the most pressing issues already resolved, more of the GitHub time now goes towards discussion, support and more administrative related stuff, than to actual work in code. Issues remaining and being worked on are those that affect very few and might be more time consuming or difficult to reproduce, or are more subjective. We're very close to our next master version and unless anything new that's particularly pressing arises this coming week, I think we may be there next week. If you have a chance to help us test the current dev branch, please do. Thanks for your help and have a great weekend!
    1 point
  8. Just throwing in htmx as an alternative here. If you are interested, I could put together a rough example.
    1 point
  9. Hi. Not tested but should work $this->wire()->addHookAfter('LanguagesPageFieldValue::getStringValue', function ($event) { $value = $event->return; $languagesPageFieldValue = $event->object; $languages = $this->wire()->languages; $userLanguageID = $this->wire()->user->language->id; $chineseLanguageID = $languages->get('chinese')->id; $newFallbackLanguageID = $languages->get('english')->id; if($userLanguageID === $chineseLanguageID && !$languagesPageFieldValue->getLanguageValue($chineseLanguageID)) { $value = $languagesPageFieldValue->getLanguageValue($newFallbackLanguageID); if(!strlen($value)) { $value = $languagesPageFieldValue->getDefaultValue(); } }; $event->return = $value; });
    1 point
  10. this file then needs to be in site/templates/, so site/templates/_init.php (but can be named anything you want) the cool thing is, you can still decide on a individual template setting level (under templates -> edit -> files) to NOT prepend this file, or to prepend a different file. Same goes for $config->appendTemplateFile, where you could state an always after the template file. see https://processwire.com/api/ref/config/ -> "Template files"
    1 point
  11. You can resize the original image like this: // max width for landscape or max height for portrait $maxSize = 900; // then loop through images // get an image $image = $page->images->first(); // is it portrait or landscape? $portrait = $image->height > $image->width; // set properties according to image orientation $property = $portrait ? 'height' : 'width'; $width = $portrait ? 0 : $maxSize; $height = $portrait ? $maxSize : 0; // if the current size exceeds the max, proceed a resize if($image->$property > $maxSize) { $is = new ImageSizer($image->filename, ['quality' => 100]); // do not crucnch quality for original jpeg images !! $is->resize($width, $height); // resizes the original image, Attention: also wipes out EXIF data, XMP etc. - only IPTC is kept! } // DONE! // end of loop EDIT: @Zeka If you have any further questions, maybe in regard of CLI script processing, please ask. https://github.com/rolandtoth/PageimageRemoveVariations/blob/master/PageimageRemoveVariationsConfig.php#L121-L159
    1 point
  12. Version 1.1.6 is out! ? Changelog: Adds Router->registerErrorHandlers() Hook, that should allow you to overwrite the general error- and warning handlers of the module. That should fix the problem that @David Lumm mentioned above without breaking things for other users. Allows Apikey & Auth-token to be set as GET-params. That can be useful when it comes to loading images via api. Fixes a bug that made it possible to authenticate with the PHP session (cookie) even though token-auth was enabled. Adds Router->setCorsHeaders() Hook Updated Composer & Firebase dependencies
    1 point
  13. Hello, With the API being deprecated at the end of this month, I've built a replacement module which uses the Instagram Basic Display API. Find out more here: Cheers, Chris
    1 point
  14. Hi, I've updated this module with a lot of progress - still should be considered alpha, but should be ready for production soon. Cheers, Chris
    1 point
  15. In an ongoing effort to provide a sort of case study, and more info about this, I'll post random screens and info about the various features of this site. (1) Custom Dashboards The site uses a custom module that supports multiple dashboards. Any given dashboard is configurable and access controlled also. This is the main dashboard: (2) The admin editor pages take advantage of some great modules, namely RuntimeMarkup @kongondo, PageFieldInfo @Robin S, Field Descriptions Extended and more, There is also a new module not released yet called Admin Comments, which for this project got a lot of use. When dealing with a large and complex data collection as was the case with this project, the editors benefited from the ability to have the data auto-analyzed on each work so the "auto flags" field helped with that. The comments also allowed editors to post information, ideas and comments right into the page editor. The AdminComments module also provides the option for any posted comment to be emailed to the other team members (selectable), and the notification email (which is customizable) allows the recipient to click directly to the editor for that page. This saved incalculable hours of work, and enhanced communication during the project, across this large data set.
    1 point
  16. I think this already looks quite promising ? $selectors = [ "template=event", "template=event, range.inRange=2020-01-01;2021-01-01", "template=event, range.inRange=2020-01-01;2020-02-01", "template=event, range.inRange=2020-02-01;2020-03-01", "template=event, range.inRange=2020-04-01;2020-05-01", ]; foreach($selectors as $selector) { $out = ''; $nl = ''; foreach($pages->find($selector) as $p) { $out .= $nl.$p->getFormatted('range'); $nl = "\n"; } d($out, $selector); } This now also works with backwards ranges (eg 19.3.2020 to 10.2.2020) because I store a separate START and END timestamp in the database: I've also implemented onDay, inMonth, inYear selectors: $selectors = [ "template=event", "template=event, range.onDay=2020-04-15", "template=event, range.inMonth=2020-03", "template=event, range.inYear=2021", ]; This also works using timestamps: $stamp = strtotime("2020-04-15"); $selectors = [ "template=event", "template=event, range.onDay=$stamp", "template=event, range.inMonth=$stamp", "template=event, range.inYear=$stamp", ]; Even sorting works out of the box - PW is once more impressive ?
    1 point
  17. $animal = 'cat'; $this->addHookAfter('Class::method', function($event) use($animal) { // is there some way to use $animal here? Yes....use 'use' echo $animal; }); Edit...OK, so I am too slow....
    1 point
  18. You can use $this->animal $this->animal = 'cat'; $this->addHookAfter('Page::render', function($event) { bd($this->animal); }); or you can also do this: $animal = 'cat'; $this->addHookAfter('Page::render', function($event) use($animal) { bd($animal); });
    1 point
  19. Hi Webrocker, A direct MySQL query is the way to go here. The solution from LostKobrakai works, in the context of ProcessWire the query looks like this: SELECT pages_id, COUNT(data) AS count FROM field_legacy_id GROUP BY data HAVING count > 1 Now you can extract the page IDs from the result and delete these pages via the Pw API, as there is existing a duplicate post. Note that if the count is greater than 2 somewhere, you'd need to execute this query more than once.
    1 point
×
×
  • Create New...