Robin S Posted March 26, 2021 Share Posted March 26, 2021 A Fieldtype for dynamic options that are generated at runtime via a hook. Configuration Inputfield type You can choose from a range of inputfield types on the Details tab of a Dynamic Options field. Your field will return a string or an array depending on if the selected input field type is for "single item selection" or "multiple item selection". Maximum number of items You can define a maximum number of items the field is allowed to contain. The core inputfields supported by this module will become disabled once the limit is reached. This option is only applicable if you have selected an inputfield type that is for multiple item selection. Format as Pagefile/Pageimage object(s) If the field will store paths/URLs to Pagefiles/Pageimages then you can enable this option to have the formatted value be a Pagefile/Pageimage object for "single" fields or an array of Pagefile/Pageimage objects for "multiple" fields. There is a related Select Images inputfield module that allows you to visually select image thumbnails. Defining selectable options Selectable options for a Dynamic Options field should be set in a FieldtypeDynamicOptions::getSelectableOptions hook in /site/ready.php. The hook should return an array of options as 'value' => 'label'. An example hook is shown on the Details tab of a Dynamic Options field: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); if($field->name === 'your_field_name') { $event->return = [ 'red' => 'Red', 'green' => 'Green', 'blue' => 'Blue', ]; } }); Formatted value If a Dynamic Options field uses a "single" input type then its formatted value is a string, and if it uses a "multiple" input type then its formatted value is an array. The unformatted value of a Dynamic Options field is always an array. Also see the Configuration section above for description of an option to have the formatted value be Pagefile/Pageimage object(s). Examples of possible uses $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // Select from the "files" field on the page if($field->name === 'select_files') { $options = []; foreach($page->files as $file) { // Value is basename, label is description if one exists $options[$file->basename] = $file->get('description|basename'); } $event->return = $options; } // Select from files in a folder if($field->name === 'select_folder_files') { $options = []; $path = $event->wire()->config->paths->root . 'my-folder/'; $files = $event->wire()->files->find($path); foreach($files as $file) { // Value is full path, label is basename $options[$file] = str_replace($path, '', $file); } $event->return = $options; } // Select from non-system templates if($field->name === 'select_template') { $options = []; foreach($event->wire()->templates as $template) { if($template->flags & Template::flagSystem) continue; $options[$template->id] = $template->name; } $event->return = $options; } // Select from non-system fields if($field->name === 'select_field') { $options = []; foreach($event->wire()->fields as $field) { if($field->flags & Field::flagSystem) continue; $options[$field->id] = $field->name; } $event->return = $options; } // Select from FormBuilder forms if($field->name === 'select_formbuilder_form') { $form_names = $event->wire()->forms->getFormNames(); // Use form names as both keys and values $event->return = array_combine($form_names, $form_names); } }); https://github.com/Toutouwai/FieldtypeDynamicOptionshttps://processwire.com/modules/fieldtype-dynamic-options/ 22 Link to comment Share on other sites More sharing options...
szabesz Posted March 26, 2021 Share Posted March 26, 2021 This is great idea indeed. Thanks for sharing! 1 Link to comment Share on other sites More sharing options...
3fingers Posted March 26, 2021 Share Posted March 26, 2021 Such a great module, very useful indeed! 2 Link to comment Share on other sites More sharing options...
ukyo Posted March 26, 2021 Share Posted March 26, 2021 Nice module, i added this feature to Mystique module next version. You can set a fallback function as a config. Here an example config. Also next version will support most of ProcessWire selectors. You can check or test it from next branch. 3 Link to comment Share on other sites More sharing options...
Robin S Posted July 23, 2021 Author Share Posted July 23, 2021 v0.1.3 released. This version adds options to set a limit to the number of items that may be selected. When the limit is reached the supported core "multiple" inputfields become disabled. It adds an option for the formatted value to be Pagefile/Pageimage object(s) where that would be relevant. And it integrates with the newly released Select Images module to create a kind of "image reference" field. See the updated readme for more detail. 3 Link to comment Share on other sites More sharing options...
LMD Posted February 10, 2023 Share Posted February 10, 2023 Thanks for this great module! I was wondering if it would be possible to add support for making it work with custom image/file fields templates? At the moment the option to enable it is crossed out in FieldtypeFile modeule settings (in PW 3.0.211 dev). Link to comment Share on other sites More sharing options...
Robin S Posted February 10, 2023 Author Share Posted February 10, 2023 8 hours ago, LMD said: At the moment the option to enable it is crossed out in FieldtypeFile modeule settings (in PW 3.0.211 dev). Probably it applies to the strikethrough to any non-core fieldtypes because it can't guarantee 100% compatibility. Did you try ticking the checkbox anyway? It seems to work when I did some quick testing. Link to comment Share on other sites More sharing options...
LMD Posted February 11, 2023 Share Posted February 11, 2023 13 hours ago, Robin S said: Probably it applies to the strikethrough to any non-core fieldtypes because it can't guarantee 100% compatibility. Did you try ticking the checkbox anyway? It seems to work when I did some quick testing. Well, this is embarassing, I did actually try it yesterday and it didn't work, but I tried again this morning and it works just fine now. I don't know what changed - maybe I forgot to save something before. Sorry about that! 1 Link to comment Share on other sites More sharing options...
LMD Posted March 25, 2023 Share Posted March 25, 2023 (edited) Update: SOLVED! Never mind, I had moved a bunch of hooks to admin.php, including the DynamicOptions hook, which is why it stopped working on the frontend. I have put the hook back in ready.php and works as expected. Of course that makes perfect sense in retrospect. [--- Snipped description of the problem, it was a wild goose chase! --] Edited March 25, 2023 by LMD I solved the issue, it was me... I was the problem! 1 Link to comment Share on other sites More sharing options...
LMD Posted April 1, 2023 Share Posted April 1, 2023 Not a problem, this time, but an FYI and a solution in case anybody needs it. When using a Dynamic Options field as an images/files custom field, the 'page' value of 'arguments(0)' is not the page being edited, but instead it's a dummy page used for the 'field-fieldname' template. If the actual page is required for some logic (a need I had), then this poses an issue... but there is a solution! In your 'getSelectableOptions' hook, do the following: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { $page = $event->arguments(0); // Page data (which will be wrong for our specific use case) $field = $event->arguments(1); // Field data // 'foo' is an images custom field if ($field->name === 'foo') { /* Get the page -- where we get page from will be different in the admin (edit) and frontend.*/ $page = $this->page; // the current page route (sufficient for the frontend) /* However, in the admin edit page (check the process) */ if ($page->process === 'ProcessPageEdit') { // Get the id of page being edited from $input and then use that to fetch the page $page = $this->pages->get($this->input->get('id', 'int')); } $options = []; // empty array to return if no valid page is found // If it is a valid id... continue as usual, using the $page var if ($page->id) { if ($page->template->name === 'gallery') { $options = ['foo' => "Foo", 'bar' => "Bar"]; } else { $options = ['lorem' => "Lorem", 'ipsum' => "Ipsum"]; } } $event->return = $options; } // ... other fields as required can use the $page from args }); No idea if there is a different way, but this method is fairly simple, besides, it's not going to be a common issue. Hope this helps somebody. Here's the Tracy dump of the custom field "page" for the curious: ProcessWire\DefaultPage id: 0 name: '' parent: '' status: 'corrupted' template: 'field-images' caption: '' foo: => '' data: array 'caption' => '' 'foo' => '' 1 Link to comment Share on other sites More sharing options...
eydun Posted January 23 Share Posted January 23 I love this module. Have you been able to make it work in a combo-field? Link to comment Share on other sites More sharing options...
Robin S Posted January 23 Author Share Posted January 23 6 hours ago, eydun said: Have you been able to make it work in a combo-field? No, this module is a fieldtype, and Combo is also a fieldtype. There isn't such a thing as one fieldtype module working inside another fieldtype module. But you can easily add both a Dynamic Options field and a Combo field to your template so that you can use them together. You can even have the Dynamic Options inputfield adjacent to any particular Combo subfield in Page Edit thanks to the "Custom form placement" option for Combo subfields: 1 Link to comment Share on other sites More sharing options...
eydun Posted January 24 Share Posted January 24 Yes, "Custom form placement" was a great addition to combo-fields. Link to comment Share on other sites More sharing options...
ttttim Posted July 24 Share Posted July 24 I have a strange issue where a Dynamic Options field returns empty when called from a page parser within the AppApi in ProcessWire. When I do the same call using Tracy Debugger, it works perfectly. I'm simply using $page->field_name to output a single value. I've tried some debugging and found that in the AppApi context, the ___formatValue method returns an empty $value. However, I don't fully understand the underlying structure of how field data is retrieved in ProcessWire. Has anyone encountered this issue or can explain why this might be happening? Any help on understanding or resolving this would be appreciated. Link to comment Share on other sites More sharing options...
Robin S Posted July 24 Author Share Posted July 24 @ttttim, as a test you could try $page->getUnformatted('field_name') and see if that is also empty. That will tell you if the issue relates to the formatValue() method or not. It's just a guess, but the saved value might be failing the test at FieldtypeDynamicOptions::sanitizeValue(). This checks the saved value against the allowed values as defined in your hook to FieldtypeDynamicOptions::getSelectableOptions(). If you temporarily change that method so that it doesn't do any sanitizing... public function sanitizeValue(Page $page, Field $field, $value) { return $value; } ...and find that you are then able to get the field value via AppApi then it means that something about AppApi is causing your hook to not return the expected selectable options. Like, maybe $page and $field are not what you would expect them to be inside the hook. You should be able to use Tracy to debug what is going on inside your hook when AppApi is involved. Link to comment Share on other sites More sharing options...
elabx Posted September 3 Share Posted September 3 Hi @Robin S! Does setting the default value in the field setting help you to set the default selection option? (I am specifically using Radios input) Thanks for this great module! 1 Link to comment Share on other sites More sharing options...
Robin S Posted September 3 Author Share Posted September 3 2 hours ago, elabx said: Does setting the default value in the field setting help you to set the default selection option? (I am specifically using Radios input) I haven't tried that before, but I just tried it now and it does work. For "Default value" you have to enter the value of the dynamic option rather than the label, and the field has to be "Required" as per the default value note. P.S. I just released v0.1.8 which fixes a minor config fields bug, so you might like to upgrade. 1 Link to comment Share on other sites More sharing options...
TomPich Posted September 12 Share Posted September 12 Hey Robin, Thanks for your work. It looks like this module would fit my need for something – could you confirm please? I need to retrieve data from an external database (I’m okay with thay), something like an array of id => value. I’d like the select options to show the value, and then store the pair id / value in the field. Can I do that with this module? Link to comment Share on other sites More sharing options...
Robin S Posted September 12 Author Share Posted September 12 10 hours ago, TomPich said: I need to retrieve data from an external database (I’m okay with thay), something like an array of id => value. I’d like the select options to show the value, and then store the pair id / value in the field. Can I do that with this module? Yes, you could do that. The fieldtype just needs an array of options returned from a hook to FieldtypeDynamicOptions::getSelectableOptions() and there are no constraints on how that array is generated. The fieldtype only stores the value, but if you needed to look up the label that goes with a value you could just run the same code that generates the options array and then get the label for any value. One way you might do this is via a method for a custom Page class that returns the selectable options - that way you don't need to repeat any code. So you would add a method like getDynamicOptions() to the Page class that the Dynamic Options field is used with. Your hook would then be: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); if($field->name === 'your_field_name') { /* * Get an array of options as $value => $label from the custom Page class method * * If you have more than one Dynamic Options field on the page that you need * to distinguish between then you could pass the field name as an argument to the method * e.g. $page->getDynamicOptions('your_field_name') * */ $options = $page->getDynamicOptions(); // Set that options array as the event return $event->return = $options; } }); And to look up the label for the selected option in the template file: // Get the label for the selected option $options = $page->getDynamicOptions(); $label = $options[$page->your_field_name]; This assumes your field is for single options. If it's for multiple options then you would loop over the field value and look up each label from the $options array. 1 Link to comment Share on other sites More sharing options...
TomPich Posted September 14 Share Posted September 14 Thank you so much @Robin S. Your answer is more than I expected... That’s what I love about you guys, in this forum. I don’t get only the answer I was asking for... I get detailed help that anticipates my need!😊 2 Link to comment Share on other sites More sharing options...
mel47 Posted November 17 Share Posted November 17 Hello! I used this module to link images from different fields. It "kind-of" working. Meaning, thumbnails are well created. But some images are not saved. I realized that some images get renamed and have so many variations. Is it possible it's because I add images to imagefield on page 1065 and PW tries to create all versions again and again? How I can create an array of images that will not have this behavior? Hope it's clear... Thanks Mel $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); //if we need 2 image_selection make a condition for page // The Dynamic Options field $field = $event->arguments(1); //images on parameters page $par = $event->wire()->pages(1065)->images; //last image for webinars and adding them to previous images foreach ($event->wire()->pages(1042)->content_blocks->find("repeater_matrix_type=2") as $p) { $all = $par->add($p->images->last()); } // For a field if($field->name === 'image_selection') { $options = []; // Get Pageimages within the "images" field on the home page foreach($all as $image) { // Add an option for each Pageimage // When the key is a Pageimage URL the inputfield will automatically create a thumbnail // In this example the label includes the basename and the filesize /** @var Pageimage $image */ $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } $event->return = $options; } }); Link to comment Share on other sites More sharing options...
Robin S Posted Monday at 07:30 AM Author Share Posted Monday at 07:30 AM @mel47, I didn't follow everything in your post, but Dynamic Options doesn't rename your images or create any variations. Looking at your code, you want to avoid doing this: 10 hours ago, mel47 said: $par->add($p->images->last()); Because $par is a Pageimages object and every Pageimages object is bound to a particular page (Page A), so when you add images to it from a different page (Page B) then the image files will be copied from from Page B's folder in /site/assets/files/ to Page A's folder, and I doubt that's what you intend. I suggest you rewrite your code so that you only add the image information to the $options array. Something like: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { $page = $event->arguments(0); $field = $event->arguments(1); $pages = $event->wire()->pages; if($field->name === 'image_selection') { $options = []; foreach($pages(1065)->images as $image) { $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } foreach($pages(1042)->content_blocks->find("repeater_matrix_type=2") as $p) { $image = $p->images->last(); if(!$image) continue; $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } $event->return = $options; } }); Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now