Leaderboard
Popular Content
Showing content with the highest reputation on 11/05/2024 in all areas
-
When you encounter a bardump output or errors in TracyDebugger, a link typically appears below the message, allowing you to open the file and line where the output occurred, with VSCode as the default editor. However, in a Windows WSL2 environment, this feature doesn’t work by default. To enable these links, add the `editor` and `localRootPath` variables to the TracyDebugger module's config or in your `site/config-dev.php` (or site/config.php). Here’s an example in my config-dev.php that works for me. Make sure to use `vscode://vscode-remote/wsl+nameOfYourDistro/pathToYourFiles/%file:%line`. This establishes a remote connection to the selected Linux distro. $config->tracy = array( 'frontendPanels' => array('mailInterceptor', 'panelSelector'), 'nonToggleablePanels' => array('mailInterceptor', 'tracyToggler'), 'outputMode' => 'DEVELOPMENT', 'forceIsLocal' => true, 'guestForceDevelopmentLocal' => true, 'editor' => 'vscode://vscode-remote/wsl+Ubuntu22.04/home/jmartsch/htdocs/fugamo/fugamo-shop/%file:%line', 'localRootPath' => 'dist/', 'backendPanels' => array( // 'processwireInfo', // 'requestInfo', // 'processwireLogs', // 'tracyLogs', // 'methodsInfo', // 'debugMode', // 'console', 'mailInterceptor', 'panelSelector', 'tracyToggler' ), ); Now you can click on the filename, and get directly to the corresponding line.5 points
-
Hello, I have created a simple module to preview theater seat reservations. This is my very first module, so be gentle as I'm not a coder. What is it about? The module creates 5 fields that must be added to the template of your choice. (e.g. event-post) In the template, you can then set the number of rows and the number of seats in each row. After save your preview is created. You can then book or cancel seats by clicking on the seats boxes or trash icon to cancel them. We have a small theater and sometimes we remove some seats, so I also added the option to remove them. Seat-booking.mp4 You can the render this on your frontend with: <?php // Assuming $page is the current page object $rows = $page->rows ?: 9; // Default to 9 rows if not set $seatsPerRow = $page->seats_per_row ?: 8; // Default to 8 seats per row if not set // Load the existing CSS for styling $cssFile = $this->wire()->config->urls->siteModules . 'TheaterSeating/styles.css'; echo '<link rel="stylesheet" href="' . $cssFile . '">'; // Start the seating chart output echo '<div class="theater-seating">'; // Loop through rows for ($i = $rows; $i > 0; $i--) { echo '<div class="row">'; // Start a new row echo '<div class="row-label">Vrsta ' . $i . '</div>'; // Row label // Loop through seats for ($j = 1; $j <= $seatsPerRow; $j++) { $seatId = "$i-$j"; $occupiedClass = in_array($seatId, explode(',', $page->booked_seats ?: '')) ? 'selected' : ''; $disabledClass = in_array($seatId, explode(',', $page->disabled_seats ?: '')) ? 'disabled' : ''; // Output the seat div echo '<div class="seat ' . $occupiedClass . ' ' . $disabledClass . '" data-seat-id="' . $seatId . '">'; // Add the cross overlay for disabled seats if ($disabledClass) { echo '<div class="cross">✖</div>'; // X overlay } echo '</div>'; // Close seat div } echo '</div>'; // Close row div } echo '<div class="stage">Oder</div>'; echo '</div>'; // Close theater seating div ?> and maybe style with: .seat { width: 50px; height: 50px; margin: 0 5px; /* Horizontal margin between seats */ background-color: #ccc; cursor: default; /* Change cursor to indicate no interaction */ display: flex; align-items: center; justify-content: center; position: relative; } .seat.occupied { background-color: #f00; /* Red for occupied seats */ } .seat.selected { background-color: #0f0; /* Green for selected (booked) seats */ } .seat.disabled { background-color: rgba(255, 0, 0, 0.5); /* Semi-transparent red for disabled seats */ } .cross { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 0, 0, 0.5); /* Semi-transparent overlay */ display: flex; align-items: center; justify-content: center; font-size: 24px; color: white; } .row-label { font-size: 16px; margin-right: 10px; /* Space between the label and seats */ font-weight: 600; width: 100px; /* Set a fixed width to align labels */ } I hope someone will find it usefull. Fell free to make it better. 😉 You can download it here: TheaterSeating.zip Cheers 😉 Roych3 points
-
The traveling over the last month or so is finally finished. In late September/early October my family traveled to Spain, France, and Italy for the first time. And the last couple weeks my wife and I were in Holland on a bike trip where we lived on a boat for a week and biked all over the Netherlands (~150 miles of biking), and got to see a large portion of it. Our forum administrator @Pete was also there, as was Jan, who maintains our website on AWS, so sometimes it felt like a mini ProcessWire meetup too. The trip was one from Tripsite, a company using ProcessWire for more than 15 years, and this trip was their 25th anniversary. There were about 30 other people there as well, several whom also work ProcessWire as editors. It was an amazing trip, and now I'm completely sold on bike and boat trips being the best way to experience a country. I felt like I was a resident rather than a tourist. I’m sorry there have not been a lot of updates here lately due to all of the travel, but now that it’s done, it’s time to get back to work on our next main/master version, which I’m greatly looking forward to. While there have only been 3 commits this week, there have been 25 commits since 3.0.241, so I’m bumping the dev branch version up to 3.0.242, to get the momentum going again. Thanks for reading, and for your patience while I catch up with communications and such, and have a great weekend! Below is a photo of Pete, Jan and Ryan on the boat in Amsterdam.2 points
-
Thanks Bernhard for your example, I'll have a look to see if I can implement it like this 🙂1 point
-
Hey @olivetree thank you for your question. I think in terms of use cases you can do quite the same with both modules. (See note at the end) The main difference between both modules is how/where data is handled. ListerPro handles all data on the server side, which means there is basically no limit in terms of scale. Pagination etc. is all done by the backend (php) and only small chunks of the data are sent to the client. With RockGrid, on the other hand, all the data is sent to the client at once and that data is then handled by the client. That has the drawback that you might hit limits earlier than with ListerPro, but it has the benefit that sorting and filtering is done on the client and produces instant results. There is no need for any ajax requests, no waiting for receiving the data, etc.; Another benefit of using RockGrid is that you have unlimited possibilities in HOW you present your data. The downside is that you need to define all that with a mix of PHP (the data selection, basically just a PW selector) and JS (the visual part). With ListerPro you can build your data listings via GUI with just a few clicks. That means that you are very limited in terms of visually presenting data. In terms of scalability RockGrid should be fine quite far, though. Users reported good results with grids having 75 columns and up to 150.000 rows! That's a lot. It always depends on the device though, but I've never had tables with 75 columns and less columns means more rows possible. If you have a look at the demo image of RockGrid: How would you build that with ListerPro? BUT: ListerPro will show page actions by default and you will not have to do anything. With RockGrid every piece of the representation comes from code, so if you need page actions you need to add code to do so (there are helper functions there, but it's more work than with ListerPro). Oh, I almost forgot 🙂 You can use RockGrid as an Inputfield as well! For the RockCommerce module I'm using RockGrid to select the variations of a product. You can filter by variation name, then select all options of the variation by clicking the variation column, then deselect single items that you don't need by clicking on the option column: So RockGrid can not only be used for similar use cases like ListerPro but also for use cases that you have used page reference fields in the past. 😎 I'll have to write better docs and make a video about it, but I wanted/needed to release it as it is a dependency for RockCommerce and for that module you don't need to create any grids on your own, you just install the module and use it. That's why for RockCommerce the RockGrid docs are not a necessity. It's a really powerful module and it has gone a long way. 🙂 Does that answer your question? PS: Oh and here is an example how you can use it for more complex input scenarios, like adding prices and doing calculations on the fly: That's also very different to what ListerPro offers 🙂1 point
-
Hey @zoeck thx that helped a lot. I hacked together an example for you that shows how you can use AlpineJS for that: public function buildForm() { $form = $this; $form->setRockFormsRenderer('UIkit'); $form->setHtmlAttribute('x-data', '{ lines: 2, init() { // update lineData when lines input changes this.$watch("lines", this.lineData.bind(this)); // monitor inputs and update textarea document.addEventListener("input", this.updateTextarea.bind(this)); }, // return an array that we can use for x-for lineData() { return Array.from({length: this.lines}); }, // update textarea when inputs change updateTextarea(e) { // find all .linedata elements const els = document.querySelectorAll(".linedata"); // get their values const values = Array.from(els).map(el => el.value); // join them with newlines const text = values.join("\n"); // set the textarea value to that document.querySelector("textarea").value = text; }, }'); $form->addInteger('lines') ->setHtmlAttribute('x-model', 'lines'); $form->addTextArea('times') ->setHtmlAttribute('rows', '10'); $form->addMarkup('<template x-for="line in lineData"> <div><!-- must have one single root element --> <input type="text" class="linedata uk-input"> </div> </template>'); } The idea is to create a textarea (that would be hidden) that holds the data and to build the UI based on another input via AlpineJS x-for directive:1 point
-
I needed to do this and thought the code might be useful for others too. In my case the organisation has a main site (Site A) and a related but separate site (Site B). The objective is for the users at Site B to be automatically kept in sync with the users of Site A via PW multi-instance. Users are only manually created or deleted in Site A. Both sites have the same roles configured. // InputfieldPassword::processInput $wire->addHookAfter('InputfieldPassword::processInput', function(HookEvent $event) { /** @var InputfieldPassword $inputfield */ $inputfield = $event->object; $input = $event->arguments(0); /** @var UserPage $page */ $page = $inputfield->hasPage; if($page->template != 'user') return; // Return early if there are any password errors if($inputfield->getErrors()) return; // Get the new password as cleartext from $input $pass = $input->get($inputfield->name); if(!$pass) return; // Set the password as a custom property on the Page object $page->newPass = $pass; }); // Pages::saved $pages->addHookAfter('saved', function(HookEvent $event) { /** @var UserPage $page */ $page = $event->arguments(0); if($page->template != 'user') return; if($page->isUnpublished()) return; // Update or create user in Site B $site_b = new ProcessWire('/home/siteb/siteb.domain.nz/', 'https://siteb.domain.nz/'); /** @var UserPage $u */ $u = $site_b->users->get($page->name); // Create a new user if none exists with this name if(!$u->id) $u = $site_b->users->add($page->name); // Set the password if the custom property was set in the InputfieldPassword::processInput hook if($page->newPass) $u->pass = $page->newPass; // Set email address $u->email = $page->email; // Set roles $u->roles->removeAll(); foreach($page->roles as $role) { $u->addRole($role->name); } $u->save(); }); // Pages::deleteReady $pages->addHookAfter('deleteReady', function(HookEvent $event) { /** @var Page $page */ $page = $event->arguments(0); if($page->template != 'user') return; // Delete user in Site B $site_b = new ProcessWire('/home/siteb/siteb.domain.nz/', 'https://siteb.domain.nz/'); $u = $site_b->users->get($page->name); if(!$u->id) return; $site_b->users->delete($u); }); This assumes the use of the default "user" template and not an alternative template. In my case the user template only has the default fields, but the code could be adapted if you have additional fields in your user template. This doesn't handle renaming of users as that's not something I have a need for. But there would be ways to achieve this too, e.g. store the user ID for Site B in a field on the user template in Site A, and then get the Site B user by ID rather than name.1 point
-
You can do something like this: $wire->addHookBefore('Inputfield::render', function(HookEvent $event) { /** @var Inputfield $inputfield */ $inputfield = $event->object; $process = $this->wire()->process; // Return early if this is not ProcessPageEdit if(!$process instanceof ProcessPageEdit) return; // The page being edited $page = $process->getPage(); // The field associated with the inputfield, if any // Useful for when the inputfield is in a repeater, as the inputfield name will have a varying suffix $field = $inputfield->hasField; // The page that the inputfield belongs to // Useful for identifying if the inputfield is in a repeater $inputfield_page = $inputfield->hasPage; // Return early if this is not a page we are targeting if($page->template != 'test_combo') return; // Do some check to identify the inputfield by name or field name if($field && $field->name === 'text_1' && $inputfield_page->template == 'repeater_test_repeater') { // Check some other field value if the message depends on it if($page->test_combo->my_date === '2024-10-18 00:00:00') { // Show an error message $inputfield->error('This is a test error message'); } } // Do some check to identify the inputfield by name or field name if($inputfield->name === 'test_combo_my_date') { // Check some other field value if the message depends on it if($page->test_repeater->first()->text_1 === 'hello') { // Show an error message $inputfield->error('Another test error message'); } } });1 point
-
@nbcommunication As the deadline of the 4th December is coming closer I started abandoning the old InstagramBasicDisplayApi module and using your new version. Thank you for releasing the new version!! I once again experienced that it is a real "pain" to set up an app via the Facebook Developer Account and then do the authorization for the instagram account that is going to be used. Maybe I am doing it wrong but I would like to ask how you - and the other users of this module - handle the authorization for client instagram accounts. Let me explain: 1. The client has a instagram account (so far so good...) 2. To make the integration possible: does the client have to set up an own facebook developer account and create the "Instagram App" inside this account by himself? To be honest, none of my clients is capable of doing this on their own. Thats why: 3. I am using my own facebook developer account to create the app 4. But then theres the authorization process for the instagram account: - You have to log in with the clients instagram credentials two times. Once to add the actual account and allow to fetch data from it. And second to generate a token. - Each time you log in with the credentials the two factor authorization method comes into place and sends a security code to the mobile phone of - the client! So basically you need to set everything up "together" with the client. And another problem: To be able to add the account to the facebook app in the first place you first have to add the clients instagram account to your facebook accounts center. Otherwise you won't be able to do any of the steps mentioned here in step 4! And this also does only work with two factor authentification. So therefore I am asking: How do you handle the integration process? I am using the InstagramBasicDisplayApi module in very few projects and I don't really have best-practice in setting up the integration yet.1 point