Jump to content

Recently Updated Topics

Showing topics posted in for the last 7 days.

This stream auto-updates     

  1. Yesterday
  2. Hi @jonatan, I've been able to pick away at this over the weekend... too cold yet to plant tatties! With the discovery of the hidden limit option in the API, I had to rewrite some of the module, but everything should just work the same, if not even better... However, I'm not sure it'll help too much with API rate limiting in your case, as each carousel album is still a separate API call. There is now the option to prevent the "children" (the rest of the album carousel images) from being retrieved: <?php $instagram = $modules->get("InstagramBasicDisplayApi"); $items = $instagram->getMedia(["children" => false]); When working with this over the weekend, I kept an eye on the rate limiting page and it did get nudged frequently, but didn't go beyond 30% used. The account I was using for testing only had a few carousels though. I've also been able to implement "lazy loading". The example I've added to the README is based on the getMedia() example, using UIkit's javascript utilities. I've attached a short clip of how this looks. Here's how it might work using Bootstrap/jQuery: In your template file: <?php $instagram = $modules->get("InstagramBasicDisplayApi"); // The pagination cursor is reset if the request is not AJAX, e.g. when the page is loaded. if($config->ajax) { header("Content-Type: application/json"); echo $instagram->getMedia(); // ["json" => true] is inferred by $config->ajax die(); } echo "<div id='instagram' class='row'></div>"; In your javascript: // Totally untested!!!! var instagram = { $el: {}, // Where the items go $loading: {}, // The loading spinner total: 0, // The total number of items init: function() { this.$el = $("#instagram"); if(!this.$el.length) return; // Add the spinner this.$el.after("<div id='instagram-loading'>...Your choice of spinner!</div>"); this.$loading = $("#instagram-loading"); this.$loading.hide(); // Get the first batch of items this.get(); }, get: function() { var this$1 = this; // Show spinner this$1.$loading.show(); // Request $.getJSON(window.location.href, function(data) { // Hide spinner this$1.$loading.hide(); if(!$.isArray(data) || !data.length) return; // If no items do not render var items = []; data.forEach(function(item) { switch(item.type) { case "VIDEO": items.push(this$1.renderItem(item.poster, item.alt, item.src)); break; case "CAROUSEL_ALBUM": var out = ""; for(var i = 0; i < item.children.length; i++) { var src = item.children[i].src; out += "<a class='carousel-item' data-fancybox='gallery-ig' data-height='600' data-caption='" + item.alt + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" + "</a>"; } items.push( "<div data-interval='3000' class='carousel slide carousel-fade col-4 p-3 pics' data-ride='carousel' id='_" + item.id + "'>" + "<div class='carousel-inner'>" + out + "<a class='carousel-control-prev' href='#_{$post->id}' role='button' data-slide='prev'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-prev-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Previous</span>" + "</a>" + "<a class='carousel-control-next' href='#_{$post->id}' role='button' data-slide='next'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-next-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Next</span>" + "</a>" + "</div>" + "</div>" ); break; default: // IMAGE items.push(this$1.renderItem(item.src, item.alt)); break; } }); var count = items.length; if(count) { // Wrap all items with a div var out = ""; for(var i = 0; i < count; i++) { out += "<div id='instagram-item-" + (this$1.total + i) + "'>" + items[i] + "</div>"; } // Append items to the container this$1.$el.append(out); // Attach listener // No idea if this works!!! $(window).on("resize scroll", function() { var next = $("#instagram-item-" + (this$1.total + count - (count < 5 ? 1 : 6))); if(!next.hasClass("seen") && this$1.inViewport()) { next.addClass("seen"); this$1.get(); } }); // Update total this$1.total = this$1.total + count; } }, function(e) { this$1.$loading.hide(); console.log(e); // ERROR }) }, inViewport: function(el) { // Untested, got the code from // https://medium.com/talk-like/detecting-if-an-element-is-in-the-viewport-jquery-a6a4405a3ea2 var elementTop = el.offset().top; var elementBottom = elementTop + el.outerHeight(); var viewportTop = $(window).scrollTop(); var viewportBottom = viewportTop + $(window).height(); return elementBottom > viewportTop && elementTop < viewportBottom; }, renderItem: function(src, alt, href) { if(href === void 0) href = src; return "<div class='col-4 p-3 pics'>" + "<a data-fancybox='gallery-ig' data-height='600' data-caption='" + alt + "' href='" + href + "'>" + "<div style='background-image: url(" + src + ")' class='pics'>" + (src !== href ? "<div class='overlay-video-icon'></div>" : "") + "</div>" + "</a>" + "</div>"; } }; $(document).ready(function() { instagram.init(); }); I've just done the jQuery from memory with the addition of an inViewport function I found online. No idea if it works! Let me know how you get on! Cheers, Chris instagram-basic-display-api-lazy-2020-03-29.mp4
  3. This markup actually belongs to InputfieldWrapper rather than individual inputfield renders. There is the InputfieldWrapper::setMarkup method that you can use to customise the markup that is rendered by InputfieldWrapper. See the defaultMarkup property as a starting point for what can be customised: /** * Markup used during the render() method - customize with InputfieldWrapper::setMarkup($array) * */ static protected $defaultMarkup = array( 'list' => "<ul {attrs}>{out}</ul>", 'item' => "<li {attrs}>{out}</li>", 'item_label' => "<label class='InputfieldHeader ui-widget-header{class}' for='{for}'>{out}</label>", 'item_label_hidden' => "<label class='InputfieldHeader InputfieldHeaderHidden ui-widget-header{class}'><span>{out}</span></label>", 'item_content' => "<div class='InputfieldContent ui-widget-content{class}'>{out}</div>", 'item_error' => "<p class='InputfieldError ui-state-error'><i class='fa fa-fw fa-flash'></i><span>{out}</span></p>", 'item_description' => "<p class='description'>{out}</p>", 'item_head' => "<h2>{out}</h2>", 'item_notes' => "<p class='notes'>{out}</p>", 'item_detail' => "<p class='detail'>{out}</p>", 'item_icon' => "<i class='fa fa-fw fa-{name}'></i> ", 'item_toggle' => "<i class='toggle-icon fa fa-fw fa-angle-down' data-to='fa-angle-down fa-angle-right'></i>", // ALSO: // InputfieldAnything => array( any of the properties above to override on a per-Inputifeld basis) ); But this applies to the whole InputfieldWrapper. In terms of making it more targeted you can set markup for individual inputfield types by using the inputfield class name as a key: $wrapper->setMarkup([ 'InputfieldText' => [ 'item' => '<li {attrs}><p>Markup before</p>{out}<p>Markup after</p></li>', ], ]); But you can't target individual inputfields by name or something like that. LoginRegisterPro has a similar setMarkup() method but has the added feature of letting you use "name=some_inputfield_name" as a key: https://processwire.com/store/login-register-pro/docs/#customizing-markup-and-or-class-attributes-html It would be cool if a feature like that was added to the core InputfieldWrapper::setMarkup method.
  4. I hate hate hate hate (repeat however many times you would like) to say this, but WooCommerce is my weapon of choice for eCommerce. For the level of my client base, they are on the understanding that if I say I am not doing that with this software and you would need something more highend and secure, then they are ok with the functionality. That will be the only time you will ever hear me talk WordPress on here (until the new Padloper comes along). But, I did check out the module by @Gadgetto that @bernhard was talking about, and it is impressive. Yes you can plug everything into the frontend yourself with Snipcart, but the backend integration is lovely. If you are worried about the fees, then in my experience, you have a product that has too many competitors and there will always be somebody out there undercutting you. Just roll something out with a slightly higher price otherwise, test the water and adapt from there.
  5. @bernhard off topic, but what is RockBodyBuilder?
  6. You are right @bernhard. And I didn't even know I could set selectors via array! I'm still scratching on the surface of ProcessWire... Code refactoring will be done when the module is in a stable state and I've implemented all necessary features.
  7. That's right, currently the W3C does not validate. @gebeer also mentioned this with the possible solution to use "text/plain" instead of "optin". I'm planning to implement this solution, but as an optional addition to keep backwards compatibility for the users who already use the "optin" variant.
  8. Hi guys, Does anyone know how to add sorting tot the child selects? The first select can get sorting in the dynamic selects settings, for example sort=name and it works nice, but for some reason it doesn't work for the second and third selects. I have tried to manually drag and drop the items in the admin for the alphabetical order, I also tried to set all parents sort settings to sort=name but that doesn't move it either. In your example @kongondo you also see this behaviour (frontend dynamic selects example) Let's say I have Continent, Country, City selects. Under my country select I have USA, Mexico, Argentina. The country pages were added in this order, so 1 USA, 2 Mexico and 3 Argentina. Argentina is always as the last item in the dropdown select (on frontend). If I add Zimbabwe under Country parent, the Zimbabwe will be last in the dropdown select. So it seems that the child drop down selects are always being sorted by creation date? Can this be setup to work in alphabetical order?
  9. Yes, without the need to enlarge the images on the bottom side, this would be a perfect use. But I needed a smooth transition from bottom black to transparent, without a visible hard line where the added bottom space touches the image. I couldn't figure out a css only solution that combines all needed parts. Also the current browser support isn't good, and I would have to use filter with additional layer divs instead of backdrop-filter.
  10. Thanks again. Solve my problem. Have to write french_Canada in C. Let's see what it the next thing that will not work... 😉
  11. Last week
  12. Actual pagination buttons aren't implemented at the moment. That footer is meant to provide clarity for editors that there's more records than the ones currently displayed. I'd consider the dashboard more a jumping-off point for editors than a replacement for other admin views better suited to browsing pages (like the page tree or listers). Ideally, they'd spend as little time as necessary on the dashboard and then go off to do whatever needs to get done in other parts of the admin. While I'm pondering whether adding this is a good idea (it is a useful feature!), there's two options: 1. Create a copy of the included collection panel and give it a stab yourself. It shouldn't be too difficult — the module supports reloading panels via AJAX and provides the necessary JS events. Have a look at the documentation section on creating custom panels or crack open the chart panel to see an example. Let me know how it goes or if the module is missing anything to get this working. 2. There is a (currently) undocumented parameter you can supply to display a ›View All‹ button next to the pagination info. The parameter name is list and it takes a page, page ID or selector. If it's an admin page (ProcessLister), it will link to that page. If it's a content page (e.g. your Clients parent page), it will open the page tree at that page. $panels->add([ 'panel' => 'collection', 'title' => 'Clients', 'data' => [ 'collection' => 'template=client, limit=10', 'sortable' => true, 'pagination' => true, 'columns' => [ 'title' => 'Title', 'url' => 'URL', 'modified' => 'Modified', // Display "View All" button in footer 'list' => 1184, // ID of lister page ] ] ]);
  13. Turns out getNext() works on the pageField whereas next isn’t contextually aware (so it gets the next item in the parent rather than in the pageField)
  14. SOLVED. As usual with processwire it's very easy. I just needed to do use add() function to add a copy to the album and then delete the relocated image. In the code below $instr contains the album name for destination album. case 'move': // Move image(s) from one album to another $out = "<p>Move $nosel images(s) to album $instr</p>"; $newalb = $pages->get("/{$instr}/"); $newalb->of(false); foreach($selected as $item){ $album = $pages->get($item->album); $album->of(false); $image = $album->images->getFile($item->file); $basename = $image->basename; $filename = $image->filename(); $out .= "<p>{$basename} in {$album->title}</p>"; $newalb->images->add($filename); // delete the relocated image $album->images->delete($image); } $newalb->save('images'); $newalb->of(true); $out .= saveUpdatedAlbums($pages, $selected); $out .= "<p>Updated album $newalb->title</p>"; $response['message'] = $out; break;
  15. Ugh... that was really ugly 😐 Here's a better version: In your autoload module: $this->addHookProperty("User::isCustomer", function(HookEvent $event) { $event->return = $this->user->isSuperuser(); if($this->user->hasRole('manager')) $event->return = true; if($this->user->hasRole('customer')) $event->return = true; }); $this->addHookProperty("User::isManager", function(HookEvent $event) { $event->return = $this->user->isSuperuser(); if($this->user->hasRole('manager')) $event->return = true; }); In your processModule: public function checkAccess() { // if user is neither customer nor manager we redirect if(!$this->user->isCustomer AND !$this->user->isManager) { $this->session->redirect('/your/admin/url/to/no-access-page'); return; } } public function executeDNS { $this->checkAccess(); $user = $this->wire('user'); $out = "<div>Hello $user, here are your dns settings...</div>"; if($user->isManager) $out .= "<div>You are a Manager, so you can edit all settings!</div>"; ... return $out; } A lot better. Welcome to PW greatness 🙂
  16. fliwire


    If admin language not default language, default language saved properly, but when showing field has wrong value. Overwrite seo field for home template: opengraph_description has value: "{seo.meta.description}". DB Data: {"opengraph_description":"{seo.meta.description1}","opengraph_description1033":"{seo.meta.description2}","opengraph_description1185":"{seo.meta.description3}"}
  17. Hey @adrian I've just started using live.js because I'm doing some work in the backend where I need JS/LESS a lot. After getting tired of reloading the browser I thought I'd try browsersync that I recently discovered but it did not work. Then I found live.js and added this to my module: if(!$this->user->isSuperuser()) return; if(!$this->config->debug) return; $this->config->scripts->add('http://livejs.com/live.js'); And this just works!! The great thing about it is that it even recognizes when my style.less file changed. The great thing here is that it does not reload the browser but only replaces the CSS file (which is crazy because the CSS is parsed via RockLESS on demand, so the server must somehow be involved. Anyway... the style gets updated without reloading the page as soon as I change my LESS file 😎 I thought this would be very easy to include in Tracy. For the frontend it would need to add the whole script tag instead of doing $config->styles->add(), but that would also be simple. What do you think? Edit: I understand now how the less-part works. On every poll of live.js the server is involved, so live.js results in lots of requests to your server! I don't think that's a very good option...
  18. If one's goal is to get an up or down from an API call, then zero vs. non-zero is functionally and effectively the same thing as boolean. If there's an opportunity to make a method more useful by taking advantage of that fact, then I'll always do it. So yes, you'll find many examples of this in PW. That's always the strategy I've tried to embrace in the PW API and plan to continue going forward. To reiterate what I stated above, the primary purpose of the has() method is not to get an ID—it is instead to check if the system has a page matching the criteria or not, without actually loading the page. Whether the method returns 0 or false makes no difference in telling the caller that the system has no page matching the criteria. Likewise if the page does match the criteria (true vs 1+). Now if you are specifically looking for a getID() function, then yes of course the name "getID" is preferable. I'm happy to add getID() as an alias for times when one might be specifically looking for that particular need. But that's not what I was looking for here. And if anyone else's experience is similar to mine, we will more often be looking to see if the system has any page matching some criteria (whether we want the ID or not). Basically replacing instances where we might have used count() before, with a more efficient alternative. So I feel pretty strongly that the has method name and return value are optimal and consistent here.
  19. I hope everyone here is doing well, staying in, and staying healthy. Our town here is under a “stay at home” order, and it’s now the law that you can’t get within 6 feet of any other person when out walking. So haven’t left the house (other than for walks and bike rides) in about 2 weeks now. Though with the whole family home all the time, it admittedly feels a lot busier than before this Coronavirus stuff, I think because there’s now a lot more people to attend to during the day (especially kids). Not much silence compared to before. 🙂 Not a bad thing, just very different. If we’ve got to spend a few months, or even a year this way, it’ll be alright, so long as the internet keeps working. I’m just thankful to have a job where I’m already used to working this way, as I know many of you do too. It seems that this whole situation is going to move a lot of activity online that previously wasn’t, so I anticipate it’s going to be potentially a very busy and important year for web development. Online communication and content delivery is going to be that much more important for the world, making reliability, scalability and security every bit as important. These are always our focus, but just want to emphasize this even more as we look forward. With a world in temporary disarray, you can count on ProcessWire to be an especially stable and reliable tool that gets even better every week, and our community always a friendly and helpful place. I’ve got several things in progress in the core, but nothing far enough along to write about just yet. I’ve also been putting a lot of work into ProCache this week, which is long due for a version update. The module still has quite a bit of PW 2.x architecture that I don’t think is needed anymore, so I’m refactoring and improving quite a bit, in addition to feature updates. Thanks for reading and I hope that you have a good and safe weekend!
  20. Thanks guys, but I'm indeed aware how Google works 😃 and had hoped to find a less hacky way of doing things then straight up SQL. The weird this is that when I try to collect the language setting from a user via a selector and $pages, I don't get the actual field value - I get the value in the $user object instead. Something that works around this would also work well for me.
  21. Better face value (not as in Phil Collins album) than body count. Joking and fun aside (which we still shouldn't lose and very often need nowadays): Never feel ashamed to use a face mask or use anti-viral stuff on daily items. Just stay safe and minimize the risks. I personally don't wanna miss anyone of you here!
  22. Super Tracy!! LOL .. Another feature discovered today:
  23. Well, it's certainly a sign that something's wrong somewhere. I never quite encountered the same problems (either separately or in combination, i.e. empty FB iFrame + can't delete modules). Things I would try / look at: Maybe your hosting company changed / updated something? (PHP version? .htaccess settings) Install Tracy Debugger and put your PW site into "debug mode" (in site/config.php). At the very least, you'll get some warnings / errors that you normally wouldn't see. Check server logs as well as PW logs See if there's any suspicious stuff in your browser JS console. Copy everything to the local machine and see how everything behaves there. Hopefully somebody else with more Form Builder experience will soon join in with help/tips.
  24. So I just opened an issue and closed it immediately because I realized that my proposed solution already exists in the form of wirePopulateStringTags 🙂 It works perfectly for my use case, only requiring to use {title} with curly braces instead of title for the first example, which in a way is even more predictable. Those functions should really have better visibility in the documentation 😄
  1. Load more activity
  • Create New...