Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 05/24/2020 in all areas

  1. Combine the power of ProcessWire selectors and SQL Differences to previous RockFinder modules RockFinder3 comes with extensive docs ? RF3 supports chaining: $RockFinder3->find("template=foo")->addColumns(['foo']). RF3 fully supports multi-language. RF3 makes it super-easy to add custom columnTypes. RF3 makes it easier to use custom SQL statements. No bloat! The module does just do one thing: Finding data. Differences to findRaw Background: RockFinder has been there before findRaw existed findRaw makes it a little easier to query page fields, but RockFinder is more flexible in that regard RockFinder might be faster than findRaw: see here DOCS & DOWNLOAD: https://github.com/baumrock/rockfinder3 Thanks for using RockFinder3. If you find RockFinder3 helpful consider giving it a star on github or saying thank you. I'm also always happy to get feedback in the PW forum! Happy finding
    8 points
  2. ProcessWire 3.0.157 on the development branch continues the trend of core refactoring that’s been happening quite a bit in 2020. Rather than doing a rewrite every few years (like some CMS projects) we instead refactor parts as we go, constantly improving and optimizing the core. This works because the core design/architecture is right where it needs to be, even 10 years in. But there’s always still bits of legacy code, and code that can be improved. So in the context of ProcessWire, refactoring means incrementally rewriting code on the inside, without changing its behavior on the outside (other than making it faster and/or more secure). This has been happening regularly over the last 10 years, and will likewise continue happening over the next 10 years and likely beyond. This week the code behind ProcessWire’s core Database and PageFinder classes got a major refactoring. This is some of the most used code in PW, as it handles everything involved in taking a selector and converting it to a database query. But it’s always been a little bit of a pain point for me because it had to build queries in a way that I thought wasn’t ideal, in order to make it possible for lots of different modular parts (mostly Fieldtype modules) to contribute to the query and for PageFinder to put it all together. It was fast and secure, but still one of those parts that felt like a little too much duct tape to me. But considering how crucial the code is, I’ve always been reluctant to make major changes, since it all worked just fine. Spending lots of years thinking about it (on and off), a desire to work out any pain points, and having better tools available (like Phpstorm and Tracy) made it possible to finally massage out this pain point. Some work still remains to be done, but it’s mostly there and I’m feeling good about it. Stuff like this is key for the maintenance and longevity of the core, and involved a lot of time and effort, but makes very little difference to users, even if it makes a lot of difference to me in maintaining the core. It would make a boring blog post for sure—lots of work and changes, but no new toys to show for it. Nevertheless, it has that feeling of a good house cleaning, even if you can't see it from the outside. The scope of changes here means that there may actually be new bugs to work out, so to be on the safe side, consider 3.0.157 to be a little more “beta” than the dev branches usually are. Though I’m running it here on processwire.com and it’s working well. Beyond the fairly major updates to the Database classes, there are also a few new Sanitizer convenience methods that are primarily variations on existing ones, but useful ones for sure. Thanks for reading and have a great weekend!
    5 points
  3. Thx for the interest everybody! I'm in contact with @elabx and @Sephiroth and we will try to build something useful and document the process so that everybody can learn from it. Similar to what ryan planned with the events fieldtype, but step by step, so it's easier to follow ?
    4 points
  4. What @Ivan Gretsky said in his second paragraph. You will want to build your backend (ProcessWire) with a RestAPI, catching/storing media from Internet on the server and delivering content/data/media to your mobile apps (hybrid, native, whatever). The apps will fetch the data from there periodically and save theses data on the local Internal/External storage of the user device. I personally use heavily SQLite as storage with my mobile apps. For the Youtube video, you can build something customized. You will not get stats about the viewers on Youtube but the end-user will have access to the media once synchronized. The idea is the following. When you upload a video on Youtube, you then fetch the video from the backend and save it on the server. Then when the app will synchronize the data, it will get and store the media on the device which will be played offline inside your app with the media player of the framework used. If the idea of catching, converting and storing the media directly from Youtube is against any Terms of Services, you could build and upload manually a compressed version of the media on the server. The goal is to have the media on the server whatever the method used... And anyway, the user will have to manage to get an access Internet from time to time to be able to download/update the app from the store or to synch the data from the backend. Also, shipping the media directly in the app (when downloaded from the app store) is a bad idea. You will ran into the issue where your binaries are limited in SIZE. Example, https://help.apple.com/app-store-connect/#/dev611e0a21f About the app development, I think you should start with an hybrid one, it will be a good first experience and will be "quite easy" to build as the learning curve will be low and, you have already the competence required for it (HTML5, JAVASCRIPT, CSS). You will have to "just" learn how to put it in a native container.
    3 points
  5. Assume you have read these: https://github.com/ryancramerdesign/ProcessWire/issues/710 https://github.com/ryancramerdesign/ProcessWire/issues/1757 He says that "whitespace is preferred in many cases", but I just don't get it - not sure how trailing whitespace has any advantage, but maybe I am missing something ?
    2 points
  6. Hey @adrian a thank you is not enough!! The console was so helpful while building the new version of RockFinder - I hope you have a use for RockFinder3 so that you get something back for your brilliant work! ?
    2 points
  7. Here's some discussions/showcases on Progressive Web Apps. The first one is particularly helpful. An alternative you could consider, if you have the time and want to pick up a new skill is to create your android app using Flutter ?
    2 points
  8. Hi All, I've brought my wee script forward to the point of functionality for my use, minus a couple of things I'll be adding, like a routine to create a list of 301 redirects that I'll be pasting into my .htaccess file on the "old" domain. I'm also going to write a separate small script to click through pages to migrate that will create a list of href's in each page's body text, so that I can see if they have to be edited. In my case, and my experience, href's can have so many variations, including links to external pages, links to internal PW pages that have been moved *internally*, and links to PW pages that have moved to an external site (e.g. the site being migrated to, using a different url). So I'm not trying to incorporate href modification into the script at this time. I realize that Adrian @adrian has created a very complex and thorough blackbox module for migrations. My script is in no way a replacement for his *much more* sophisticated module. As I mentioned, and as I've noted in the script, I wrote this for a very narrow use-case that may only be useful to me. No idea, actually. I'm eager to dig deeper and deeper into ProcessWire, and move from my own procedural code method into object-oriented code. Lot's of catching up to do, since for the last 13 years I've been managing servers, etc, and didn't code all day, every day. But my oh my, I LOVE ProcessWire. What a joy to use. ? EDIT: May 26, 2020: I've added the routines listed above to v1.1, pasted in a new response below, dated May 26. I also fixed an error with the chown action. See post further down, for the script. Peter
    2 points
  9. HelperFieldLinks Just got a new module working that is only visible to superusers, and is handy for when developing a site, or investigate someone elses. 1. It adds a shortcut link to all fields on page in the backend. The link name equals the field name, so on very large complex sites with lots of fields, it can help to quickly see what name the field has. 2. It also adds a shortcut to the used template (in the template select field under "Settings" tab). They appear on bottom right corner of the field. Any suggestions for a better module name and general feedback is welcome. ProcessWire Modules Directory: http://modules.proce...er-field-links/ Direct github download: https://github.com/s...elperFieldLinks
    1 point
  10. Hi all, Work is ramping up for Nifty Solutions so I'm looking for a seasoned PW dev/team I can pass work to on an ad-hoc basis when I'm too busy to take it on myself - fingers crossed this could be as soon as next month with the level of interest our recent marketing efforts have generated. The three most likely scenarios at the moment for websites are: Complete design and build - I'm working on a base site profile that will be the standard way sites start and would really prefer Bootstrap 4 for the design framework as it's what I'm most used to Alteration of an existing theme and new installation from an existing site profile (we have one or two customers in a Group of companies, so re-using the same theme/structure and minor alterations to colours, templates etc on those for example) Ad-hoc template changes to existing websites Since I imagine it will vary quite a bit from job to job and workload each month could be anywhere from zero to lots, hourly rate is probably sensible for the more piecemeal work as well. For new, complete websites though we are usually working off a fixed price so maybe a rough cost/time estimate of how long it might take to design and build a complete website if I were to point you in the direction of an example? I'd love to set up something with someone who's strong on the design side of things ideally and happy with both design and dev, definitely happy using hooks and maybe occasionally writing small modules for backend interfaces. Would love to set something up where I can offload work to more than one developer if possible as I realise the ad-hoc nature may mean relying on one person doesn't line up with everyone's own workload. I've not had to outsource before for development so this is all quite new to me. Would prefer someone within a few hours +- GMT as well if possible but not necessarily essential - there's some merit to having work happen overnight and magically appear in my inbox the next morning as long as there's not lots of calls going on at really odd hours too often ? All work goes through a project management system on our side so it's easy to track tasks, have discussions and log hours etc. PM me if interested, happy to answer questions via PM and I'll reply early next week.
    1 point
  11. Done ? Just realized that this update actually breaks a lot of the functionality provided by RockFinder3 - so it's critical to get an update for this! ?
    1 point
  12. Yeah, please do - I think it's going to prove quite painful if we can't generate queries that work independently. I make use of the "Selector Queries" feature on the RequestInfo panel in Tracy a LOT and it's no longer as useful. Sounds like Ryan is keen to come up with a fix, but an extra voice might help motivate him ?
    1 point
  13. Remember you have some wording in the docs that needs changing, eg "The ProcessModule of RockFinder3 does use Tracy for dumping the results, so TracyDebugger is required for the ProcessModule to run."
    1 point
  14. Sorry you find it ugly - I know it doesn't look nice, but it's one of my favorite things in Tracy - so nice to know which browser tab has the dev version vs live version of a site.
    1 point
  15. I might give the FA icons a go. The reason I didn't initially is because they aren't loaded on the frontend of a site, but these days I do load them with Tracy for the custom links in the PW Info panel anyway, so it wouldn't be any extra load now, although I think someone mentioned they were having issues because they were using FA 5 on the frontend and there was some conflict. I'll think about it later.
    1 point
  16. Well, it's not just the "created" field - the same error shows with the "name" field as well. Loving some of the options this has BTW - having those docs has really shown me what this can do! Just a thought about the Process module and the Tracy Console - I am not sure it's clear that the Process module is only needed to load Tabulator - otherwise loading the Console panel from any other page works for dumping the objects in the normal Tracy way. I do think it would be great to figure out a way to load the Tablulator script on demand in Tracy if a RF dump() is called.
    1 point
  17. Hey @bernhard - looking awesome - I think it's time I took this for a proper test spin ? Just tested my first query and got this error:
    1 point
  18. Not necessarily. You have at least two other options. If it's not a lot of work and you really need this to be sorted, you can grant someone here from the forums (not just anyone!) temporary superuser access either to the remote site or send them the original DB dump of the site before you started working on it. If you have very sensitive data in the dump/site, then this becomes a bit tricky. If you have a budget, pay someone from the forums (post in the Job's board) to sort this out for you.
    1 point
  19. @kongondo Have been studying/playing with Flutter and catching up on latest trends in CSS, Photoshop, and other stuff during "all this sh*t" (that's what we in Australia call the pandemic that will never be named). Can't "asynch await" to put new knowledge into action ?
    1 point
  20. Adding some code to expand on my post above. In /site/ready.php: // Forgot password: add verification code as a URL parameter $wire->addHookBefore('ProcessForgotPassword::renderEmailBody', function(HookEvent $event) { $url = $event->arguments(0); $verification_code = urlencode($event->arguments(1)); $url .= "&code=$verification_code"; $event->arguments(0, $url); }); // Forgot password: pre-fill verify field from URL parameter $wire->addHookBefore('ProcessForgotPassword::renderForm', function(HookEvent $event) { $form = $event->arguments(0); $form_name = $event->arguments(1); if($form_name !== 'step3') return; $verify_field = $form->getChildByName('verify'); if(!$verify_field) return; $code = $event->wire('input')->get('code'); if(!$code) return; $verify_field->description = 'Please type or paste in the code you received in your email if it is not already shown below.'; $verify_field->value = urldecode($code); }); I just set this up on a site and it seems to be working well.
    1 point
  21. omg thank you so much i was so stupid ? i changed it to this and it work foreach ($page->produkt_repeat_field as $building) { foreach ($building->r_galeria_image as $obrazok){ $thumbnail = $obrazok->size(450, 250); echo"<article>"; echo "<a href='{$obrazok->url}' class='fresco' data-fresco-caption='{$obrazok->description}' data-fresco-group-options='preload: [1,2], effects: {spinner: {show:150, hide:150 } }' data-fresco-group='kolace'><img src='$thumbnail->url' alt='obrazok'></a>"; echo"</article>"; } } i totally forgot i was looping throught repeat fields and not throught gallery, thank you sorry for my mistake
    1 point
  22. Hello, Do you think it would be possible to build a "Macro Rocorder"? A module where we click on a button and every action on admin will be saved to a file (may be with the aftersave Hook) with every detail in a way that all steps could be repeated when ever we want. Imagine you have crm project with a huge amount of templates and fields and want to reuse it on new projects... Such a solution would help. Is there something like this? If not is this possible?
    1 point
  23. That's what RockMigrations is for ? I've thought about a similar thing back in 2016 because I didn't know better (https://processwire.com/talk/topic/14603-rocksvn-brings-version-control-to-your-fields-templates/ ) but migrations are the way to go. The new migrate() function makes it very easy to build reusable setups: https://github.com/BernhardBaumrock/RockMigrations#migration-config-files
    1 point
  24. It’s not a macro recorder, but do you know the admin actions Module? https://modules.processwire.com/modules/process-admin-actions/
    1 point
  25. Just a quick update to you all know that there is a new keyboard shortcut for reloading a snippet from disk, clearing previous output, and running the code which should be a nice time saver and also prevent you from accidentally running old code in the console that hasn't been updated from the disk version when you are using an external editor. I have also rejigged the execution of code when "Run" is called vs injecting. Hopefully the visual cues of the button changing from "Run" to "Inject" and the status in the header showing "Inject @ Ready" makes it more obvious what is going to happen when. Another update is that the Dumps Recorder panel now stores its data in a file so that it will survive across sessions which may be useful if you are hooking certain session methods and other scenarios where the normal Dumps panel is not showing your bd() calls.
    1 point
  26. Hi. I had the same problem like @fliwire with changed resource property in template context not getting saved and outputted correctly. I changed the FieldtypeMystique::getBlankValue() method a bit: public function getBlankValue(Page $page, Field $field) { return new MystiqueValue($page, $page->template->fieldgroup->getField($field,true)); } API Ref: https://processwire.com/api/ref/fieldgroup/get-field/ With this change you are getting the field in context of the current page template. Then you have the correct resource filename in $field->resource. This works here in my setup, but I haven't been able to test much for now. Hope this does not break something else. Anyway, thanks for this module. Looks very promising!
    1 point
  27. template overwrite field input settings has bug. Selected resource is saving properly but when shown all inputs empty. Checked db value, db has also default input resource and home page resource. Expect only home page values. (counts in below code). {"title":"","checkbox":"","headline":"","summary":"","fieldset":"","fieldset_title":"","fieldset_description":"","another_fieldset":"","another_fieldset_title":"","another_fieldset_description":"","content":"","__json":null,"__name":"example-dive","counts":"888888888","__resource":"settings"}
    1 point
  28. Hi @ukyo I just created a PR to support easy value setting via setAndSave(). Before: After: $page->setAndSave('contact', ['str' => 'foo']); https://github.com/trk/Mystique/pull/3
    1 point
  29. Next version merged with master branch. Image field removed from example ? sorry about that. Module updated.
    1 point
  30. Ok, thx! So why is that field in the example file? For me as an enduser the experience was: "Oh, wow, that even supports image fields! Let's try it! [uploading] Hm... Doesn't work - maybe another thing that isn't working on Windows due to path issues?" ? PS: When are you planning to merge NEXT into MASTER?
    1 point
  31. Thanks @bernhard, I updated module with same finder function used on FontIconPicker module. I am using dirname function for getting module or templates folder names from file path. "Example : Dive" is title of config, "(mystique)" is name of module folder
    1 point
  32. OK - I almost thought so. Anyway, your module is a very good timesafer. It also saves resources?
    1 point
  33. It works - thank you. Great how fast you have solved this.? I once used the example file "Example-Dive". Here is an image inputfield, how does it work? When I upload a picture, nothing happens. Do you have any more practical examples or information for beginners?
    1 point
  34. I think thats PW core problem, see https://github.com/processwire/processwire-issues/issues/979
    1 point
  35. @cosmo @wbmnfktr I updated module, i tested it with fresh pw 140 dev installation and i see there is problem on https://github.com/trk/Mystique/commit/b16fdba23f508c4901aeed56e71041ad34cf8c8b and removed this function. Let me know if you still have problem ?
    1 point
  36. @cosmo @wbmnfktr @next version is not fully working version. Use published version on module directory. If you want to try @next version, don't do it on live projects ! I will check @next version with latest processwire dev version.
    1 point
  37. Nice to hear that. Processing input field there is not special check for input field. https://github.com/trk/Mystique/blob/master/InputfieldMystique.module.php#L135 Little bit worked on a base field type (basically this field type will get entered field type's database schemas and will create 1 database table for all entered fields), if i success about this field type, i will try to use input field based checks. For the moment getting and setting directly posted data.
    1 point
  38. Upon further inspection, setting the 'type' to 'InputfieldAsmSelect' does work and does save properly. Awesome.
    1 point
  39. I am using this module for SEO, LANGUAGE and ELEMENTS (uikit components) USAGE EXAMPLE : LANGUAGE On my private module, i added my custom configs path to Mystique module by using : Mystique::add('my-module-configs-path'); - Create config file <?php namespace ProcessWire; // Filename: MyModule/configs/Mystique.language.php // This options normally coming from a file array, i added 2 options for example $options = [ 'tr' => 'Türkçe', 'en' => 'English' ]; $defaultValue = 'en'; /** * Resource : MyModule => Language */ return [ 'title' => __('MyModule: Language'), 'fields' => [ 'title' => [ 'label' => __('Language title'), 'description' => __('Title of language'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'code' => [ 'label' => __('Code'), 'description' => __('Language short code'), 'type' => Mystique::SELECT, 'options' => $options, 'defaultValue' => $defaultValue, 'required' => true, 'columnWidth' => 50 ], 'flag' => [ 'label' => __('Flag'), 'description' => __('Language flag code'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'direction' => [ 'label' => __('Direction'), 'checkboxLabel' => __('Right to left'), 'description' => __('Direction of language'), 'type' => Mystique::TOGGLE_CHECKBOX, 'type_fallback' => Mystique::CHECKBOX, 'columnWidth' => 50 ], 'currency' => [ 'label' => __('Currency'), 'description' => __('Code of currency'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'symbol' => [ 'label' => __('Symbol'), 'description' => __('Symbol of currency'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'grouping_separator' => [ 'label' => __('Grouping separator'), 'description' => __('Thousand separator for amount'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'decimal_separator' => [ 'label' => __('Decimal separator'), 'description' => __('Decimal separator for amount'), 'type' => Mystique::TEXT, 'columnWidth' => 50 ], 'separator' => [ 'label' => __('Use separator'), 'checkboxLabel' => __('YES'), 'description' => __('Apply space between amount and currency symbol ?'), 'type' => Mystique::TOGGLE_CHECKBOX, 'type_fallback' => Mystique::CHECKBOX, 'columnWidth' => 33 ], 'show_decimal' => [ 'label' => __('Decimal'), 'checkboxLabel' => __('YES'), 'description' => __('Show amount decimals ?'), 'type' => Mystique::TOGGLE_CHECKBOX, 'type_fallback' => Mystique::CHECKBOX, 'columnWidth' => 33 ], 'symbol_after' => [ 'label' => __('Symbol after'), 'checkboxLabel' => __('YES'), 'description' => __('Display symbol after amount ?'), 'type' => Mystique::TOGGLE_CHECKBOX, 'type_fallback' => Mystique::CHECKBOX, 'columnWidth' => 33 ], ] ]; - Select config file from Mystique field settings - Add Mystique field to language template Access data via api (in this example mystique field name is : lang) <?php $language = $user->language; // lang is Mystique field echo 'Title : ' . $language->lang->title . '<br>'; echo 'Code : ' . $language->lang->code . '<br>'; echo 'Flag : ' . $language->lang->flag . '<br>'; echo 'Direction : ' . $language->lang->direction . '<br>'; echo 'Currency : ' . $language->lang->currency . '<br>'; echo 'Symbol : ' . $language->lang->symbol . '<br>'; echo 'Grouping separator : ' . $language->lang->grouping_separator . '<br>'; echo 'Decimal separator : ' . $language->lang->decimal_separator . '<br>'; echo 'Separator between amount and symbol : ' . $language->lang->separator . '<br>'; echo 'Show decimal : ' . $language->lang->show_decimal . '<br>'; echo 'Show symbol after amount : ' . $language->lang->symbol_after . '<br>'; Output: Title : English Code : en Flag : gb Direction : 0 Currency : GBP Symbol : £ Grouping separator : , Decimal separator : . Separator between amount and symbol : 1 Show decimal : 1 Show symbol after amount : 0
    1 point
  40. Keep the field name same, do what you want. Because all data on database in string format (i am not checking user posted data), you can set field settings by using processwire fields api and you can limit user by settings. Hook methods could be used for custom usages.
    1 point
  41. My question was less about the field definitions, but more about the data stored in a field defined by your module. E.g. say I want to change an integer field to a float field.
    1 point
  42. @BitPoet will check it, but i don't want to limit user's database options and if you need an important searchable field i am not advising to use this field, use a fully searchable field. @LostKobrakai I will add more details on readme.md file and will update this post and module page. About your question : as you see on youtube video, updating fields config file will update data entry form for admin page. If you remove field, you won't see this field on admin pages anymore. I am not caching your configs, always trying to get configs from selected config files.
    1 point
  43. Hello there, I've started using ProcessWire at work a while ago and I have been really enjoying building modular, clean and fast sites based on the CMS (at work, I usually post as @schwarzdesign). While building my first couple of websites with ProcessWire, I have written some useful helper functions for repetitive tasks. In this post I want to showcase and explain a particular function that generates a responsive image tag based on an image field, in the hope that some of you will find it useful :) I'll give a short explanation of responsive images and then walk through the different steps involved in generating the necessary markup & image variations. I want to keep this beginner-friendly, so most of you can probably skip over some parts. What are responsive images I want to keep this part short, there's a really good in-depth article about responsive images on MDN if you are interested in the details. The short version is that a responsive image tag is simply an <img>-tag that includes a couple of alternative image sources with different resolutions for the browser to choose from. This way, smaller screens can download the small image variant and save data, whereas high-resolution retina displays can download the extra-large variants for a crisp display experience. This information is contained in two special attributes: srcset - This attribute contains a list of source URLs for this image. For each source, the width of the image in pixels is specified. sizes - This attribute tells the browser how wide a space is available for the image, based on media queries (usually the width of the viewport). This is what a complete responsive image tag may look like: <img srcset="/site/assets/files/1015/happy_sheep_07.300x0.jpg 300w, /site/assets/files/1015/happy_sheep_07.600x0.jpg 600w, /site/assets/files/1015/happy_sheep_07.900x0.jpg 900w, /site/assets/files/1015/happy_sheep_07.1200x0.jpg 1200w, /site/assets/files/1015/happy_sheep_07.1800x0.jpg 1800w, /site/assets/files/1015/happy_sheep_07.2400x0.jpg 2400w" sizes="(min-width: 1140px) 350px, (min-width: 992px) 480px, (min-width: 576px) 540px, 100vw" src="/site/assets/files/1015/happy_sheep_07.1200x0.jpg" alt="One sheep"> This tells the browser that there are six different sources for this image available, ranging from 300px to 2400px wide variants (those are all the same image, just in different resolutions). It also tells the browser how wide the space for the image will be: 350px for viewports >= 1140px 480px for viewports >= 992px 540px for viewports >= 576px 100vw (full viewport width) for smaller viewports The sizes queries are checked in order of appearance and the browser uses the first one that matches. So now, the browser can calculate how large the image needs to be and then select the best fit from the srcset list to download. For browsers that don't support responsive images, a medium-sized variant is included as the normal src-Attribute. This is quite a lot of markup which I don't want to write by hand every time I want to place an image in a ProcessWire template. The helper function will need to generate both the markup and the variations of the original image. Building a reusable responsive image function Let's start with a function that takes two parameters: a Pageimage object and a standard width. Every time you access an image field through the API in a template (e.g. $page->my_image_field), you get a Pageimage object. Let's start with a skeleton for our function: function buildResponsiveImage( Pageimage $img, int $standard_width ): string { $default_img = $img->maxWidth($standard_width); return '<img src="' . $default_img->url() . '" alt="' . $img->description() . '">'; } // usage example echo buildResponsiveImage($page->my_image_field, 1200); This is already enough for a normal img tag (and it will serve as a fallback for older browsers). Now let's start adding to this, trying to keep the function as flexible and reusable as possible. Generating alternate resolutions We want to add a parameter that will allow the caller to specify in what sizes the alternatives should be generated. We could just accept an array parameter that contains the desired sizes as integers. But that is not very extendible, as we'll need to specify those sizes in each function call and change them all if the normal size of the image in the layout changes. Instead, we can use an array of factors; that will allow us to set a reasonable default, and still enable us to manually overwrite it. In the following, the function gets an optional parameter $variant_factor. // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be upscaled $default_image = $original_img->width($standard_width, ['upscaling' => false]); // the maximum size for our generated images $full_image_width = $original_img->width(); // fill the variant factors with defaults if not set if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $srcset = implode(', ', $srcset); // example usage echo buildResponsiveImage($page->my_image_field, 1200, [0.4, 0.5, 0.6, 0.8, 1, 1.25, 1.5, 2]); Note that for resizing purposes, we want to get the original image through the API first, as we will generate some larger alternatives of the images for retina displays. We also don't want to generate upscaled versions of the image if the original image isn't wide enough, so I added a constraint for that. The great thing about the foreach-loop is that it generates the markup and the images on the server at the same time. When we call $original_img->width($width), ProcessWire automatically generates a variant of the image in that size if it doesn't exist already. So we need to do little work in terms of image manipulation. Generating the sizes attribute markup For this, we could build elaborate abstractions of the normal media queries, but for now, I've kept it very simple. The sizes attribute is defined through another array parameter that contains the media queries as strings in order of appearance. $sizes_attribute = implode(', ', $sizes_queries); The media queries are always separated by commas followed by a space character, so that part can be handled by the function. We'll still need to manually write the media queries when calling the function though, so that is something that can be improved upon. Finetuning & improvements This is what the function looks like now: function buildResponsiveImage( Pageimage $img, int $standard_width, array $sizes_queries, ?array $variant_factors = [] ): string { // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be upscaled $default_image = $original_img->width($standard_width, ['upscaling' => false]); // the maximum size for our generated images $full_image_width = $original_img->width(); // fill the variant factors with defaults if not set if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $srcset = implode(', ', $srcset); return '<img src="' . $default_img->url() . '" alt="' . $img->description() . '" sizes="' . $sizes_attribute . '" srcset="' . $srcset . '">'; } It contains all the part we need, but there are some optimizations to make. First, we can make the $sizes_queries parameters optional. The sizes attribute default to 100vw (so the browser will always download an image large enough to fill the entire viewport width). This isn't optimal as it wastes bandwidth if the image doesn't fill the viewport, but it's good enough as a fallback. We can also make the width optional. When I have used this function in a project, the image I passed in was oftentimes already resized to the correct size. So we can make $standard_width an optional parameter that defaults to the width of the passed image. if (empty($standard_width)) { $standard_width = $img->width(); } Finally, we want to be able to pass in arbitrary attributes that will be added to the element. For now, we can just add a parameter $attributes that will be an associative array of attribute => value pairs. Then we need to collapse those into html markup. $attr_string = implode( ' ', array_map( function($attr, $value) { return $attr . '="' . $value . '"'; }, array_keys($attributes), $attributes ) ); This will also allow for some cleanup in the way the other attributes are generated, as we can simply add those to the $attributes array along the way. Here's the final version of this function with typehints and PHPDoc. Feel free to use this is your own projects. /** * Builds a responsive image element including different resolutions * of the passed image and optionally a sizes attribute build from * the passed queries. * * @param \Processwire\Pageimage $img The base image. * @param int|null $standard_width The standard width for this image. Use 0 or NULL to use the inherent size of the passed image. * @param array|null $attributes Optional array of html attributes. * @param array|null $sizes_queries The full queries and sizes for the sizes attribute. * @param array|null $variant_factors The multiplication factors for the alternate resolutions. * @return string */ function buildResponsiveImage( \Processwire\Pageimage $img, ?int $standard_width = 0, ?array $attributes = [], ?array $sizes_queries = [], ?array $variant_factors = [] ): string { // if $attributes is null, default to an empty array $attributes = $attributes ?? []; // if the standard width is empty, use the inherent width of the image if (empty($standard_width)) { $standard_width = $img->width(); } // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be // upscaled if the desired width is larger than the original $default_image = $original_img->width($standard_width, ['upscaling' => false]); // we won't create images larger than the original $full_image_width = $original_img->width(); // fill the variant factors with defaults if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $attributes['srcset'] = implode(', ', $srcset); // build the sizes attribute string if ($sizes_queries) { $attributes['sizes'] = implode(', ', $sizes_queries); } // add src fallback and alt attribute $attributes['src'] = $default_image->url(); if ($img->description()) { $attriutes['alt'] = $img->description(); } // implode the attributes array to html markup $attr_string = implode(' ', array_map(function($attr, $value) { return $attr . '="' . $value . '"'; }, array_keys($attributes), $attributes)); return "<img ${attr_string}>"; } Example usage with all arguments: echo buildResponsiveImage( $page->testimage, 1200, ['class' => 'img-fluid', 'id' => 'photo'], [ '(min-width: 1140px) 350px', '(min-width: 992px) 480px', '(min-width: 576px) 540px', '100vw' ], [0.4, 0.5, 0.6, 0.8, 1, 1.25, 1.5, 2] ); Result: <img class="img-fluid" id="photo" srcset="/site/assets/files/1/sean-pierce-1053024-unsplash.480x0.jpg 480w, /site/assets/files/1/sean-pierce-1053024-unsplash.600x0.jpg 600w, /site/assets/files/1/sean-pierce-1053024-unsplash.720x0.jpg 720w, /site/assets/files/1/sean-pierce-1053024-unsplash.960x0.jpg 960w, /site/assets/files/1/sean-pierce-1053024-unsplash.1200x0.jpg 1200w, /site/assets/files/1/sean-pierce-1053024-unsplash.1500x0.jpg 1500w, /site/assets/files/1/sean-pierce-1053024-unsplash.1800x0.jpg 1800w, /site/assets/files/1/sean-pierce-1053024-unsplash.2400x0.jpg 2400w" sizes="(min-width: 1140px) 350px, (min-width: 992px) 480px, (min-width: 576px) 540px, 100vw" src="/site/assets/files/1/sean-pierce-1053024-unsplash.1200x0.jpg" alt="by Sean Pierce"> Now this is actually too much functionality for one function; also, some of the code will be exactly the same for other, similar helper functions. If some of you are interested, I'll write a second part on how to split this into multiple smaller helper functions with some ideas on how to build upon it. But this has gotten long enough, so yeah, I hope this will be helpful or interesting to some of you :) Also, if you recognized any problems with this approach, or can point out some possible improvements, let me know. Thanks for reading!
    1 point
  44. Hi There! When linking text using the rich text editor, you get a dialog letting you specify an external url or chose an internal page/file as link destination. I'm looking for a FieldType providing this exact behavior. With the default installation I can only chose between the URL and the Page field type. And the module repository does not provide an existing solution. Is there a simple solution for this? Or would I have to implement my own FieldType module? Regards, Marco
    1 point
  45. Just updated to 1.0.3 with some fix and new feature. fixed issue with template edit link added support for repeater fields http://modules.processwire.com/modules/helper-field-links/
    1 point
×
×
  • Create New...