bytesource Posted August 3, 2013 Share Posted August 3, 2013 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: Is it even possible to automatically set the value of a field? If yes, how could I change the above code so that it works as intended? Cheers, Stefan Link to comment Share on other sites More sharing options...
adrian Posted August 3, 2013 Share Posted August 3, 2013 (edited) 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 August 3, 2013 by adrian 4 Link to comment Share on other sites More sharing options...
horst Posted August 3, 2013 Share Posted August 3, 2013 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. Link to comment Share on other sites More sharing options...
adrian Posted August 3, 2013 Share Posted August 3, 2013 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 Link to comment Share on other sites More sharing options...
bytesource Posted August 4, 2013 Author Share Posted August 4, 2013 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 Link to comment Share on other sites More sharing options...
adrian Posted August 4, 2013 Share Posted August 4, 2013 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. 1 Link to comment Share on other sites More sharing options...
bytesource Posted August 5, 2013 Author Share Posted August 5, 2013 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 1 Link to comment Share on other sites More sharing options...
bytesource Posted August 5, 2013 Author Share Posted August 5, 2013 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 1 Link to comment Share on other sites More sharing options...
bytesource Posted August 8, 2013 Author Share Posted August 8, 2013 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 Link to comment Share on other sites More sharing options...
onjegolders Posted August 8, 2013 Share Posted August 8, 2013 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? Link to comment Share on other sites More sharing options...
adrian Posted August 8, 2013 Share Posted August 8, 2013 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. 1 Link to comment Share on other sites More sharing options...
bytesource Posted August 9, 2013 Author Share Posted August 9, 2013 @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 Link to comment Share on other sites More sharing options...
kongondo Posted August 9, 2013 Share Posted August 9, 2013 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 Link to comment Share on other sites More sharing options...
bytesource Posted August 9, 2013 Author Share Posted August 9, 2013 @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 Link to comment Share on other sites More sharing options...
horst Posted August 9, 2013 Share Posted August 9, 2013 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. 2 Link to comment Share on other sites More sharing options...
bytesource Posted August 9, 2013 Author Share Posted August 9, 2013 @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 1 Link to comment Share on other sites More sharing options...
kongondo Posted August 9, 2013 Share Posted August 9, 2013 @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.. 1 Link to comment Share on other sites More sharing options...
adrian Posted August 9, 2013 Share Posted August 9, 2013 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. Link to comment Share on other sites More sharing options...
bytesource Posted August 10, 2013 Author Share Posted August 10, 2013 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 2 Link to comment Share on other sites More sharing options...
SiNNuT Posted August 10, 2013 Share Posted August 10, 2013 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 3 Link to comment Share on other sites More sharing options...
bytesource Posted August 10, 2013 Author Share Posted August 10, 2013 @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 Link to comment Share on other sites More sharing options...
adrian Posted August 10, 2013 Share Posted August 10, 2013 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. 2 Link to comment Share on other sites More sharing options...
bytesource Posted August 11, 2013 Author Share Posted August 11, 2013 @adrian Necessary of not, I highly value best practices, so thanks a lot for this tip! Cheers, Stefan Link to comment Share on other sites More sharing options...
onjegolders Posted August 12, 2013 Share Posted August 12, 2013 @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. Link to comment Share on other sites More sharing options...
bytesource Posted August 12, 2013 Author Share Posted August 12, 2013 @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 1 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