Jump to content

nbcommunication

Members
  • Posts

    201
  • Joined

  • Last visited

  • Days Won

    2

Everything posted by nbcommunication

  1. Thanks @Roych, Will get a similar setup in place and see if I can get it replicated and debugged. Cheers, Chris
  2. Hi @Roych, Just done a test and the pasted code isn't the problem for me, so I'm the problem likely lies in #2 or #3. I also note the error messages from the other thread, particularly the mention of WireHttp, which suggests the module is falsely parsing a URL and trying to request oEmbed data from it. The setup of the body field would be a massive help in debugging this - if you are able to screenshot the Details and Input tabs, that would be brilliant. Cheers, Chris
  3. Hi @Roych, Thanks for letting me know, I'll look into this in the morning - likely something to do with the regex. To aid in debugging, can you please: 1. Confirm that this is the code that was pasted into the body field using the source option: <iframe src="https://docs.google.com/forms/d/e/1FAIpQLSczjrIs47Nd9iJiPFtTNH_sRJgdBOXvw29vJFKLo20jJJwIvw/viewform?embedded=true" width="640" height="4264" frameborder="0" marginheight="0" marginwidth="0">Nalaganje …</iframe> 2. Post the Markup you are using in the module config. 3. Let me know what other textformatters are used on the body field and their order? That should hopefully allow me to debug the problem. Cheers, Chris
  4. Thanks @DV-JF, The auto-renewal of tokens has been working for me so far, I have 10+ production installs I think. Where it may not work is if there isn't an API request the week prior to the renewal date. I did limit cache time to 1 week, so I'm hoping this won't happen on production sites. This did happen on my sandbox install which I used for testing the module - came back several months later to a token unable to renew, so just removed then re-added the account. Therefore, it is a remote possibility that renewal won't happen, but very unlikely on a site that is getting traffic. No need for coffee, just delighted to get feedback! Cheers, Chris
  5. Hi, This module does not appear to be compatible with the latest master. Seems the refactoring of InputfieldDatetime since 3.0.148 is the culprit. Failed to construct module: \InputfieldDatetimeAdvanced - Method InputfieldDatetimeAdvanced::getInputFormats does not exist or is not callable in this context I added getInputFormats() from a previous version of InputfieldDatetime and that appears to have fixed it: <?php /** * Get the input format string for the user's language * * thanks to @oliverwehn (#1463) * * @param bool $getString Specify true to get a format string rather than an array * @return array|string of dateInputFormat timeInputFormat * */ protected function getInputFormats($getString = false) { $inputFormats = array(); $language = $this->wire('user')->language; $useLanguages = $this->wire('languages') && $language && !$language->isDefault(); foreach(array('date', 'time') as $type) { $inputFormat = ''; if($useLanguages) { $inputFormat = trim($this->getSetting("{$type}InputFormat{$language->id}")); } if(!strlen($inputFormat)) { // fallback to default language $inputFormat = $this->get("{$type}InputFormat"); } $inputFormats[] = $inputFormat; } if($getString) return trim(implode(' ', $inputFormats)); return $inputFormats; } Cheers, Chris
  6. Render oEmbed data from YouTube/Vimeo URLs... or TextformatterVideoEmbed for power users. https://github.com/nbcommunication/TextformatterVideoMarkup The use case... On an upcoming project, we want to be able to render YouTube/Vimeo URLs as thumbnail images, that when clicked open up in a (UIkit) lightbox. Additionally, we want to be able to specify the thumbnail image - as part of a RepeaterMatrix block which contains a URL field (video) and an Image field (thumb). The result is this module, which allows you to specify the markup used to render the oEmbed data: The formatter can be used on any Text field e.g. Text, Textarea (CKEditor or not), URL etc. Global configuration options are available (e.g. rel=0), based on TextformatterVideoEmbedOptions. An 'empty value' can be specified for URLs that do not return data from the oEmbed endpoint The render method is hookable, allowing you to customise rendering on a per page, per field basis Plenty more information here 🙂 https://github.com/nbcommunication/TextformatterVideoMarkup/blob/master/README.md Back to the use case... How do we render the thumbnail and then use the image from our Image field? In the module config Markup field: <figure data-uk-lightbox> <a href="{url}" data-poster="{thumbnail_url}" data-attrs="width: {width}; height: {height}"> <img src="{thumbnail_url}" alt="{title}"> </a> </figure> Then in site/ready.php <?php $wire->addHookBefore('TextformatterVideoMarkup::render', function(HookEvent $event) { // Arguments (for info) $tpl = $event->arguments(0); // string: The markup template $data = $event->arguments(1); // array: The oEmbed data $url = $event->arguments(2); // string: The requested URL $emptyValue = $event->arguments(3); // string: The empty value used if no data is returned // Object properties (for info) $page = $event->object->page; // Page: The page $field = $event->object->field; // Field: The field $html = $event->object->html; // bool: Is it HTML being parsed, or plain text? // Replace the thumbnail image if($field->name == 'video' && $page->hasField('thumb') && $page->thumb) { $data['thumbnail_url'] = $page->thumb->url; $event->arguments(1, $data); } }); The module requires PW >= 3.0.148 and PHP >= 7. It probably doesn't need to, but the expectation is that power users will be able to meet these requirements! The module is also Beta - please don't use in production yet. I suspect there will be edge cases related to the changes I made to the URL regexes from TextformatterVideoEmbed - so far though they are working for me. If you come across any issues please let me know! Cheers, Chris
  7. That didn't take long - @kkalgidim, if you re-download (should now be version 1.4.1) then hopefully that'll work for you.
  8. @kkalgidim - looks like PHP < 5.6. Arrays in class constants are allowed from 5.6 onwards. You really should be on 7.2 or above! Given that ProcessWire supports 5.4 and above, I'll look into tweaking this so you can install. Cheers, Chris
  9. Hi @kkalgidim, What PHP version are you running? Cheers, Chris
  10. Updated to 1.0.1 (Stable), mainly reducing hook priority < 200 so it runs before ProCache.
  11. Hi @jonatan, You would probably need to do something like: var resetPagination = new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", window.location.href); oReq.addEventListener(xhr, "load", function() { if (xhr.status === 0 || xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { resolve(xhr); } else { reject(); } }); oReq.addEventListener(xhr, "error", function() { reject(); }); oReq.addEventListener(xhr, "timeout", function() { reject() }); xhr.send(); }); resetPagination.then(function() { instagram.init(); }, function() { // Error }); If I remember correctly, this kind of plain Javascript request doesn't actually register as an ajax request, so $config->ajax returns false, and the pagination will be reset. I'm not sure this is the best way to go though - I'd probably be trying to store the requested data in an array/object and then just pull it from there when the page changes e.g. var requests = {}, var items = []; // On page change var page = "page" + pageNum; // whereever you get this info from if(page in requests) { items = requests[page]; } else { items = "an AJAX request to get the next page of items"; requests[page] = items; } It will probably require a bit of reworking of your code, but it's a far better way to go. You shouldn't have to request the data more than once - store it! Hope that helps, Chris
  12. Hi, Update to 1.3.3 - tweak to how the cache name/key is generated, shouldn't effect anything... Cheers, Chris
  13. Hi @jonatan, Nice one! No thanks required - you've highlighted issues which has made the module significantly better! Cheers, Chris
  14. Hi @LAPS, I've added a setCampaign() method. It is essentially an alias for addTags(), but takes multiple string arguments instead of an array like addTags(). <?php $mg->to("an@email.com") ->subject"A Subject") ->setCampaign("campaign1", "campaign1-a", "testing") ->bodyHTML("<p>A Message</p>") ->send(); It also enables open and click tracking by default, as this is presumably behaviour you want for campaign analytics. If this isn't required, using addTags() is the way to go, but you can also pass false as the first argument to disable this behaviour and use whatever tracking settings you have by default. Cheers, Chris
  15. Hi @LAPS, OK - I'll take a look at this tomorrow and implement a method for this. Cheers, Chris
  16. Hi @LAPS, Campaigns isn't something I have experience with, but is it not a reporting system in Mailgun for analysing/comparing messages? From what I can gather, this uses message data and/or tags to do this, both of which are already implemented in the module. There isn't anything available in the API specifically related to campaigns. Is there another implementation you'd like to see, something like a setCampaign() method which adds the message data? Cheers, Chris
  17. The API issue with account_type has been resolved, so I've reinstated this in the module, version 1.3.2.
  18. Hi @jonatan, I'm not sure what more I'd be able to do to directly help here. At this point your best bet is to add a bunch of logging to try and determine where the problem is occurring. For example: // Attach listener console.log("Should only log once on init"); $(window).scroll(this.debounce(function() { console.log("Probably not needed, this will log during scrolling"); if(!this$1.busy && $(window).scrollTop() == $(document).height() - $(window).height()) { console.log("User has reached the bottom."); // When this is logged I should really only see one at the time due to the debounce function and the 'busy' switch. this$1.get(); // Similar logging to be added to get() } }, 256)); <?php $instagram = $modules->get("InstagramBasicDisplayApi"); // The next url is stored in session. The code below will log it. $instagram->log($session->getFor($instagram, "next$page->id")); if($config->ajax) { $instagram->log("You may want to log here to test how often AJAX requests are made"); header("Content-Type: application/json"); echo $instagram->getMedia(); // ["json" => true] is inferred by $config->ajax die(); } By using logging to trace the process, you will hopefully be able to figure out what is causing the issue. I suspect it is still the listener. Cheers, Chris
  19. Hi @houseofdeadleg, Seems to be an issue on the APIs end with the "account_type" field. There's an open bug report: https://developers.facebook.com/support/bugs/1127960260870980/ I've updated the module to remove this field for the time being. If you update, it should work for you. Cheers, Chris
  20. Hi @jonatan, Aye it is the listener. Try the following: var instagram = { $el: {}, // Where the items go $loading: {}, // The loading spinner total: 0, // The total number of items busy: false, init: function() { this.$el = $("#instagram"); if(!this.$el.length) return; // Add the spinner this.$el.after("<div id='instagram-loading'><span class='loadertextcontainer'><b class='loadertext'>loading<span>_</span><span>_</span><span>_</span></b></span></div>"); this.$loading = $("#instagram-loading"); this.$loading.hide(); // Get the first batch of items this.get(); var this$1 = this; // Attach listener $(window).scroll(this.debounce(function() { if(!this$1.busy && $(window).scrollTop() == $(document).height() - $(window).height()) { this$1.get(); } }, 256)); }, debounce: function(func, wait, immediate) { var timeout; if(wait === void 0) wait = 256; return function() { var context = this; var args = arguments; var later = function() { timeout = null; if(!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if(callNow) func.apply(context, args); }; }, get: function() { var this$1 = this; // Show spinner this$1.$loading.show(); // Prevent requests while this one is running this$1.busy = true; // 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, index) { switch(item.type) { case "VIDEO": items.push(this$1.renderItemVideo(item.src, item.alt, item.href, index, item.poster)); break; case "CAROUSEL_ALBUM": var out = ""; for(var i = 0; i < item.children.length; i++) { var src = item.children[i].src; if (i <= 0) { out += "<a class='carousel-item active fb-gallery-ig' data-height='600' data-caption='" + item.href + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" } else { out += "<a class='carousel-item fb-gallery-ig' data-height='600' data-caption='" + item.href + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" } +"</a>"; } items.push( "<div data-interval='3000' class='carousel slide carousel-fade col-12 col-sm-6 col-md-4 p-3 pics' data-ride='carousel' id='instagram-item-" + (this$1.total + index) + "'>" + "<div class='carousel-inner'>" + out + "<a class='carousel-control-prev' href='#instagram-item-" + (this$1.total + index) + "' 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='#instagram-item-" + (this$1.total + index) + "' 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, item.href, index)); break; } }); var count = items.length; if(count) { // Append items to the container this$1.$el.append(items.join("")); // Allow more requests this$1.busy = false; // Update total this$1.total = this$1.total + count; } }, function(e) { this$1.$loading.hide(); console.log(e); // ERROR }) }, renderItem: function(src, alt, href, index, poster) { return "<div class='col-12 col-sm-6 col-md-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<a class='fb-gallery-ig' data-height='600' data-caption='" + href + "' href='" + src + "' data-alt='" + alt + "'>" + "<div style='background-image: url(" + src + ")' class='pics'>" + "</div>" + "</a>" + "</div>"; }, renderItemVideo: function(src, alt, href, index, poster) { return "<div class='col-12 col-sm-6 col-md-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<a class='fb-gallery-ig' data-height='600' data-caption='" + href + "' href='" + src + "'>" + "<div style='background-image: url(" + poster + ")' class='pics'>" + (src !== poster ? "<div class='overlay-video-icon'></div>" : "") + "</div>" + "</a>" + "</div>"; } }; $(document).ready(function() { instagram.init(); }); I've added a "busy" variable which prevents requests when one is already being made, and a debounce function to control the scroll handler which has been moved to init(). Works for me. Cheers, Chris
  21. Hi @jonatan, It is probably related to the listener - when I get a chance I'll fire up the code and see if I can figure out a solution. Cheers, Chris
  22. Nice one. Yeah, my implementation using UIkit's Scrollspy component isn't great to be honest, but I didn't want to use a 3rd-party plugin for the example. Your best bet would be to look for a jQuery plugin for handling the scroll-to-bottom thing, I'm sure something will exist! Basically, when the scroll-to-bottom is triggered, you need to call the get() function. This should replace the // Attach Listener part of my example. Cheers, Chris
  23. Hi @jonatan, Try putting the if($config->ajax) code at the top of your template file, before anything else is being output. This will ensure that what is being returned is purely JSON. You can log failed requests like so: $.getJSON(window.location.href, function(data) { // ... }).fail(function(jqXHR, textStatus, errorThrown) { console.log(jqXHR, textStatus, errorThrown); }); I think what you'd probably be getting is a JSON parse error due to the html being output prior to the AJAX response. The reason for the next results to not be explicit in the way you've suggested is that the "next" link provided by the API is tied to the original request, and since pagination is likely to be handled by AJAX requests, it makes sense to just build this in as I've done. Cheers, Chris
  24. 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, index) { switch(item.type) { case "VIDEO": items.push(this$1.renderItem(item.poster, item.alt, item.src, index)); 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='instagram-item-" + (this$1.total + index) + "'>" + "<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, item.src, index)); break; } }); var count = items.length; if(count) { // Append items to the container this$1.$el.append(items.join("")); // 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, index) { return "<div class='col-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<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
  25. Hi @jonatan, I'm making good progress on this but not finished yet. Discovered that there's a hidden "limit" API option which has necessitated a bit of a rewrite. May get to it this weekend (weather is looking good though, so might be out in the garden!), if not on Monday. Cheers, Chris
×
×
  • Create New...