bernhard

[alpha] RockForms: Flexible, Fast and Secure FrontendForms for ProcessWire

Recommended Posts

https://gitlab.com/baumrock/RockForms

It uses the great NetteForms from Nette Framework (docs: https://doc.nette.org/en/2.4/forms )

 

Features:

  • Client AND Server-side validation in one go (you only need to code your rules ONCE)
  • CSRF protection (by processwire)
  • Honepot spam protection (custom)
  • Helper to create PW pages from submitted forms ($form->createPage($parent, $template, $title))
  • Add ?success=yourform tag to url ONLY after successful submission (for analytics)
  • Recover fields after unsuccessful submission (server side validation error)
  • Define custom actions + message on successful submission
  • UiKit form renderer (more can be accepted by pull requests)

 

Sample Form Setup:

<?php namespace ProcessWire;

$form = $modules->get('RockForms')->form;
$form->addRadioList('salut', 'Anrede', ['m' => ' Herr', 'w' => ' Frau']);
$form->addText('forename', 'Vorname');
$form->addText('surname', 'Nachname');
$form->addText('email', 'E-Mail-Adresse')
  ->setRequired('Bitte geben Sie Ihre Mailadresse an')
  ->addRule(Form::EMAIL)
  ;
$form->addSubmit('submit', 'Anfrage senden');

$form->onSuccess = function($form) {
  $form->createPage(1017, 'mailitem', date('d.m.Y H:i:s') . ', {forename} {surname} {email}');
  // send email
  // do whatever you want
  // return success message
  return 'thank you for your subscription';
};

echo $form->render();

For all available fields you can look here: https://doc.nette.org/en/2.4/form-fields

Validation: https://doc.nette.org/en/2.4/form-validation

Rendering: https://doc.nette.org/en/2.4/form-rendering

 

More complex form example of the screencast:

Spoiler

<?php namespace ProcessWire;
use \Nette\Utils\Html;

$form = $modules->get('RockForms')->form;
$form->getElementPrototype()->id = 'anfrage';
$form->showLabels = false;

$form->addGroup();
$form->addRadioList('salut', 'Anrede', ['m' => ' Herr', 'w' => ' Frau']);
$form->addText('forename', 'Vorname');
$form->addText('surname', 'Nachname');

$form->addGroup();
$form->addText('tel', 'Telefon');
$form->addText('email', 'E-Mail-Adresse')
  ->setRequired('Bitte geben Sie Ihre Mailadresse an')
  ->addRule(Form::EMAIL)
  ;
$form->addUpload('pdf', 'PDF')
  ->setRequired(false) // optional
  ->addRule(Form::MAX_FILE_SIZE, 'Maximum file size is 8MB.', 8 * 1024 * 1024)
  ->getComponent('pdf')->addRule(Form::MIME_TYPE, 'Erlaubte Formate: PDF, JPG, PNG', [
    'application/pdf',
    'image/jpeg',
    'image/png',
  ]);
  ->setOption('description', 'Letzte Jahresabrechnung Strom oder Gas (optional)')
  ;
$form->addCheckbox('dsgvo', 'Ich bin damit einverstanden, dass Energieguru meine Daten zum Zwecke der Auftragsverarbeitung speichert')
  ->setRequired('Sie müssen der Datenerfassung zustimmen')
  ;
$form->addSubmit('submit', 'Anfrage senden')
  ->setAttribute('class', 'uk-button-primary uk-button-large uk-width-1-1')
  ;

$form->onSuccess = function($form) {
  $form->linkedFields = [
    'pdf' => 'files'
  ];
  $form->createPage(1017, 'mailitem', date('d.m.Y H:i:s') . ', {forename} {surname} {email}');

  // return success message
  return '<div class="uk-text-center uk-padding-large"><h3>ENERGIEGURU bedankt sich für ihre Anfrage.</h3><div>Innerhalb der nächsten 24 Stunden erhalten Sie eine Rückmeldung bzw. Ihr individuelles Angebot.</div></div>';
};

echo $form->render();

 

 

Module is alpha and not well tested yet. It should be save to use, but there might be breaking changes in the future. Also I'm quite sure that not all possible situations are covered. There are some switch() statements that check for the fieldtype and I only implemented the ones I needed so far. All others are easy to add - please make pull requests if you find any unsupported fields. Thank you.

 

LIMITATIONS:

  • Multiform support limited

 

Changelog:

  • 6.8.18 add multiform support
  • 8.4.18 v2 little bugfix + load assets automatically
  • 19.3.18 alpha release
  • Like 29

Share this post


Link to post
Share on other sites
52 minutes ago, bernhard said:

Please let me know what you think

Great idea! :) Thanks for sharing! Are you thinking of maintaining and further developing this module into the de facto "form helper for developers"? That would be awesome ;)

Share this post


Link to post
Share on other sites

Nice, seems you had more time on this than me on my very similar module :) 

If you (or someone else) could devote enough time I can share my module that I have already started to polish. Perhaps you could merge the two or get ideas from it:

nfh.thumb.gif.26b120f04e18d496ef7c3d8afb5e1c15.gif

  • Like 12

Share this post


Link to post
Share on other sites
14 hours ago, szabesz said:

Great idea! :) Thanks for sharing! Are you thinking of maintaining and further developing this module into the de facto "form helper for developers"? That would be awesome ;)

I think I don't get your point - it is already exactly what you are suggesting. And yes I plan to maintain it, since I need forms often and it was always a pain to get them running properly. Though I will 100% stick to uikit, so if anybody needs support for any other frameworks just open a PR and add code here: https://gitlab.com/baumrock/RockForms/blob/master/RockForms.module.php#L144

14 hours ago, tpr said:

Nice, seems you had more time on this than me on my very similar module :) 

If you (or someone else) could devote enough time I can share my module that I have already started to polish. Perhaps you could merge the two or get ideas from it:

Not really, but forms always bothered me for a long time (you know it), I was not really happy with my nette implementations, I knew you had one implementation lying around somewhere but then I decided to implement it on my own ;) It was fun :)

Your screencast looks nice, but I'm not sure if we should go this direction... I don't want to build a replacement for FormBuilder. The intention is to make it really really easy and fast (and secure) to get up and running with simple forms (like contact forms and newsletter subscriptions). This always seems to be a little request for my clients but it actually meant a lot of work for me. I've purchased and tried FormBuilder but somehow didn't like it. Maybe because I find it easier and more fun to program my own version than learning to use otherone's software :) 

What you are doing in your screenshot can already be done easily with my module: Just setup the template and fields you need and then put the code in the onSuccess() callback, for example:

$form->onSuccess = function($form) {
  // send mail
  ...
  $mail->body = $pages->get(123)->mailBody; // multilanguage email response text field
  ...

  // redirect to any page
  $this->wire->session->redirect('yourpageurl');
}

I don't want to define a GUI for this module since I don't think it is necessary. It would mean a lot of work and not a lot of benefits. Or even worse, it would cost flexibility. If you need a GUI for your clients it's easy to build one on your own. And it would be totally customised and not packed with features nobody needs (thats the beauty of PW imho).

 

Having said all that, I'm happy to hear feature suggestions that you think would make sense for such a module. For example a method to render the form into a HTML table to send via mail is on my mind but not implemented yet.

  • Like 3

Share this post


Link to post
Share on other sites

I see your points - I started that way too. Then I realized it's much easier to handle multi-language strings like success message, etc so I added a GUI. I still think this is an improvement, I can make adjustments much easier in the admin when needed.

But it's not a requirement, you can use it purely from code too.

The form process part looks like this:

    if ($nfh->checkSuccess()) {
        $nfh->saveToPage($nfh->getTitle('name, email'));
        $nfh->sendAdminEmail();
        $nfh->getResponse();
        exit;
    }

Here saveToPage, sendEmail, sendAdminEmail methods can have parameters so you don't need to use the corresponding admin page.

If there's a page for it, then adding a form is as easy as

$nfh = addForm(1066);
echo $nfh->form;
// or $nfh->renderForm()

The module loads the form and form field files from page ID 1066 name.

Anyway, I think I'll clean up it a bit and share as I see there's a need for it.

  • Like 3

Share this post


Link to post
Share on other sites
41 minutes ago, bernhard said:

I'm happy to hear feature suggestions that you think would make sense for such a module

This is what I implemented so far:

  • ajax submit
  • datepicker, autocomplete, autogrow, character counter
  • add CSS classes to controls for better CSS targetting
  • add date, ip and language to each submission

I've also added a honeypot but on larger sites it doesn't seem to be effective. I need to integrate a captcha, this is one of the reasons I haven't shared it yet.

  • Like 1

Share this post


Link to post
Share on other sites
16 hours ago, bernhard said:

CSRF protection (by processwire)

There's $form->addProtection() in Nette forms but that always throws an error. How you managed to add this?

Share this post


Link to post
Share on other sites
22 minutes ago, tpr said:

There's $form->addProtection() in Nette forms but that always throws an error. How you managed to add this?

As I said, I used the PW internal one ;) https://gitlab.com/baumrock/RockForms/blob/master/RockForms.module.php#L68-75

37 minutes ago, tpr said:

If there's a page for it, then adding a form is as easy as


$nfh = addForm(1066);
echo $nfh->form;
// or $nfh->renderForm()

Sorry, I don't understand your first post. Do you mean you setup an admin page and then you can render this page directly as form on the frontend? Interesting idea. This way one could use the processwire admin to build forms. Then on the frontend we could to $form->loadFromPwTemplate('myrequestform'); I think that would make a lot of sense since the page is needed for logging anyhow.

34 minutes ago, tpr said:

This is what I implemented so far:

  • ajax submit
  • datepicker, autocomplete, autogrow, character counter
  • add CSS classes to controls for better CSS targetting
  • add date, ip and language to each submission
  • Ajax: I guess you implemented a vanilla js solution?
  • datepicker, autocomplete, autogrow, character counter
    Pff, no need for this at the moment. where/how do you define those settings?
  • Add css classes
    Good point. I'm sure this will be useful.
  • date/ip/lang
    Mhm, might make sense as well.
Ok, so as soon as we start adding all those things it might also make sense to have a custom process module to handle the forms. And also install the necessary fields on installation. Should not be a big deal.
 
The problem is, that I need to work on my Datatables module now (will be a perfect companion for this module for listing all form submissions). Then finish some client work and then I might find time for such additions.

If anybody else thinks he can find time earlier feel free to take over. I think the module is already usable as is, I just wanted to share it early to start a discussion (which appearently worked out).
  • Like 4

Share this post


Link to post
Share on other sites

Thanks, I will try this CSRF instead.

No, the admin page is just holding the fields for messages, and it's also the parent page of the submissions. 
Form fields are defined in a php file, it would be very hard to map Nette Forms to fields.

It looks like this:

  • form page path: /forms/contact
  • form processor: /site/templates/forms/contact.php
  • form fields: /site/templates/forms/contact-fields.php

I use a separate file for fields, this way it's easy to copy and include (to another project).

Others:

  • yes, everything is jQuery-free
  • settings are defined at PHP level, and passed via data-xxx property. I need some special prefixes like "date::", "selector::" and such in certain cases, but perhaps this could be improved.
  • process module: maybe, I do not know much about them
  • Like 1

Share this post


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

Form fields are defined in a php file, it would be very hard to map Nette Forms to fields.

I don't think so. It would be easy to loop the fields defined in a processwire template and add the corresponding fields to the nette form (of course not with all settings like width, required etc. but that should be done 100% by nette imho). My idea was just to setup a pw template with the fields and then render the form in the template file like this:

<?php
$form = $modules->get('RockForms')->form;
$form->addFromTemplate('mypwformtemplate');

$form->getComponent('email')->setRequired();
...
$form->addSubmit('submit', 'Subscribe');

$form->render();

The type of the nette field could be defined via the PW field's title: text_forename (regular textfield), password_mypassword (password field), mycustomnettefieldtype_demo (custom nette field). For more complex forms there would still be the option to define the form manually (no drawback).

My setup needs only one php file to define the form and to process the input, not sure why you split that into two files?

  • Like 1

Share this post


Link to post
Share on other sites

But how would you add validation rules, setRequired, showIf-like stuff and others? Surely these can be added somehow but it would be too cheesy.

As I wrote, I wanted to have the fields to be in a separate php file, so I can reuse them in another project easily. The form processor part can be lengthy if it requires complex logic before saving to the DB, it's much cleaner this way. Plus the fields php file can also be lengthy, especially if you need to add custom (backend) validation functions too (in one such file I have 400 lines of code).

  • Like 1

Share this post


Link to post
Share on other sites
2 minutes ago, tpr said:

But how would you add validation rules, setRequired, showIf-like stuff and others? Surely these can be added somehow but it would be too cheesy.

$form = $modules->get('RockForms')->form;

//$form->addFromTemplate('mypwformtemplate');
// this would result in this initial form setup:
$form->addText('forename', 'Vorname');
$form->addEmail('myemail', 'E-Mail-Adresse'); // either by using an email field in pw or by setting the title to email_myemail
$form->addUpload('attachment', 'Anhang hochladen'); // using a pw file field with title upload_attachment --> upload_ will set the nette type and the rest will be used as the field's name

// then you can adjust your form via nette and do whatever you need:
$form->getComponent('myemail')->setRequired();
...
$form->addSubmit('submit', 'Subscribe');

$form->render();

 

7 minutes ago, tpr said:

As I wrote, I wanted to have the fields to be in a separate php file, so I can reuse them in another project easily. The form processor part can be lengthy if it requires complex logic before saving to the DB, it's much cleaner this way. Plus the fields php file can also be lengthy, especially if you need to add custom (backend) validation functions too (in one such file I have 400 lines of code).

Ok, I can imagine that this can make sense. I'll stick to my 1-file-setup since everybody can use include() whenever the need comes up :)

Share this post


Link to post
Share on other sites

I'm not entirely convinced :) You would have to create those fields and remember their names if you need to add setRequired and such things. But it's an advantage if you willl save it to a page with the same fields.

  • Like 1

Share this post


Link to post
Share on other sites
32 minutes ago, tpr said:

You would have to create those fields and remember their names if you need to add setRequired and such things

Sure, but that's the same for outputting fields in regular processwire templates... echo $page->myfield... You also need to remember the field's name here.

I almost wanted to implement the feature right away but I started with the renderTable() method, because it is more important. Also I'm not sure how my idea will work with groups and containers... I have to think about that.

Share this post


Link to post
Share on other sites

just added the renderTable() method:

$form->onSuccess = function($form) {
  $form->linkedFields = [
    'pdf' => 'files'
  ];
  $log = $form->createPage(123, 'rockforms_anfrage', date('d.m.Y H:i:s') . ', {forename} {surname} {email}');
  
  $m = new WireMail();
  $m->to('your@email.com')
    ->from('your@email.com')
    ->subject('Your great subject')
    ->bodyHTML($form->renderTable());
  
  foreach($log->files as $file) $m->attachment($file->filename);
  $m->send();

  // return success message
  return '<div class="uk-text-center uk-padding-large">'.
    '<h3>ENERGIEGURU bedankt sich für ihre Anfrage.</h3>'.
    '<div>Wir melden uns in Kürze bei Ihnen. Wenn Sie Ihre letzte Jahresrechnung hochgeladen '.
    'haben erhalten Sie in den nächsten 24 Stunden Ihr individuelles Angebot!</div>'.
    $form->renderTable([
      'wrapper' => '<div class="uk-card uk-card-secondary uk-card-body uk-margin">{table}</div>',
    ]).
    '</div>';
};

5ab13f98489a7_2018-03-2018_04_48-energieguru.at-Anfrage.thumb.png.9d18855f1a01927797a241e9b1e06d17.png

mail.png.3df636158e4bfaf2ed5b145d456ed468.png

I agree this could be automated, but at least until that is finished it is already REALLY simple to accomplish.

  • Like 2

Share this post


Link to post
Share on other sites

@bernhard this looks very promising, thank you for sharing!

It would be great if you could add Recaptcha V2 support. I was once struggling on getting it to work with a Nette Form implementation for a project.

Here is the most up to date and seemingly well maintained Nette extension I could find: https://packagist.org/packages/contributte/reCAPTCHA

  • Like 1

Share this post


Link to post
Share on other sites

Thanks @gebeer 

I took a quick look but have no time (and interest) to implement this at the moment. At least not as long as I don't have any spam issues with the existing honeypot solution. I'm happy to accept pull requests though.

I've fixed a small bug and I've added automatic loading of assets while checking your request. V2 is on gitlab :)

  • Like 1

Share this post


Link to post
Share on other sites

@bernhard Understood, never mind. Its only that I get requests from my clients to implement recaptcha. Otherwise I would stick to honeypot as well. Also, on one of the sites where recaptcha V2 is implemented, I still get some Spam. Not more or fewer than on sites with honeypot. But try to explain this to clients who want something specific because they heard the buzz word recaptcha...

Share this post


Link to post
Share on other sites
32 minutes ago, gebeer said:

Its only that I get requests from my clients to implement recaptcha. [...] But try to explain this to clients who want something specific because they heard the buzz word recaptcha...

I have a suggestion for an "explanation" for such a client in form of an offer you could send him:

  • Form with honeypot spam-protection: X €
  • Form with recaptcha spam-protection: X + Y €

And with the Y € we could fund the implementation of recaptcha into RockForms ;)

"Y" has to be totally overpriced, of course :D

  • Like 1
  • Haha 2

Share this post


Link to post
Share on other sites

just merged @gebeer's changes to support multiple renderers in separate files and he also added a bootstrap renderer :) thank you!

  • Like 2

Share this post


Link to post
Share on other sites

Awesome news, really! :) From now on, whenever I need a form, your module will be the one to go...

  • Like 1

Share this post


Link to post
Share on other sites

Hey @bernhard - thanks for this - I decided to give it a go in one of my current projects. I usually build from scratch using the PW form API, but thought it was time to give this a go given how good most Nette products are.

So far really enjoying using it :)

A few thoughts:

1) Any reason you are loading netteForms.js instead of netteForms.min.js ?

2) Any chance you'd consider making the RockFormsBranding div optional? It might be ok on some projects, but on others I really don't think I could have it displayed. Not trying to take credit away, but hopefully you understand.

3) I find the alert dialog really annoying and very 90's. I know it's part of Nette and not something you added (https://github.com/nette/forms/blob/849b672612b709227371f0e0c7d4ee9110206ddc/src/assets/netteForms.js#L304) but I would love to see it gone without hacking that file. I am tempted to make a request to David at Nette, or maybe you could if you agree? Or is there a way to disable this that I am not seeing?

Thanks! 

  • Like 2

Share this post


Link to post
Share on other sites

Hey @adrian, glad you gave it a try :)

1) No, I guess this was just for development. PR welcome (really need to work on other projects atm, see rockgrid and rockfinder for example and some client work).

2) Everybody who wants to (or needs to) remove the branding can contact me via PM

3) I agree. I've used Nette Live Validation in an older project and this looked a lot better, but I've just not had the time to implement it. This might be more complex when thinking about supporting different css frameworks. Or it might be as easy as loading this script: https://github.com/contributte/live-form-validation. I don't know and I wanted to go with the default tools as I'm short on resources for this module. And the most important part was that it works and is quick to setup and save, of course :) If you come up with a better solution I'm happy to merge it, but at the moment, I'm sorry, I have other priorities.

  • Like 2

Share this post


Link to post
Share on other sites

Thanks @bernhard to the link to that live-form-validation script - that is a much nicer experience!

  • Like 1

Share this post


Link to post
Share on other sites
5 hours ago, bernhard said:

2) Everybody who wants to (or needs to) remove the branding can contact me via PM

I'm not an expert on software licenses, but I think this restriction is incompatible with the license you are releasing the module under (which seems to be the MIT license based on the code comment here).

Quote

MIT, Removal of the branding in the rendered form is prohibited

The MIT license allows for modification, so anyone can modify the code to remove the branding markup. Not sure what sort of license would be compatible with what you intend - I think all the common open source licenses permit modification.

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.