Jump to content

[SOLVED] Problem saving values from dynamic created inputfields in the database


Recommended Posts

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

skizze.jpg.02d04c57464b88d20cd79a599fa064fd.jpg

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):

Screenshot(4).png.03b4e61c044030eaa905530e1e80f80b.png

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).

Screenshot(5).png.dbc818090e35e9be9669df84f33ca8a1.png

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

Link to comment
Share on other sites

OK, I am 1 step forward :-)

Adding

$this->value['hours'] = $time_values;

to the processInput() method adds the values to the object

Here is the complete code of this method:

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 and set the value (if changed or not) back
        $time_values = [];
        foreach($nameAttributes as $nameAttr => $value) {
            $time_values[$nameAttr] = $value;
        }

        //add it to the object
        $this->value['hours'] = $time_values;

        return $this;

    }

Now the values are also reachable in sleepValue() method and the sanitizeValue() method.

ProcessWire\OpeningHours Object
(
    [data] => Array
        (
            [hours] => Array
                (
                    [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] => 
                    [openinghours_ho-0-start] => 
                    [openinghours_ho-0-finish] => 
                )

        )

)

Nevertheless the value in the DB is NULL :-( 

Link to comment
Share on other sites

OK, I figured it out.

I transformed the values of the inputfields inside the sleepValue function to a json array and now the values will be stored in the database.

public function sleepValue(Page $page, Field $field, $value)
    {
      // throw error if value is not of the right type
      if (!$value instanceof OpeningHours) {
          throw new \Exception($this->_('Expecting an instance of OpeningHours'));
      }
      $content = json_encode($value->data['hours']);
      $sleepValue = ['hours' => $content];
      return $sleepValue;
    }

So the responsible lines are

$content = json_encode($value->data['hours']);
$sleepValue = ['hours' => $content];

 

Screenshot(6).png.e61b8d90f245d99774ae43da04876465.png

The problem of the storage was that the column 'hours' was not defined in the sleepValue method. This was the important part because without it the system doesnt know where to store the value.

 

  • Like 2
Link to comment
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
 Share

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Robin S
      Select Images
      An inputfield that allows the visual selection and sorting of images, intended for use with the FieldtypeDynamicOptions module. Together these modules can be used to create a kind of "image reference" field.

      Integration with FieldtypeDynamicOptions
      InputfieldSelectImages was developed to be used together with FieldtypeDynamicOptions (v0.1.3 or newer):
      Create a Dynamic Options field. Choose "Select Images" as the "Inputfield type". Select Images appears in the "Multiple item selection" category but you can set "Maximum number of items" to 1 if you want to use Select Images for single image selections. Define selectable options for the field via a FieldtypeDynamicOptions::getSelectableOptions hook. See some examples below. FieldtypeDynamicOptions is recommended but is not a strict requirement for installing InputfieldSelectImages in case you want to use an alternative way to store the field data.
      Selection of Pageimages
      In this example the field allows selection of Pageimages that are in the "images" field of the home page.
      The field will store URLs to the Pageimages so it works as a kind of "image reference" field. You can use the "Format as Pagefile/Pageimage object(s)" option for the Dynamic Options field to have the formatted value of the field be automatically converted from the stored Pageimage URLs to Pageimage objects.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get Pageimages within the "images" field on the home page foreach($event->wire()->pages(1)->images 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; } }); Selection of image files not associated with a Page
      When not working with Pageimages you must add a "data-thumb" attribute for each selectable option which contains a URL to a thumbnail/image.
      In this example the field allows selection of image files in a "/pics/" folder which is in the site root.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get files that are in the /pics/ folder $root = $event->wire()->config->paths->root; $path = $root . 'pics/'; $files = $event->wire()->files->find($path); // Add an option for each file foreach($files as $file) { $basename = str_replace($path, '', $file); $url = str_replace($root, '/', $file); // The value must be an array with the following structure... $options[$url] = [ // The label for the image 'label' => $basename, 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => $url, ], ]; } $event->return = $options; } }); The field values don't have to be image URLs
      The values stored by the Dynamic Options field don't have to be image URLs. For example, you could use the images to represent different layout options for a page, or to represent widgets that will be inserted on the page.
      Also, you can use external URLs for the thumbnails. In the example below the options "calm" and "crazy" are represented by thumbnails from placecage.com.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "calm_or_crazy" if($field->name === 'calm_or_crazy') { $options = []; // Add options that are illustrated with thumbnails from placecage.com $options['calm'] = [ // The label for the option 'label' => 'Nicolas Cage is a calm man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/260/260', ] ]; $options['crazy'] = [ // The label for the option 'label' => 'Nicolas Cage is a crazy man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/c/260/260', ] ]; $event->return = $options; } }); Field configuration
      You can define labels for the button, notices, etc, that are used within the inputfield if the defaults don't suit.

       
      https://github.com/Toutouwai/InputfieldSelectImages
      https://processwire.com/modules/inputfield-select-images/
    • By Neue Rituale
      https://github.com/neuerituale/FieldtypeOembed
      or in the modules directory: https://processwire.com/modules/fieldtype-oembed/
    • By carlitoselmago
      It's been twice this year as a PW developer where clients have asqued me to have a way to put content on specific coordinates of a picture. Something like what image maps are made of. But not necesarly on links, sometimes text, sometimes images and links. 
      So I've been thinking to build an inputfield for that purpose. But I'm not sure how could it be the most flexible so I can share it in github/modules page.
      Ideally it would need to work like a repeater field where you start uploading an image and then adding any custom field like title, url etc. So the user could add multiple elements floating over that image.
      Does anybody here know the ideal way to start the arquitecture of something like that? I could just hardcode the fields I need for the website I have to code this month but it would be nice to be something more flexible.
    • By jploch
      Fieldtype Page Table Grid
      This is a sneak preview of a side project I've been working on for quite some time now. A lot of work and thought has gone into this, so I will most likely release this as a commercial module at some point in the near future. 

      As a designer (and developer) I get the appeal of a WYSIWYG editor. After playing around with some WYSIWYG page builder tools, I always felt something was wrong about them. So I decided to build my own PW version based on PageTable.

      Here is a small demo (using AdminThemeCanvas, but its working with other admin themes as well) :
      There is also a complete website that I built for a friend of mine using this module and some custom blocks.
      Concept
      This fieldtype shares a lot of features with PageTableExtended: it's also an extension of PageTable and renders the block templates in the backend and frontend (native PW templates and fields). You can also add your own css via module settings.
      The difference is, this fieldtype also gives you the ability to rearrange and resize elements in a visual way as well as enable inline editing for text, ckeditor and file fields. Similar (and promising) attempts have been made, but I wanted something based on native CSS grid instead of a CSS framework...so I built my own version. Most CSS frameworks are based on flexbox, which is great for layouting elements horizontally. With CSS grid, you can place elements horizontally and vertically, allowing for layouts that were not previously possible with CSS. Similar to webflow, this fieldtype uses javascript (in the backend) to let you manipulate CSS grid in a visual way to design fully responsive websites (or parts of them). It should still be possible to include a CSS framework if you like (just add the classes to your block markup and include the CSS via module settings).
      The CSS grid layout manipulations are saved in a single field as a JSON array and used to generate a dynamic stylesheet that you simply include in your main template (no inline styles). The styles are saved within the breakpoint you select and cascade down to smaller breakpoints. That means you can specify just the basic breakpoint and adjust other breakpoints if needed. The exception is the mobile breakpoint which will display everything in one column as a default (you can change the layout here too).
      The fieldtype also comes with an optional style panel to manipulate some additional CSS properties directly on the page. You can customize the panel or disable it completely from the module settings (and just use a CSS file that you include via module settings). The style panel is based on inputfields (nothing is saved to the database). This means that you just have to install the module and all fields are available to all blocks automatically (this can be customized). It also has the benefit that your installation is not flooded with fields; this module only installs one field.
      Don't want to give your customer all that power? Design features can be disabled for certain roles. The grid editor role can just edit the content and use the inline editing feature to edit content quickly. You can then also grant access individually to the style panel, resize or drag functionality.
      Features
      Blocks are just pages Blocks are defined by native PW templates and fields Manipulate CSS grid in a visual way to design fully responsive websites (or parts of them) Design features can be disabled for certain roles Inline editing of text, ckeditor and file fields The layout is 100% CSS grid (very small css file size) Simply drag and resize to manipulate grid items directly inside the backend Manipulate grid columns and rows directly on the page (use any number of columns you want) All style manipulations are saved as JSON and used to generate a dynamic stylesheet that you just include in your main template (no inline styles) Nested groups/grids (child pages of nested blocks are created under group parent) Global blocks work with page reference field (changes on one page, changes all blocks on all pages) Manual and auto placement of grid items Define custom icons for your blocks via native template settings (template -> advanced -> icon) Option to load lazysizes in the backend to enable lazy loading of assets with class lazyload Works with all default and ui-kit based admin themes If you have any questions or feedback, let me know.
    • By franciccio-ITALIANO
      Hi everyone.
      I've created 12 templates that are the same but each with an extra bit of html code. 
      The piece of code is as follows:
      <div> <div class="box-pf"> <i class="fa fa-map-pin fa-2x fa-red faa-pulse animated"></i> <a href=""> <span class="uk-text-middle"><i>Sonchus oleraceus</i> 'Grespino degli Orti'</span></b> </a> </div> </div> On the third line we read "fa-red."
      I created 12 similar templates.
      The first template has only one box with fa-red, the last template has 12 boxes with icons of 12 different colors.
      So. is there any way to have only 1 template and add, if I want and when I want, a small or big, same or different piece of html code?
       
×
×
  • Create New...