Jump to content
bytesource

How to automatically set the value of a field?

Recommended Posts

Hi all,

I am trying to build a very simple invoice system using Processwire. There is one invoice per page. To ensure that each page has a unique title, I would like to set the value of the title field automatically.

I rewrote a module I found on the forum, but the following code does not work (a blank field stays always blank in the admin):

public function init() {
        // add after-hook to the inputfield render method
        $this->addHookAfter("Inputfield::render", $this, "renderField");
    }

    public function renderField(HookEvent $event) {
        // // get the current field
        $field = $event->object;
        if($field->name == 'invoice_number' && $field->get('value') == '' ) {
            $id = date("Y") . "/" . $page->id;  // example for a possible id value
            $field->set('value', $id);
        }
    }
 

Also, when clicking on the empty input field, the following message is displayed:

This webpage has disabled automatic filling for this form.

I now have the following questions:

  1. Is it even possible to automatically set the value of a field?
  2. If yes, how could I change the above code so that it works as intended?

Cheers,


Stefan

Share this post


Link to post
Share on other sites

Just curious - you say that you want to make the title field unique, but in your code you are trying to set the value of the invoice_number field. 

This should do what you want, assuming you actually want to set the title field.

<?php

class MarkupTitleField extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(

            'title' => 'Markup Title Field',
            'version' => 100,
            'summary' => 'Set the title field to predefined value',
            'singular' => true,
            'autoload' => true,
            );
    }

    public function init() {
            // add before-hook to the inputfield render method
            $this->addHookBefore("Inputfield::render", $this, "renderField");
        }

    public function renderField(HookEvent $event) {
        // // get the current field
        $field = $event->object;
        if(($field->name == 'title' || $field->name == '_pw_page_name') && $field->value == '' ) {
            $last_page_id = wire('pages')->get("include=all,sort=-id")->id;
            $id = date("Y") . "/" . ($last_page_id+1);  // example for a possible id value
            $field->set('value', $id);
        }
    }

}

Edited code to get the id of the page that is about to be created. Not sure if this is completely robust. You might want to consider a different unique identifier.

Edited by adrian
  • Like 4

Share this post


Link to post
Share on other sites

You may also have a look to http://mods.pw/4N

If you use this as the holder for your unique id, you have DB-wide security ;-) regardless what possible uid you want to use.

Share this post


Link to post
Share on other sites

Horst - I don't think that module will work for the title field, will it?

Although I am still not sure if bytesource is looking to make the title field unique anyway - I guess we'll wait and see :)

Share this post


Link to post
Share on other sites

I apologize for replying late. Unfortunately this happens quite often due to the time difference. 

I wrote that I wanted to make the title field guaranteed to be unique, but used the custom field invoice_number instead as I already created some pages and did not want to mess with existing titles before being sure the module code worked. 

So, yes, ultimately it is about uniqueness, and in this regard Ryan's fieldtype Text Unique might in fact come in handy (thanks for the tip, Host). 

However, I also always try to learn something new, and right now I would like to find out how to dynamically set a value for a field - any value would be fine at this point. 

But even when I set the value 'hello' for the value of the field:

$field->set('value', "hello");
 

I did not get any output. So I guess the problem might be with one of the following:

  • Incorrect usage of either $field->get() or $field->set() or both.
  • Some template or field role restriction that resulted in this error message: This webpage has disabled automatic filling for this form. 

I haven't edited any roles, though.

Cheers,

Stefan

Share this post


Link to post
Share on other sites

Stefan,

I believe the only reason why your code wasn't working is because you were using $this->addHookAfter. You'll notice that the code I put together uses $this->addHookBefore. You need to populate the value before rendering the field.

As for the automatic filling error. Ryan has autocomplete="off" set for the form when editing an existing page. I have also seen this browser produced warning - it is not actually an error.

Did you try the module I put together? You can easily change the fields that are being dynamically set to whatever you want.

  • Like 1

Share this post


Link to post
Share on other sites

Adrian, 

You are right, I missed the fact that you changed the after hook into a before hook. 

I edited my code accordingly and am glad to announce that now everything is working as exptected:

class FieldHelper extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(
            'title' => 'FieldHelper',
            'version' => 100,
            'summary' => 'Hide fields depending on permission, role, or user.',
            'singular' => true,
            'autoload' => true
            );
    }

    public function init() {
        // add before-hook to the inputfield render method
        $this->addHooKBefore("Inputfield::render", $this, "renderField");
    }

    /**
     * If not empty, create unique value for textfield 'invoice_number'
     *
     */
    public function renderField(HookEvent $event) {
        $field = $event->object;

        if($field->name == 'invoice_number' && $field->value == '' ) {
          // $id is unique, as page id is unique
          $id = date('Y') . "/" . $this->process->getPage()->id; 
          $field->set('value', $id);
        }
    }

} 
 

Thanks a lot for your help!

Cheers, 

Stefan

  • Like 1

Share this post


Link to post
Share on other sites

Adrian, 

My code above only works for pages that have already been saved to the database and therefore have a page id. 

When used with the title tag, however, this is a problem, as the value for this field must be set before the page is saved. 

As it turns out you already anticipated this use case by calculating the unique value based on the id of the last page saved to the database:

http://processwire.com/talk/topic/4201-how-to-automatically-set-the-value-of-a-field/#entry41172

I marked your answer as the best answer, so that others will find your solution faster than I did.

 
Cheers, 
 
 
Stefan
  • Like 1

Share this post


Link to post
Share on other sites

I have a follow up question. 

The title field should only be automatically set for pages with the template 'documents'. However, when adding a new page, there has not been a template attached yet. I then tried to identify a target page via the name of the template of its parent, but without having been saved to the database, there is no parent, either. 

So my question is: How can I get hold of an instance of a page that has not been saved yet? And what properties does such a page possess that I could use to determine if the title field should be set automatically?

Cheers,

Stefan

Share this post


Link to post
Share on other sites

Hi Bytesource, just out of interest, the way I finally went about having a unique invoicing number for our invoicing system was simply to count the existing number of invoices and to add 1.

This relies on not deleting invoices. Instead, when a user (normally myself) clicks delete on an invoice, it simply changes its status to "cancelled" and I can then choose whether to display it in the invoice list or not.

This approach may be too basic for your needs and it doesn't use a module but it seems to work well enough for us.

EDIT: OK, so I should have paid more attention, I see that you're wanting to do this through the admin?

Share this post


Link to post
Share on other sites

When creating a new page, there is a get variable set: parent_id=xxxx

You should be able to use $input->get->parent_id to do what you want.

  • Like 1

Share this post


Link to post
Share on other sites

@onjegolders

I like your solution as it is simple and easy to implement. I am afraid, though, that it would not work in my case. My system is intended to handle both quotations and invoices, and quotations get deleted regularly. 

@adrian

Thanks for the great advise. 

I wrote the following test code but $parent_id seems to not exist:

class FieldHelper extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(
            'title' => 'FieldHelper',
            'version' => 100,
            'summary' => 'Hide fields depending on permission, role, or user.',
            'singular' => true,
            'autoload' => true
            );
    }

    public function init() {
        // add before-hook to the inputfield render method
        $this->addHooKBefore("Inputfield::render", $this, "renderField");
    }



    public function renderField(HookEvent $event) {

        $parent_id = $input->get->parent_id; 
        echo "id: " . $parent_id;  // => id:
        

        // $parent = wire('pages')->get($parent_id);

    }

}
 
 
 
Out of curiosity: How can I find out about which variables are available at which time, e.g. how did you know what GET variables are passed around?
 
Cheers, 
 
Stefan
 

Share this post


Link to post
Share on other sites

Check in Firebug? (Net)

Actually, I think it is a post and not a get?, i.e.  $input->post->parent_id

POST http ://localhost/pw/pw/page/add/

The POST parameters are (note, Token is alphanumeric - not as below:))

TOKENxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
_pw_page_name	testpage
parent_id	1
submit_save	Save
template	43
title	testpage

Share this post


Link to post
Share on other sites

@kongondo

I think it is still a GET parameter, in fact it has been right in front of me all the time:

http://localhost/mysite/processwire/page/add/?parent_id=1010

However, in that case the following code should return 1010, but nothing is returned:

$parent_id = $input->get->parent_id; 
echo $parent_id
 
 
Could it be that there is something wrong with the above code syntactically?
 
Cheers, 
 
Stefan

Share this post


Link to post
Share on other sites

 Could it be that there is something wrong with the above code syntactically?

Yes, you call it from within a method / function == wrong scope!

You need to call it wire('input')->get->parent_id, I think. ;)

  • Like 2

Share this post


Link to post
Share on other sites

@host

I got hit by the scope again! I guess I should finally start learning PHP from the ground up. 

Thanks a lot for your suggestion, the following code is indeed working:

$parent_id = wire('input')->get->parent_id; 
echo "id: " . $parent_id;  // => 1010
 
 
Cheers, 
 
Stefan
  • Like 1

Share this post


Link to post
Share on other sites

@kongondo

I think it is still a GET parameter, in fact it has been right in front of me all the time:

However, in that case the following code should return 1010, but nothing is returned:

Aahh, I see., you want to grab it before the page is saved (i.e. just after you tell PW that you want to create a new page (GET)) rather than after it has been created (POST) which is what I referred to. Glad it's working now.. :)

  • Like 1

Share this post


Link to post
Share on other sites

Looks like everyone else was out of bed before me this morning - glad you sorted out the scope issue. As you figured out, $_GET variables are passed via the URL and so are very easy to find.

Share this post


Link to post
Share on other sites

For completeness I would like to add the final code I came up with.

I decided to not only set the value of the title field, but also the value of the name field (called _pw_page_name internally). This is because the page name gets entered automatically as you type into the title field, but stays blank if the value of the title field is preset, thus causing an error when the page is saved.

class FieldHelper extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(
            'title' => 'FieldHelper',
            'version' => 100,
            'summary' => 'Hide fields depending on permission, role, or user.',
            'singular' => true,
            'autoload' => true
            );
    }

    public function init() {
        // add before-hook to the inputfield render method
        $this->addHooKBefore("Inputfield::render", $this, "renderField");
    }

    /**
     *  Pre-fill certain fields if empty with unique identifier based on the page id
     *
     */
        public function renderField(HookEvent $event) {

            // Get hold of an instance of the parent
            $parent_id = wire('input')->get->parent_id; 
            $parent    = wire('pages')->get($parent_id);

            $field = $event->object;
            $name  = $field->name;
            $value = $field->value;
            
            // Only prefill the title field
            // for children of pages with template 'customer':
            if ($parent->template == 'customer') {
                if ($name == 'title' ||  $name == '_pw_page_name')  { 
                    if ($value == '') {

                        $presetValue = $this->uniqueValue();
                        if ($name == '_pw_page_name') {
                            // URL-encode path name
                            // http://php.net/manual/en/function.filter-var.php
                            $presetValue = filter_var($presetValue, FILTER_SANITIZE_URL);
                        }
                         
                         $field->set('value', $presetValue);
                    }
                }
            }
        }

    public function uniqueValue() {
          $last_page_id = wire('pages')->get("include=all,sort=-id")->id;
          $id = date("Y") . "/" . ($last_page_id+1);  // example for a possible id value
          return $id;
    }
}
 
Output in the admin on opening a new page:
Title:  2013/1073
Name: 2013-1073
 
I am not sure if using filter_var($presetValue, FILTER_SANITIZE_URL) is the best way to sanitize the URL path name. Please let me know if there is a better or more idiomatic way.
 
Cheers, 
 
Stefan
 
  • Like 2

Share this post


Link to post
Share on other sites

To be consistent it's probably best to use PW's own sanitizer for pageName:

$sanitizer->pageName($value, true)

See the cheatsheet http://cheatsheet.processwire.com/#sanitizer and/or look at the source if you want to check out what it does exactly https://github.com/ryancramerdesign/ProcessWire/blob/dev/wire/core/Sanitizer.php

  • Like 3

Share this post


Link to post
Share on other sites

@SiNNuT

Thanks for pointing me to the Sanitizer class. Seeing the actual code makes understanding the functions much easier. 

I often use the cheatsheet, which  is a godsend for learning the Processwire API, but wasn't familiar with $sanitizer yet.

As for my code, I had to use wire('sanitizer)->pageName instead of $sanitizer->pageName.

Cheers,

Stefan 

Share this post


Link to post
Share on other sites

It certainly doesn't hurt and is good practice, but you don't really need to sanitize it at all - you are defining exactly what it will be yourself. The one thing to note on this is that PW is automatically converting the "/" in between the year and the page id, into a dash for the name field. You could do this yourself, which $sanitizer->pageName would take care of.

The one thing you might want to sanitize is $parent_id by using "(int)", eg:

$parent = wire('pages')->get((int)$parent_id);

which converts it to an integer, making that pages->get completely safe. Probably unnecessary since this is an admin form, but that original get variable could be changed by someone.

  • Like 2

Share this post


Link to post
Share on other sites

@adrian

Necessary of not, I highly value best practices, so thanks a lot for this tip!

Cheers, 

Stefan

Share this post


Link to post
Share on other sites

@onjegolders

I like your solution as it is simple and easy to implement. I am afraid, though, that it would not work in my case. My system is intended to handle both quotations and invoices, and quotations get deleted regularly. 

@adrian

Thanks for the great advise. 

I wrote the following test code but $parent_id seems to not exist:

class FieldHelper extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(
            'title' => 'FieldHelper',
            'version' => 100,
            'summary' => 'Hide fields depending on permission, role, or user.',
            'singular' => true,
            'autoload' => true
            );
    }

    public function init() {
        // add before-hook to the inputfield render method
        $this->addHooKBefore("Inputfield::render", $this, "renderField");
    }



    public function renderField(HookEvent $event) {

        $parent_id = $input->get->parent_id; 
        echo "id: " . $parent_id;  // => id:
        

        // $parent = wire('pages')->get($parent_id);

    }

}
 
 
 
Out of curiosity: How can I find out about which variables are available at which time, e.g. how did you know what GET variables are passed around?
 
Cheers, 
 
Stefan

No problem. Ours also uses quotations and these do get deleted (unlike invoices which get cancelled). When we choose to send a quote to an invoice, at the top of the page, we perform the same number check to see what invoice number it needs to be allocated. It works fine for us. Let me know if I can help further.

Share this post


Link to post
Share on other sites
@onjegolders

 

Using a module to implement the unique number was a good opportunity for me to learn more about fieldtypes, not necessarily because it is the most straightforward solution. 

 

I really want to get to know the internals of Processwire better, but normally don't have enough time beside work to read up on the documentation. So I am actively looking for opportunities to implement something new.  

 

Cheers, 

 

Stefan

  • Like 1

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.

×
×
  • Create New...