Jump to content

gebeer

Members
  • Posts

    1,391
  • Joined

  • Last visited

  • Days Won

    39

Everything posted by gebeer

  1. Looks like https://processwire.com/modules/profields-table/ could be a good candidate for your use case.
  2. 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. $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 :-)
  4. I can confirm that this hook is working on a clean PW3.0.208dev install. So problem is somewhere within my other install.
  5. Latest version of https://github.com/metafizzy/infinite-scroll is from 2020. It uses modern web APIs like fetch, throttles scroll events and uses the https://developer.mozilla.org/en-US/docs/Web/API/DOMParser API, so it is quite efficient in what it does. This is true in many cases, but not in all. Just because a plugin offers configuration options it does not necessarily mean that it is bloated. To me all the config options in infinite-scroll plugin do make sense. It focuses on the task that it is trying to solve, and does this in a quite concise way. If you take a look at the source you will see that it is well structured. Lets say your URL to the index page is /newspage and the template is newspage.php. You can handle the AJAX requests in newspage.php inside an if($config->ajax) block if($config->ajax) { // build your posts array or return the posts HTML return $this->halt; // stop execution } Then your AJAX GET request goes to the same page. To make a request to the same page you can use $.get('./', queries, ...) or use the $.ajax object and ommit the url parameter $.ajax({ type: "GET", // url: "index.php", ommit data: queries, }).done(function (data) { //... }); All the logic with $limit and $offset in your posts.php example is there to figure out which posts to return in pure PHP. This is commonly called "pagination". You are here because you are using our all beloved content management framework ProcessWire. And PW has it's own implementation of pagination and it does it in a very well thought out and efficient way. So why not use the tools that your framework provides? Namely https://processwire.com/api/ref/paginated-array/ . You are already building a PaginatedArray with $posts = $pages->find("template=video, limit='12', sort=-date"); // $posts is an instance of PaginatedArray class because of limit in the selector string With PW's excellent API documentation and the well documented methods of the PaginatedArray class you should be able to figure out how to return what you need inside the if($config->ajax) block. Take a look at the setLimit() and setStart() methods of the PaginatedArray class. So right there you have some more homework :-) Of course you could do it with pure PHP but this would go beyond the scope of this forum. We shall be happy to help you doing it the ProcessWire way, though.
  6. Yeah that's inside a fieldset
  7. @Ivan Gretskywhat do you think about extending the InputfieldLeafletMapMarker with some address inputs and implementing a clean JS approach roughly following my example from above along with a clean UI? I'd be willing to contribute to this effort. EDIT: Oh and there is a custom fieldtype I once made that we could borrow some code from https://github.com/gebeer/FieldtypeAddressGeonames
  8. @Ivan Gretskygreat news. I contributed to the module many years ago with leaflet providers and marker cluster integration. It definitely needs some attention and love. Upgrading assets etc. I think that leaflet.js in general is still a viable solution for open source maps in general. Mapbox have changed their subscription model few years ago that is why I still prefer Leaflet over Mapbox. As for geocoding addresses this is still an issue with the open source provider that sits behind InputfieldLeafletMapMarker (Nominatim). They have improved a lot over time. But still failing on more exotic addresses where the Google Maps geocoder is doing a better job. There's quite a few open source geocoding services out there, but most of them use data from OSM's Nominatim. So you can't expect better results than you get with Nominatim. If looking for closed source geocoding APIs, one that might be worth looking into is https://positionstack.com/ . They have a free tier and seem to have a huge address pool. And I'd rather trust my data with an Austrian Company then sending it to Google. Just sayin... It could be used a s a fallback in case Nominatim returns garbage or nothing. It is not. At least with v3.0.3 of InputfieldLeafletMapMarker. You can make seperate inputfields that hold address data work together with the InputfieldLeafletMapMarker. I once wrote a module for a specific use case where I have 3 inputfields, address, postcode, city. The module loads some JS that first hides the default InputfieldLeafletMapMarker inputs, adds a button which takes the values from those fields, geocodes them through Nominatim, adjusts the marker on the map in the InputfieldLeafletMapMarker and fills the (now hidden) fields with the lat lng and name. Although I have done this about 4 years ago or so, it is still working. Here's the module code for reference in case anyone wants to go a similar route. Not a very clean and portable integration but it is doing the job and is here mainly to serve as an example how you could tackle this scenario. EDIT: short screencast to show UI AddressToMap.module.php (just loads the necessary JS) <?php use ProcessWire\HookEvent; class AddressToMap extends WireData implements Module { /** * getModuleInfo is a module required by all modules to tell ProcessWire about them * * @return array * */ public static function getModuleInfo() { return array( // The module'ss title, typically a little more descriptive than the class name 'title' => 'AddressToMap', // version number 'version' => 001, // summary is brief description of what this module is 'summary' => 'Geocode an Address and put a Pin on the Leaflet Map', // singular=true: indicates that only one instance of the module is allowed. // This is usually what you want for modules that attach hooks. 'singular' => true, // autoload=true: indicates the module should be started with ProcessWire. // This is necessary for any modules that attach runtime hooks, otherwise those // hooks won't get attached unless some other code calls the module on it's own. // Note that autoload modules are almost always also 'singular' (seen above). 'autoload' => "template=admin", // Optional font-awesome icon name, minus the 'fa-' part 'icon' => 'map', ); } public function init() { $this->wire->addHookAfter('ProcessPageEdit::loadPage', $this, 'loadAssets'); } public function loadAssets(Hookevent $event) { $id = $event->arguments(0); $page = $this->pages->get($id); if($page->template == 'member') { $this->config->scripts->add($this->config->urls->siteModules . $this->className . "/{$this->className}.js"); } } } AddressToMap.js var AddressToMap = { init: function () { // $(window).on('map:init', function (e) { // map = e.originalEvent.detail.map; // console.log(map); // }); var coder = window.L.Control.Geocoder.nominatim(); // console.log(AddressToMap.geocoder); var address = $('p.InputfieldLeafletMapMarkerAddress').css({ 'display': 'none' }); var setAddress = $('<p></p>').addClass('InputfieldLeafletMapMarkerSetAddress'); var button = $($.parseHTML("<button class='btn btn-primary btn-block' id='setAddress'><i class='fa fa-arrow-down fa-fw'></i>" + ProcessWire.config.strings.addresstomap + "<i class='fa fa-arrow-down fa-fw'></i></button>")); setAddress.append(button); setAddress.insertAfter(address); var inputfieldName = $('.Inputfield.InputfieldLeafletMapMarker').attr('id').replace('wrap_', ''); button.on('click', function (event) { event.preventDefault(); var combAddress = $('#Inputfield_address').val() + ', ' + $('#Inputfield_postcode').val() + ', ' + $('#Inputfield_city').val(); coder.geocode(combAddress, function (results) { if(results[0] !== undefined) { var map = window.leafletmap; map.eachLayer(function(layer){ if(layer.options.draggable === true) { var latlng = L.latLng(results[0].center.lat, results[0].center.lng); layer.setLatLng(latlng); $('#_' + inputfieldName + '_lat').val(results[0].center.lat); $('#_' + inputfieldName + '_lng').val(results[0].center.lng); $('input#' + inputfieldName).val(results[0].name); map.fitBounds(results[0].bbox); } }); } else { alert('Could not geocode this address'); } }); }); } } $(document).ready(function () { AddressToMap.init(); });
  9. For my approach you don't need full pagination, just one link to the next page, something like 'newspages/page2'. The JS plugin will take care of fetching that next page and then updating the link to read 'newspages/page3' etc. Look at my code sample how I build that initial link URL. I don't see anything messy about pagination links in general. But, like I said, you don't need them for my approach. What you describe is exactly what the infinitescroll plugin does, except for search/sort. You could implement that via a JS filter. But typically infinite scroll implementations do not need search/filtering. From a UX perspective that would be overkill. If you need search/filtering capabilities, infinite scroll might not be the best solution for your use case in the first place. Mind the link that @bernhardposted above. In my example approach search engines have a link to the next batch of posts and can index on from there. Recommended resources: https://processwire.com/docs/front-end/markup-pager-nav/ Yes. vistors without JS (search engine bots etc.) will not see any posts on initial page load client side scroll performance because of non-throttled $(window).scroll event listener. generally the applied scroll detecting technique seems outdated. https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API would be a better approach usability: no bowser history (back/forward) button jQuery dependency (not a real problem more a matter of preference) You still have not stated why you don't want to use plugins. I don't know if taking some seemingly outdated code to build upon is a good approach for a solution in 2023. No offence intended, just my opinion.
  10. Thanks @Robin Sfor checking. I will try this once more on a clean install to confirm.
  11. Why do you have this requirement? JS plugins like https://infinite-scroll.com/ are there for a reason. So developers don't have to reinvent the wheel. Said plugin is well thought out and documented and makes it quite easy to achieve what you want. All you need is a container with your posts and a link to the next page. The rest is taken care of by the plugin. Of course, you could spend hours and write the JS by yourself. I'd rather honor desandro's work and give him 25 bucks for a dev license. Already done it because I needed it for a custom template of a WP site some years ago. If you want to code the JS functionality yourself for whatever reason, you can certainly do that and and have fun with it. You should take browser history into account for usability reasons. The plugin does that. If you decided to use that plugin, setting up the markup and JS initialization could look somewhat like this (simplified example) /** @var PaginatedArray $newspages */ $newspages = $pages->find("template=newsitem, limit=12"); /** @var string $nextPageUrl typically something like /newspage/page2 */ $nextPageUrl = $page->url . $input->pageNumStr((int) $input->pageNum() +1) ?> <div class="infinitescroll"> <?php foreach($newspages as $p) { ?> <article class="post">...</article> <?php } ?> </div> <a href="<?= $nextPageUrl ?>" class="pagination__next">Next page</a> <script> let elem = document.querySelector('.infinitescroll'); let infScroll = new InfiniteScroll( elem, { // options path: 'newspage/<?= $config->pageNumUrlPrefix ?>{{#}}', append: '.post', history: false, // or true }); </script> I have not tested this example code but it should get you started.
  12. Coming here 4 years later because the problem still seems to appear in PW3.0.208 dev and the proposed workarounds are not working for me. I tried both hooks in Pages::find and ProcessPageSearch::findReady. Even if I uncomment the if statements in https://github.com/processwire/processwire/blob/6018c1fbc41c5aab4aceedf1d4a9c610de34aff3/wire/modules/Process/ProcessPageSearch/ProcessPageSearch.module#L378 and https://github.com/processwire/processwire/blob/6018c1fbc41c5aab4aceedf1d4a9c610de34aff3/wire/modules/Process/ProcessPageSearch/ProcessPageSearch.module#L390 Unpublished pages are still not selectable from autocomplete fields for non superusers. Kind of frustrating. And no idea how else to tackle this. Any help would be much appreciated. Need to switch to InputfieldSelectMultiple InputfieldAsmSelect which is not so nice since there will be ~50 pages from which to select.
  13. This is working great. Thank you. Actually I had added it to the JSON before inside a migration method for MM settings. Since MM stores it's settings not as ModuleConfigData but as custom JSON, I had to come up with a method in our migration class that keeps the settings in sync between dev and live. Just in case anybody needs this, here is what I did: /** * apply settings to MediaManager */ private function applyMediaManagerSettings() { $path = $this->wire('config')->urls->admin . 'media-manager/'; $selector = "template=media-manager-settings, status<" . Page::statusTrash; if (!$this->wire('modules')->isInstalled('PagePaths')) $selector .= ", parent={$path}"; $settingsPage = $this->wire('pages')->get($selector); $settingsRaw = $settingsPage->media_manager_settings; $settings = wireDecodeJSON($settingsRaw); //allowed media types $settings['allowed_media'] = ['media' => [/* "audio", */"document","image","video"]]; // Add uploads to media library and publish them $settings['after'][0] = 1; // show filter profiles: yes $settings['show_filter_profiles'] = [1]; // Display User Media: Display all Media $settings['user_media_only'] = [1]; // Sort Media By: Created $settings['sort_media'] = [4]; // Sort Media Order: Descending $settings['sort_media_order'] = [2]; // custom columns in Media manager $imageFields = []; foreach (['products', 'clinical_applications', 'technologies', 'image_gallery'] as $field) $imageFields[] = $this->wire->fields->get($field)->id; $settings['custom_columns'] = array('image' => $imageFields); // filter profiles $settings['filters'] = [ "filter-by-tags" => ["defaultSelector" => "title%=, media_manager_image.tags~=, media_manager_document.tags~=, media_manager_video.tags~=", "title" => "Filter by Tags"], ]; // save settings $settingsPage->of(false); $settingsPage->media_manager_settings = wireEncodeJSON($settings); $settingsPage->save(); }
  14. Hello @kongondo, I have added a checkbox field to the media-manager-image template but cannot choose it as a custom column. Guess this matter is about supported field types for custom columns. Fieldtype Checkbox seems not to be supported. It is supported in normal listers, though. Would it be possible to add support? And is there a list of the supported field types? EDIT: If I add the field id manually to the MM settings JSON, the column appears in the image lister, but not in the Settings ASM Select
  15. Hi all, I was playing around with background video clipped by text to get a nice text effect like https://codepen.io/gebeer/live/wvxweZX In that process I was brushing up knowledge about best compression options for html5 video and came across this very concise article that sums it all up: https://www.kaels-kabbage.com/posts/html5-video-and-images-for-web/ The video in my codepen is 1920x188px 30 seconds at 30fps. The results I got with h264 codec are already pretty good. But with AV1 it is much better. Comparison: original uncompressed: 3.2MB h264 codec: 568KB AV1 codec: 258KB I used Handbrake (ffmpeg) for encoding. For comparison I also tried several cloud services and found https://convertio.co/av1-converter/ to be the most versatile.
  16. I would not have expected this since the lib is actively maintained and used by many projects. But in the github issues I saw that the author seems to be working on PHP 8 support. Most things we can do with CSS/JS but if you want to deliver completely different content for mobile devices, doing it on the PHP level is a viable option. Caching is a problem, though and needs some extra attention. Personally I would solve this through img srcset and size attributes. No need for separate code here.
  17. @snck I am currently working on implementing Repeater Matrix support. It is basically working but needs some more thorough testing. ATM the setMatrixItems() method seems to be working well. If you like, you can grab a copy from https://github.com/gebeer/RockMigrations/tree/repeatermatrix and test it out. This is how you create a Repeater Matrix field: // first create the blank field $rm->createField('repeater_matrix_test', 'FieldtypeRepeaterMatrix', ['label' => 'Product Content Blocks', 'tags' => 'products']); /** * Set items (matrixtypes) of a RepeaterMatrix field * * If wipe is set to TRUE it will wipe all existing matrix types before * setting the new ones. Otherwise it will override settings of old types * and add the type to the end of the matrix if it does not exist yet. * * CAUTION: wipe = true will also delete all field data stored in the * repeater matrix fields!! */ $rm->setMatrixItems('your_matrix_field', [ 'foo' => [ // matrixtype name 'label' => 'foo label', // matrixtype label 'fields' => [ // matrixtype fields 'field1' => [ 'label' => 'foolabel', // matrixtype field options 'columnWidth' => 50, // matrixtype field options ], 'field2' => [ 'label' => 'foolabel', // matrixtype field options 'columnWidth' => 50, // matrixtype field options ], ], ], 'bar' => [ // matrixtype name 'label' => 'bar label', // matrixtype label 'fields' => [ // matrixtype fields 'field1' => [ 'label' => 'foolabel', // matrixtype field options 'columnWidth' => 50, // matrixtype field options ], 'field2' => [ 'label' => 'foolabel', // matrixtype field options 'columnWidth' => 50, // matrixtype field options ], ], ], ], true); Still need to test the other methods for removing/adding items etc. But this one should work. At least it was working in my tests. When testing is completed I will make a PR so @bernhard can implement.
  18. Hi all, we are moving a site from TYPO3 to PW. The old site has all URLs end in .html. Is there a simple way to achieve this in PW without breaking things like urlSegments, pagination etc. ? It might be doable with a hook to Page::path but I think that will break urlSegments. Maybe instead of trying to achieve that goal it might be better to implement redirects. Has anyone done this before? Would be great to hear your opinions/experiences.
  19. I wouldn't rely on this regex for mobile detection. There's a great library for that: https://github.com/serbanghita/Mobile-Detect You can simply copy the MobileDetect.php to site/templates/inc/MobileDetect.php. (If you prefer, you could install it via composer) And then include it in _init.php and use it like include_once('./inc/MobileDetect.php'); function isMobileDevice() { $detect = new \Detection\MobileDetect; return $detect->isMobile(); }
  20. Sorry, my fault. Try $item->colwidth->value
  21. To me this looks like a redirect issue. Thing is, that $_FILES gets cleared when POST requests are redirected. You can check for redirects in your browser dev tool's network tab when you submit the form. If you use the exact same form for both submissions (company and employee), you need to check what is different when you submit the form. Is the form action URL for both exactly the same? Do you have any code in place that redirects the form submission to a different URL when using it for employee?
  22. I'd go with FieldtypeOptions. Let's say your field is called "colwidth" and your options are: 1=1 2=2 3=3 Then you could do something like <?php $w1 = $page->colwidth; $w2 = 4 - $w1; ?> <div uk-grid> <div class='uk-width-<?= $w1 ?>-4@m uk-width-1-1@s'> <figure> <img src='{$page->image->url}' alt=''> </figure> </div> <div class='uk-width-<?= $w2 ?>-4@m uk-width-1-1@s'> $page->body </div> </div>
  23. Yes, you can use https://processwire.com/blog/posts/processwire-3.0.74-adds-new-fieldsetpage-field-type/ exactly for that so you can reuse other fields inside the FieldsetPage fields. Think of repeaters without repeating 🙂
  24. Love them. But too heavy for me while programming. I like light background music that helps me concentrate https://www.youtube.com/user/StudyMusicProject
×
×
  • Create New...