Jump to content
MarkE

Interactive updating of select inputfields

Recommended Posts

Please note that the updated version of this script and any other associated info is now at https://github.com/MetaTunes/Form-update

I had a need to interactively update the page choices in a multi-choice page select field. I chose to do this with a general-purpose piece of jQuery. By combining it with a InputfieldPage::getSelectablePages hook, you can get the trigger field to alter the selectable pages interactively.  I have also found this to be useful in a number of other situations - e.g. updating a RuntimeMarkup field for changes on a page. There may be more elegant ways of achieving this (I'm open to suggestions), but in case it is useful to others, I'll post it here. Hopefully the comments in the script are self-explanatory and describe how to use it. Note that there are several console.log statements to help with debugging, which you can remove once happy with its operation.

Happy to answer any questions (if I can 😉 ). Also, if anyone can explain how to get it working fully with checbox/toggle ad radio buttons, I would be grateful.

/* Script to refresh a form content when an element gets changed
To work across all admin pages, this script needs to be loaded in admin.php
    – add the line $config->scripts->add($config->urls->templates . "scripts/form-update.js"); before the final require in templates/admin.php.
Typical use is to modify other elements based on a select drop-down change
The trigger element can have the following data attributes assigned to it (typically set these with $myInputfield->attr() in a module or hook):
* data-action="form-update" : Required to run the script.
* data-update-target="#myid1" : Required - the element to change. Note that this should not be the whole form, otherwise .find(target) will not find it.
* data-confirm="Some confirmation text": Optional - if you want to show a confirmation before the update, this holds the text to display. If absent, there will be no confirmation dialogue.
                If the user chooses ‘cancel’, the script will revert the change and terminate.
* data-alert="Some alert text": Optional – if you want to warn the user that the update cannot happen for some reason (the script will then revert the change and terminate).
* data-cache="#myid2" : Optional - if you want to cache the (changed) value, this element stores it.
* data-cache-prefix="Some prefix string" : Optional (requires data-cache) - a prefix to prepend the value stored in the cache
This currently works with the following trigger elements:
* select options
* select page (single and multiple)
* page list select (single and multiple)
* asm select
* page autocomplete (but note that data attributes must be set in the wrapper element - e.g.  $myInputfield->wrapAttr() )
* checkboxes (set attributes in wrapper as above)
but not with:
* toggle
* checkbox
* radio buttons
(These partly work - the attributes need to be in the wrapper -, but doesn't work completely as wrapper 'value' attribute is not updated by PW (always 0) )

NOTE: If you are using this with other js scripts (e.g. in a module) that listen for events in the target, you must use event delegation
(e.g. $(document).on("change","#myid", function(){}); NOT $("#myid").onchange(function(){}); ) because #myid is dynamic if it is inside the target)
 */

$(document).on('focusin', '[data-action="form-update"]', function(){
    // get the value before the element is changed
    console.log("Saving value " + $(this).val());
    $(this).data('val', $(this).val());
}).on('change','[data-action="form-update"]', function(event){
    var prev = $(this).data('val');
    var current = $(this).val();
    console.log("Prev value " + prev);
    console.log("New value " + current);
    // if trigger element has data-confirm attribute, confirm or revert and exit
    var confirmText = $(this).data('confirm');
    if (confirmText) {
        if (!confirm(confirmText)) {
            $(this).val(prev);
            return;
        }
    }
    // if trigger element has data-alert attribute, show alert and exit
    var alertText = $(this).data('alert');
    if (alertText) {
            alert(alertText);
            $(this).val(prev);
            return;
    }
    // cache the value before proceeding (if data-cache set)
    var cache = $(this).data('cache');
    var cachePrefix = ($(this).data('cache-prefix')) ? $(this).data('cache-prefix') : '';
    $(cache).val(cachePrefix + current);
    var $form = $(this).closest('form');
    var target = $(this).data('update-target');
    console.log("Target is " + target);
    var method = $form.attr('method');
    var action = $form.attr('action');
    var data = $form.serialize();
    var encodedName;
    // .serialize() will omit select elements that do not have a 'null' option (e.g. asm select, page list select)
    // or checkboxes with nothing selected
    // so find them and add empty parameters to the data string, otherwise the page field will not be updated
    $($form.find('select, input').each(function(index){
        console.log('Select element no. ' + index + ' with name ' + $(this).attr("name") + ' has serialize = ' + $(this).serialize());
        encodedName = encodeURI($(this).attr("name"))
        if (data.search(encodedName) === -1) {
            data += ('&' + encodeURI($(this).attr("name")) + '=');
        }
    }));
    console.log("Submitted data: " + data);
    if (!method)
        method = 'get';
    if (!action)
        action = window.location.href;
    // If you want to fade the affected inputfields then assign the loading class to their wrappers with method wrapClass(loading)
    $(target).find('.loading').css({
        display: 'block',
        opacity: 0.2
    }).animate({
        opacity: 1
    }, 5000);
    // then send your request
    $.ajax(action, {
        type: method,  // type used, not method, for older versions of jquery
        data: data,
        // you can also add an error handler here if required, in case the server returns an error on the request
        success: function (data) {
            // Initial ajax just returns an array with message. Need to GET the form data.
            $.ajax(window.location.href, {
                type: 'GET', cache: false, success: function (data) {
                    // then just take the target, and replace it with the target div from the returned data
                    console.log("Returned data: " + data);
                    console.log("Updating html with: " + $(data).find(target).html());
                    $(target).html($(data).find(target).html());
                }
            });
        }
    });
});

 

  • Like 1

Share this post


Link to post
Share on other sites

Updated version here - works with more field types - just make sure you have at least 3.0.148 if you want to use it with autocomplete

/* Script to refresh a form content when an element gets changed
To work across all admin pages, this script needs to be loaded in admin.php
    – add the line $config->scripts->add($config->urls->templates . "scripts/form-update.js"); before the final require in templates/admin.php.
Typical use is to modify other elements based on a select drop-down change
The trigger element can have the following data attributes assigned to it (typically set these with $myInputfield->attr() in a module or hook):
* data-action="form-update" : Required to run the script.
* data-update-target="#myid1" : Required - the element to change. Note that this should not be the whole form, otherwise .find(target) will not find it.
* data-confirm="Some confirmation text": Optional - if you want to show a confirmation before the update, this holds the text to display. If absent, there will be no confirmation dialogue.
                If the user chooses ‘cancel’, the script will revert the change and terminate.
* data-alert="Some alert text": Optional – if you want to warn the user that the update cannot happen for some reason (the script will then revert the change and terminate).
* data-cache="#myid2" : Optional - if you want to cache the (changed) value, this element stores it.
* data-cache-prefix="Some prefix string" : Optional (requires data-cache) - a prefix to prepend the value stored in the cache
This currently works with the following trigger elements:
* select options
* select page (single and multiple)
* page list select (single and multiple)
* asm select
* page autocomplete (but note that data attributes must be set in the wrapper element - e.g.  $myInputfield->wrapAttr() )
     Note also that autocomplete only works fully with the latest master 3.0.148
* checkboxes (set attributes in wrapper as above)
* checkbox (set attributes in wrapper as above; also, if you need to set or get the value (0 or 1) you may need to use getParent() )
* toggle (but only with 0,1 formatting and 'select' input type; plus see the comments for checkbox above)
but not with:
* toggle other than as described above
* radio buttons

NOTE: If you are using this with other js scripts (e.g. in a module) that listen for events in the target, you must use event delegation
(e.g. $(document).on("change","#myid", function(){}); NOT $("#myid").onchange(function(){}); ) because #myid is dynamic if it is inside the target)
 */

$(document).on('change focusin', '[data-action="form-update"]', formUpdate); // need 'change' to catch normal select fields and 'mouseenter' for others

function formUpdate(event) {
    console.log("event type = " + event.type);
    value = inputVal(this);
    if (event.type != 'change') {   // if the input has not changed, just get the value now so that we can revert if necessary when it is changed
        console.log("Saving prev value " + value);
        $(this).data('prevVal', value);
        return;
    }
    console.log("Saving current value " + value);
    $(this).data('currVal', value);
    var prev = $(this).data('prevVal');
    var current = $(this).data('currVal');
    console.log("Prev value " + prev);
    console.log("New value " + current);

    // if trigger element has data-confirm attribute, confirm or revert and exit
    var confirmText = $(this).data('confirm');
    if (confirmText) {
        if (!confirm(confirmText)) {
            $(this).val(inputVal(this, prev));
            return;
        }
    }
    // if trigger element has data-alert attribute, show alert and exit
    var alertText = $(this).data('alert');
    if (alertText) {
        alert(alertText);
        $(this).val(inputVal(this, prev));
        return;
    }
    // cache the value before proceeding (if data-cache set)
    var cache = $(this).data('cache');
    var cachePrefix = ($(this).data('cache-prefix')) ? $(this).data('cache-prefix') : '';
    $(cache).val(cachePrefix + current);
    var $form = $(this).closest('form');
    var target = $(this).data('update-target');
    console.log("Target is " + target);
    var method = $form.attr('method');
    var action = $form.attr('action');
    var data = $form.serialize();
    var encodedName;
    // .serialize() will omit select elements that do not have a 'null' option (e.g. asm select, page list select)
    // or checkboxes with nothing selected
    // so find them and add empty parameters to the data string, otherwise the page field will not be updated
    $($form.find('select, input').each(function(index){
        console.log('Select element no. ' + index + ' with name ' + $(this).attr("name") + ' has serialize = ' + $(this).serialize());
        encodedName = encodeURI($(this).attr("name"));
        if (data.search(encodedName) === -1) {
            data += ('&' + encodeURI($(this).attr("name")) + '=');
        }
    }));
    console.log("Submitted data: " + data);
    if (!method)
        method = 'get';
    if (!action)
        action = window.location.href;
    // If you want to fade the affected inputfields then assign the loading class to their wrappers with method wrapClass(loading)
    $(target).find('.loading').css({
        display: 'block',
        opacity: 0.2
    }).animate({
        opacity: 1
    }, 5000);
    // then send your request
    $.ajax(action, {
        type: method,  // type used, not method, for older versions of jquery
        data: data,
        // you can also add an error handler here if required, in case the server returns an error on the request
        success: function (data) {
            // Initial ajax just returns an array with message. Need to GET the form data.
            $.ajax(window.location.href, {
                type: 'GET', cache: false, success: function (data) {
                    // then just take the target, and replace it with the target div from the returned data
                    $(target).html($(data).find(target).html());
                    console.log("Returned data: " + data);
                    console.log("Updating html with: " + $(data).find(target).html());
                }
            });
        }
    });
}

function inputVal(el, val=null) {
    var value = $(el).val();
    var inputfield = $(el);
    if ($(el).hasClass('InputfieldCheckbox')) {
        console.log("checkbox");
        inputfield = $(el).find("input[type='checkbox'], input[type='radio']").first();
        if (val === 1) {
            $(inputfield).attr('checked', 'checked');
        } else if (val === 0) {
            $(inputfield).removeAttr('checked');
        }
        value = ($(inputfield).attr('checked') === 'checked') ? 1 : 0;
    } else if ($(el).hasClass('InputfieldToggle')) {
        console.log("toggle");
        inputfield = $(el).find("option[selected='selected']").first();
        if (val === '1') {
            $(inputfield).attr('selected', 'selected');
        } else if (val === '0') {
            $(inputfield).removeAttr('selected');
        }
        value = ($(inputfield).attr('selected') === 'selected') ? '1' : '0';
    } else if ($(el).hasClass('InputfieldPage') && $(el).find(".InputfieldPageAutocompleteData")) {
        console.log("page autocomplete");
        inputfield = $(el).find(".InputfieldPageAutocompleteData").first();
        value = $(inputfield).val();
    } else {
        console.log("other selector type");
        if (val) {
            $(el).val(val);
        }
        value = $(el).val();
    }
    console.log("returning value = " + value);
    return value;
}

 

  • Like 4

Share this post


Link to post
Share on other sites

@MarkE this looks interesting. Ajax loading for runtimeMarkup or page fields sounds awesome! 

I could not get it to work so far. This is the hook called from inside a module.

 $this->addHookAfter('InputfieldPage::getSelectablePage', function(HookEvent $event) {
    $InputfieldPage = $event->object;
    $InputfieldPage->attr('data-action', 'form-update');
    $InputfieldPage->attr('data-update-target', '.InputfieldRepeaterItem');
});

I ended up adding the attributes with javascript. 

$('.InputfieldPage').attr('data-action', 'form-update');
$('.InputfieldPage').attr('data-update-target', '.InputfieldRuntimeMarkup');

This prints to the console when I select a page from the select field. It also seems like the runtimeMarkup field is updating, In the console it shows that the old content is inserted inside runtimeMarkup, so I see a quick update and it returns the same content.

Ok it's working fine when I test it on a page! 
It's not working inside repeater matrix and probably regular repeater fields for me (testet also with only one field populated). 

Share this post


Link to post
Share on other sites
1 hour ago, jploch said:

It's not working inside repeater matrix

I've not tested that!

Share this post


Link to post
Share on other sites

I would love to use this for a page builder module I am working on.
My test has a page select field (single) and a runtimeMarkup field assigned to a repeaper matrix type.

The console JS logs seem fine, the content inside runtimeMarkup flashes and gets replaced by the same content that was the initialised value, no matter what I select. Do you have any ideas, how I could adapt your code to work inside a repeater?

Share this post


Link to post
Share on other sites
11 hours ago, jploch said:

My test has a page select field (single) and a runtimeMarkup field assigned to a repeaper matrix type.

Do you mean that the select field is outside the repeater field and that the runtimeMarkup is inside it? So the markup will be identical for each repeated item?

Share this post


Link to post
Share on other sites
4 hours ago, MarkE said:

Do you mean that the select field is outside the repeater field and that the runtimeMarkup is inside it? So the markup will be identical for each repeated item?

Hey! Thanks for responding to this! The select field and the runtimeMarkup field are inside the same repeater item.
Of course this would not work with multiple repeater items, but if I get it to work with one item I think a can change the code to work with multiple select fields and use the repeater IDs to target each select and runtimeMarkup field.

Share this post


Link to post
Share on other sites

How are you setting the data attributes for the trigger select field? AFAIK this can't be done in the usual way: $inputfield->attr(). I think it would need to be in js. Mind you, I don't have repeater matrix (pro), just the plain repeater.

Share this post


Link to post
Share on other sites
13 hours ago, MarkE said:

How are you setting the data attributes for the trigger select field? AFAIK this can't be done in the usual way: $inputfield->attr(). I think it would need to be in js. Mind you, I don't have repeater matrix (pro), just the plain repeater.

My quick test was in JS like this (works outside repeater, but not inside, testet with one repeater item in regular repeater and repeater matrix)

$(document).ready(function () {
  $('.InputfieldPage').attr('data-action', 'form-update');
  $('.InputfieldPage').attr('data-update-target', '.InputfieldRuntimeMarkup');
});

 

Share this post


Link to post
Share on other sites

@jploch: Work in progress report

Firstly, if you set the attrs in $(document).ready then the repeater needs to be permanently open. Otherwise you need to detect the opening event and then set them.

Secondly, you need to set separate attrs for each repeater item.

Thirdly, I can't get AJAX working properly in PW to update from the changed repeater inputfields. So I have done a hack to use a hidden field outside the repeater to hold a cached value. Unfortunately, because this needs to serve more than one repeater item, it needs to be prefixed so that only the relevant repeater gets updated.

I'd be grateful for any improvements on this approach!!

The code I have (in my context, with 'comment' as the cache field) is :

In the js for the runtimeMarkup field (runtime_markup_note):

$(document).ready(function () {
    $("[id^='Inputfield_rentalAdjustmentPage_repeater']").each(function (index, element) {
        var id = $(element).attr("id");
        var target = id.replace('Inputfield_rentalAdjustmentPage_repeater', '#wrap_Inputfield_runtime_markup_note_repeater');
        var cachePrefix= id.replace('Inputfield_rentalAdjustmentPage_repeater', '');
        $(element).attr('data-action', 'form-update');
        $(element).attr('data-cache', '#Inputfield_comment');
        $(element).attr("data-cache-prefix", cachePrefix + ':');
        $(element).attr('data-update-target', target);
    });
});

In the php for the same field:

$pageId = $page->id;
$adjPageId = $page->rentalAdjustmentPage;
$adjParentTitle = $page->parent->title;
$bkgPage = wire()->pages->get("title=$adjParentTitle");
$adjTypeId = $bkgPage->comment;
if (strpos($adjTypeId, $pageId) == 0) {
    $adjTypeId = str_replace($pageId . ':', '', $adjTypeId);
}
$out = wire()->pages->get("id=$adjTypeId")->summary;
if ($out) {
    echo $out;
} else {
    echo wire()->pages->get("id=$adjPageId")->summary;
}

The php is especially clunky and it would be better to get the host page via the for-page url segment, I think.

  • Like 1

Share this post


Link to post
Share on other sites

@MarkE
Thanks for your help!

It's not clear to me where to put the js and php code you posted.
Should I put the php inside the template file thats getting rendered with the runtimeMarkup field or in the runtimeMarkup Module file (FieldtypeMarkupRender.module)? 

Share this post


Link to post
Share on other sites

In the runtimeMarkup field settings you specify the location and names of your php and js files.

Share this post


Link to post
Share on other sites

Also, the solution to not being able to access the attrs in the repeater items is to turn off "Repeater dynamic loading (AJAX) in editor" on the details tab of the repeater field. Then everything works whether or not the repeater items are initially shown closed.

Share this post


Link to post
Share on other sites
1 hour ago, MarkE said:

Also, the solution to not being able to access the attrs in the repeater items is to turn off "Repeater dynamic loading (AJAX) in editor" on the details tab of the repeater field. Then everything works whether or not the repeater items are initially shown closed.

A already disabled ajax loading for the repeater.

Than I added your php code to the php file that gets rendered with RuntimeMarkup (block_page.php) and added the js file (block_page.js). Both are loading fine (I testet with console log). But its still not working. I get a error with tracy:

PHP Deprecated: strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior in .../site/templates/fields/matrixgrid/block_page.php:8

Here is my complete code inside block_page.php (not sure what rentalAdjustmentPage is refering to):

<?php
namespace ProcessWire;
$pageId = $page->id;
$adjPageId = $page->rentalAdjustmentPage;
$adjParentTitle = $page->parent->title;
$bkgPage = wire()->pages->get("title=$adjParentTitle");
$adjTypeId = $bkgPage->comment;
if (strpos($adjTypeId, $pageId) == 0) {
    $adjTypeId = str_replace($pageId . ':', '', $adjTypeId);
}
$out = wire()->pages->get("id=$adjTypeId")->summary;
if ($out) {
    echo $out;
} else {
    echo wire()->pages->get("id=$adjPageId")->summary;
}
?>

<a href="<?= $page->block_page->url ?>" class="flex-container overlay-parent">
  <img data-src="<?php if($page->block_page->thumbnail) {echo $page->block_page->thumbnail->first()->url();}?>" data-sizes="auto" class="lazyload overlay-bg" />
  <h3 class="overlay absolute">
    <?= $page->block_page->headline ?>
  </h3>
</a>

JS in block_page.js

$(document).ready(function () {

  console.log("Loading is fine")

  $("[id^='Inputfield_rentalAdjustmentPage_repeater']").each(function (index, element) {
    var id = $(element).attr("id");
    var target = id.replace('Inputfield_rentalAdjustmentPage_repeater', '#wrap_Inputfield_block_page_render_repeater');
    var cachePrefix = id.replace('Inputfield_rentalAdjustmentPage_repeater', '');
    $(element).attr('data-action', 'form-update');
    $(element).attr('data-cache', '#Inputfield_comment');
    $(element).attr("data-cache-prefix", cachePrefix + ':');
    $(element).attr('data-update-target', target);
  });
});


Should the selector be like #wrap_Inputfield_block_page_render_repeater or #wrap_Inputfield_block_page_render_repeater3076 with the actual repeater id? Both don't seem to wok for me:

var target = id.replace('Inputfield_rentalAdjustmentPage_repeater', '#wrap_Inputfield_block_page_render_repeater');


Not sure where that comment field from you example is living:

The code I have (in my context, with 'comment' as the cache field) is :

 

Share this post


Link to post
Share on other sites

As I said, that code is in my context - i.e. with my field names. You will need to change those to suit your context. The field 'comment' is on the host page (outside the repeater) - just a plain text field for the cache. rentalAdjustmentPage is my trigger field inside the repeater (a page select field).

  • Like 1

Share this post


Link to post
Share on other sites

ok thanks for clarification and sorry for my slow following here!  I inserted my field names and now getting a result inside the comment field like "3076:1028", the first ID is from the repeater item, the second is for the requested page? Still the same content gets returned. This is how my select field looks in PW wich the above changes:

<select id="Inputfield_block_page_repeater3076" class="uk-select" name="block_page_repeater3076" data-action="form-update" data-cache="#Inputfield_comment" data-cache-prefix="3076:" data-update-target="#wrap_Inputfield_block_page_render_repeater3076"><option value=""> </option><option value="1269">GIGASET</option><option value="1220">JESSICA VON BREDOW</option><option value="1167">NINA HEMMER</option><option value="1230">STEFAN KNOPF</option></select>

 

Share this post


Link to post
Share on other sites

the only error I get is this with tracy:

HP Deprecated: strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior

 

Share this post


Link to post
Share on other sites

Hmm - I don't get that error. What arguments are being supplied?

BTW, a better way of finding the host page:

$bkgPageId = str_replace('for-page-', '', $page->parent->name);
$bkgPage = wire()->pages->get("id=$bkgPageId");

 

  • Like 1

Share this post


Link to post
Share on other sites

Per PHP manual

If needle is not a string, it is converted to an integer and applied as the ordinal value of a character. This behavior is deprecated as of PHP 7.3.0, and relying on it is highly discouraged. Depending on the intended behavior, the needle should either be explicitly cast to string, or an explicit call to chr() should be performed.

Share this post


Link to post
Share on other sites

@MarkE 
Its working now! Thanks for your help! It finally worked with the first code you postet. Before I used the "Updated version here - works with more field types" code example. The first code works perfectly with my page select field and your adjustments. I will send you a copy of my module once its ready, it adds drag and drop, resize support for repeater and repeater matrix fields and uses this code to ajax update page reference blocks inside repeaters and renders them with RuntimeMarkup 🙂 . Maybe I find a way to improve this approach. It would be cool if this could be a feature of the RuntimeMarkup Module someday. I still need to learn more about PHP and PW, I am ok with javascript and frontend stuff..

Share this post


Link to post
Share on other sites

After some testing it turns out that, if the page hosting the repeater has children it is no longer replacing the runtimeMarkup field. 

Share this post


Link to post
Share on other sites

I don’t seem to have that problem. My host page has several children. 

Share this post


Link to post
Share on other sites

Uploaded script to github at https://github.com/MetaTunes/Form-update

This includes a force refresh of the target field in order to make it work in more instances (e.g. a nice new application for providing previews of multi-select page-per-image)

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Juergen
      Hello @ all,
      I am creating a new inputfield/fieldtype to store opening hours, but I am struggeling to save values from multiple dynamic created inputfields in 1 column of the database.
      Scenario:
      The user can enter one or more opening times per day in a UI.
      Fe:
      Monday open from 08:00 to 12:00 and from 14:00 to 17:00 Tuesday open from 08:00 to 12:00 and from 14:00 to 19:00 and so on
      Via a little JavaScript you can add as much opening times as you need per day - the additional inputfield will be created dynamically.
      After form submission all the values are in the POST array -> this works (see example below):
      ProcessWire\WireInputData Object ( [openinghours_mo-0-start] => 09:00 [openinghours_mo-0-finish] => 13:00 [openinghours_mo-1-start] => 14:00 [openinghours_mo-1-finish] => 18:00 [openinghours_mo-2-start] => 21:00 [openinghours_mo-2-finish] => 23:00 [openinghours_tu-0-start] => 09:00 [openinghours_tu-0-finish] => 13:00 [openinghours_tu-1-start] => 14:00 [openinghours_tu-1-finish] => 18:00 [openinghours_we-0-start] => 09:00 [openinghours_we-0-finish] => 13:00 [openinghours_we-1-start] => 14:00 [openinghours_we-1-finish] => 18:00 [openinghours_th-0-start] => 09:00 [openinghours_th-0-finish] => 13:00 [openinghours_th-1-start] => 14:00 [openinghours_th-1-finish] => 18:00 [openinghours_fr-0-start] => 09:00 [openinghours_fr-0-finish] => 13:00 [openinghours_fr-1-start] => 14:00 [openinghours_fr-1-finish] => 18:00 [openinghours_sa-0-start] => [openinghours_sa-0-finish] => [openinghours_so-0-start] => [openinghours_so-0-finish] => ) The property name is always the name attribute of the field 😉 . If the property is empty means closed on that day.
      Now I need to combine all those values into 1 array (or json array) and store it in the database in 1 column called 'hours' in my case (see screenshot below):

      In my ___processInput(WireInputData $input) method I have tried to make it work like this:
      public function ___processInput(WireInputData $input): self { $name = $this->attr('name'); $value = $this->attr('value'); //input object includes always every input on the page, so lets filter out only inputs from this field //we need to do this, because the number of values is variable - so extract only values that starts with $name.'_' $nameAttributes = []; foreach($input as $key=>$value){ if(substr($key, 0, strlen($name.'_')) === $name.'_'){ $nameAttributes[$key] = $value; } } // loop through all inputfields of this fieldtype $time_values = []; foreach($nameAttributes as $nameAttr => $value) { $time_values[$nameAttr] = $value; } } //save it in the database $input->set('hours', serialize($time_values)); return $this; } The only important part of this code is the last part with the serialize function.
      After saving it will create a record in the database, but the value is always NULL (default value) (see below).

      Checking $time_values returns all the values, but printing out "$this" shows me that the property "hours" inside the Openinghours object is empty (see below) - so the mistake must be there, but I dont know where?!?!?!?
      [title] => Home [openinghours] => ProcessWire\OpeningHours Object ( [data] => Array ( [hours] => ) ) If I check the sleepValue() method or the sanitizeValue() - they are also empty. So it seems that the values will not reach these methods. I havent found a clear documentation of whats going on behind the saving process of an inputfield.
      As far as I know the saving process starts with the form submission. The values are in the POST array and will be processed by the processInput() method. Before they will be saved in the database they will be sanitized by the sanitizeValue() mehtod and afterwards they will be prepared for storage in the sleepValue() method.  The last step is the storage itself.
      Has someone an idea what is missing by storing values from multiple fields into 1 database column or has someone a working example of such a scenario on github to help me out.
      A clear explanation of the storage process will be also helpful.
      Thanks and best regards
    • By Juergen
      Hello @ all!
      I want to share a simple fieldtype and inputfield to store address data with you.
      I have created this inputfield for learning purposes and it has no fancy functionality. It is simply for storing address data such as street, number, postalcode and so on in one table. As an addition you can store latitude and longitude too, so you can use them in maps.
      Here is a screenshot of what it looks like:

      You can select which fields are mandatory and you can choose if the inputs for longitude and latitude should be displayed. These settings can be configured in the field configuration.
      If you find this inputfield useful you can download it at https://github.com/juergenweb/FieldtypeSimpleAddress
      There you will find a detailed explanation. If you have an idea of an usefull feature that can be added or you have detected a bug, please report it in my github account.
       
    • By MoritzLost
      This module allows you to integrate hCaptcha bot / spam protection into ProcessWire forms. hCaptcha is a great alternative to Google ReCaptcha, especially if you are in the EU and need to comply with privacy regulations.

      The development of this module is sponsored by schwarzdesign.
      The module is built as an Inputfield, allowing you to integrate it into any ProcessWire form you want. It's primarily intended for frontend forms and can be added to Form Builder forms for automatic spam protection. There's a step-by-step guide for adding the hCaptcha widget to Form Builder forms in the README, as well as instructions for API usage.
      Features
      Inputfield that displays an hCaptcha widget in ProcessWire forms. The inputfield verifies the hCaptcha response upon submission, and adds a field error if it is invalid. All hCaptcha configuration options for the widget (theme, display size etc) can be changed through the inputfield configuration, as well as programmatically. hCaptcha script options can be changed through a hook. Error messages can be translated through ProcessWire's site translations. hCaptcha secret keys and site-keys can be set for each individual inputfield or globally in your config.php. Error codes and failures are logged to help you find configuration errors. Please check the README for setup instructions.
      Links
      Github Repository and documentation InputfieldHCaptcha in the module directory (pending approval) Screenshots (configuration)

      Screenshots (hCaptcha widget)

       
       

       
    • By cosmicsafari
      Hi all,
      I have need to dynamically set InputFieldMultiSelect to  selected on page load based on the status of some items within a database table.
      However I keep running into issues when trying to do this via InputfieldSelect::setOptionAttributes()
      https://processwire.com/api/ref/inputfield-select/set-option-attributes/
      Going by the above it sounds like it should be pretty straight forward, and for certain values it seems to work but not when I wanted to set it to 'selected'.
      For example:
      $f->setOptionAttributes(1030,['foo' => 'test']); The above works as I would have wanted, in that it updates the option with the value 1030, to include the attribute foo="test"
      But the same code above edited to the following:
      $f->setOptionAttributes(1030,['selected' => 'selected']); Doesn't seem to do anything?
      I assume I'm missing something or trying to implement the 'selected' wrongly but I'm not sure how else I should approach this, any advice would be much appreciated.
       
    • By MarkE
      I'm looking for an inputfield module that might allow the entry and evaluation of a conditional expression (following php syntax).
      My use case is an admin function for writing pro-forma emails/letters where some components of the pro-forma are dependent on conditions determined by an admin user. The pro-forma is then cloned for use and the relevant components are included depending on the runtime value of the conditions. The conditions usually include hanna codes as the items to be compared. This is implemented in a parent-child structure, where the main mail body is in the parent and each child then has a condition (textarea) field and a body textarea field for the optional text relating to that condition.
      As an interim solution on my dev machine, I am just using eval() to evaluate the conditions, but I really don't want to use this in the live environment. My idea is to use an approach similar to that for hanna codes to store the php and render it. This would be (somehow) wrapped in a new inputfield module (extending InputfieldTextarea?) with an evaluate() method that would return true or false as appropriate. It would be placed inside a try...except structure to catch syntax errors etc.
      It seemed to me that this might be quite a useful utility module and that someone might have developed something similar, but I can't find anything. Does anyone have any pointers, or will I need to start from scratch? If the latter, then I'd appreciate some help along the way as I am a bit of a novice in these matters.
×
×
  • Create New...