Soma

Create simple forms using API

Recommended Posts

Just wanted to share what I recently used to create forms in modules and in frontend using the API and Inputfield modules PW provides and uses on its own. I think many newcomers or also advanced user aren't aware what is already possible in templates with some simple and flexible code. Learning this can greatly help in any aspect when you develop with PW. It's not as easy and powerful as FormBuilder but a great example of what can be archieved within PW.

Really? Tell me more

The output markup generated with something like echo $form->render(); will be a like the one you get with FormBuilder or admin forms in backend. It's what PW is made of.

Now since 2.2.5~ somewhere, the "required" option is possible for all fields (previous not) and that makes it easier a lot for validation and also it renders inline errors already nicely (due to Ryan FormBuilder yah!). For example the Password inputfield already provides two field to confirm the password and will validate it. De- and encryption method also exists. Or you can also use columns width setting for a field, which was added not so long ago.

Some fields like Asm MultiSelect would require to also include their css and js to work but haven't tried. Also file uploads isn't there, but maybe at some point there will be more options. It would be still possible to code your own uploader when the form is submitted.

Validation?

If you understand a little more how PW works with forms and inputfields you can simply add you own validation, do hooks and lots of magic with very easy code to read and maintain.

You can also use the processInput($input->post) method of a form that PW uses itself to validate a form. So getting to see if there was any errors is simply checking for $form->getErrors();.

Also the $form->processInput($input->post) will prevent CSRF attacks and the form will append a hidden field automaticly. It's also worth noting that processInput() will work also with an array (key=>value) of data it doesn't have to be the one from $input->post.

Styling?

It works well if you take your own CSS or just pick the inputfields.css from the templates-admin folder as a start. Also the CSS file from the wire/modules/InputfieldRadios module can be helpful to add. And that's it. It's not very hard to get it display nicely.

Here an code example of a simple form.

<?php
$out = '';

// create a new form field (also field wrapper)
$form = $modules->get("InputfieldForm");
$form->action = "./";
$form->method = "post";
$form->attr("id+name",'subscribe-form');

// create a text input
$field = $modules->get("InputfieldText");
$field->label = "Name";
$field->attr('id+name','name');
$field->required = 1;
$form->append($field); // append the field to the form

// create email field
$field = $modules->get("InputfieldEmail");
$field->label = "E-Mail";
$field->attr('id+name','email');
$field->required = 1;
$form->append($field); // append the field

// you get the idea
$field = $modules->get("InputfieldPassword");
$field->label = "Passwort";
$field->attr("id+name","pass");
$field->required = 1;
$form->append($field);

// oh a submit button!
$submit = $modules->get("InputfieldSubmit");
$submit->attr("value","Subscribe");
$submit->attr("id+name","submit");
$form->append($submit);

// form was submitted so we process the form
if($input->post->submit) {

    // user submitted the form, process it and check for errors
    $form->processInput($input->post);

    // here is a good point for extra/custom validation and manipulate fields
    $email = $form->get("email");

    if($email && (strpos($email->value,'@hotmail') !== FALSE)){        // attach an error to the field
        // and it will get displayed along the field
        $email->error("Sorry we don't accept hotmail addresses for now.");

    }

    if($form->getErrors()) {
        // the form is processed and populated
        // but contains errors
        $out .= $form->render();
    } else {

        // do with the form what you like, create and save it as page
        // or send emails. to get the values you can use
        // $email = $form->get("email")->value;
        // $name = $form->get("name")->value;
        // $pass = $form->get("pass")->value;
        //
        // to sanitize input
        // $name = $sanitizer->text($input->post->name);
        // $email = $sanitizer->email($form->get("email")->value);

        $out .= "<p>Thanks! Your submission was successful.";

    }
} else {
    // render out form without processing
    $out .= $form->render();
}

include("./head.inc");
echo $out;
include("./foot.inc");


 

Here the code snippet as gist github: https://gist.github.com/4027908 Maybe there's something I'm not aware of yet, so if there something to still care about just let me know. Maybe some example of hooks could be appended here too. Thanks

Edit March 2017: This code still works in PW2.8 and PW3.

  • Like 46

Share this post


Link to post
Share on other sites

Great tutorial Soma! This is the best summary of using PW's Inputfields that I've seen.

I noticed you did $field->attr('id+name', 'email') so just wanted to explain what that is for those that may be unsure of the syntax. That syntax is basically saying to set the 'id' and 'name' attribute to have the 'email'. While every field needs a 'name' attribute (like in HTML) the 'id' attribute is optional… if you don't assign an id attribute, PW will make one up. If you intend to custom style a field with CSS or target it from javascript, then it's best to assign your own 'id' attribute. Otherwise, it doesn't matter.

// this…
$field->attr('id+name', 'email');

// …is the same as:
$field->attr('id', 'email');
$field->attr('name', 'email');

// …as is this (direct reference):
$field->id = 'email';
$field->name = 'email';

The advantage of using the attr() function over direct reference is that attr() can't ever collide with other Inputfield properties that might have the same name as a field attribute. It's basically your way of saying "this should definitely be an HTML attribute and not anything else." For recognized attributes like 'name' or 'value' it doesn't matter what syntax you use because an Inputfield already knows 'name' and 'value' are standard HTML attributes. But if you needed to add a custom attribute like "data-something", well then you'd definitely want to use the attr() method of setting.

That attr() method should only be used for things that would actually be HTML attributes of the <input>, because they will literally end up there. So if you do an $field->attr('label', 'Hello'); you'll end up with an <input label='Hello'> in the markup, which is obviously not something that you want. :) That's why you assign a non-attribute property like 'label' or 'description' directly, like: $field->label = 'Something';

Last note about $attr() is that it can be used for both setting and getting attributes:

$field->attr('value', 'something');
echo "The field's value is: " . $field->attr('value');

// same as:
$field->value = 'something';
echo "The field's value is $field->value";

To extend your example, lets say that you wanted the 'email' and 'password' fields in a fieldset titled "About You". You would create the fieldset, and then add/append the fields to the $fieldset rather than the $form. Then you'd add the $fieldset to the $form:

$fieldset = $modules->get('InputfieldFieldset');
$fieldset->label = 'About You';

$field = $modules->get("InputfieldEmail");
$field->label = "E-Mail";
$field->attr('id+name','email');
$field->required = 1;
$fieldset->append($field); // append the field

$field = $modules->get("InputfieldPassword");
$field->label = "Password";
$field->attr("id+name","pass");
$field->required = 1;
$fieldset->append($field);

$form->append($fieldset); 

Or lets say that you wanted those 'email' and 'password' fields to be each in their own column so that are next to each other horizontally rather than vertically. You would assign the 'columnWidth' property to both the email and password fields. In this case, we'd give them both a value of 50 to say that we want them to be a 50% width column:

$field->columnWidth = 50; 

To jump out of tutorial mode and into idea mode: lately I've been thinking that PW should have a YAML to Inputfields conversion tool in the core (something that would be pretty easy to build), so that one could define a form like this:

name: subscribe-form

action: ./

method: post

fields:

- name: full_name

label: Your Full Name

type: Text

required: 1

- name: email

type: Email

label: Your E-Mail Address

placeholder: you@company.com

required: 1

- name: pass

type: Password

label: Your Password

required: 1

- name: submit

type: Submit

value: Subscribe

And create it like this (where $yaml is the string above above):

$form = $modules->get('InputfieldForm');
$form->load($yaml);
echo $form->render();
  • Like 20

Share this post


Link to post
Share on other sites

This is really interesting stuff and I'm learning so much from it. I've already tested Soma's code and it works very well. Is there a way of configuring $form->render() so that it outputs different html (divs for ul/li etc.)?

  • Like 3

Share this post


Link to post
Share on other sites

Thanks a lot for these indications. It helps me a lot, even though I'm a little stuck on one thing : my form is OK, but I'm trying to use a datepicker in one field... is this possible?

Here's where I am :

I changed this part of Soma's code

// you get the idea
$field = $modules->get("InputfieldPassword");
$field->label = "Passwort";
$field->attr("id+name","pass");
$field->required = 1;
$form->append($field);

with

// you get the idea
$field = $modules->get("InputfieldDatetime");
$field->label = "Date";
$field->attr("id+name","date");
$field->required = 1;
$form->append($field);

And tried to play around with

$field->datepicker = 1;

...But I have to admit I don't really understand what I'm doing and I just know this doesn't work...

I've spent quite a few hours on that (don't laugh ;-)) and if one of you can give me a hint, I'd greatly appreciate!

Thanks !

  • Like 1

Share this post


Link to post
Share on other sites

This is a field that requires additional stuff thats why it doesn't work. It would work if you include jQuery UI (with js and css) and to init the datepicker to the input you either do it by yourself with a js script or you can use the wire/modules/Inputfield/InputfieldDatetime/InputfieldDatetime.js.

Depending on the jQuery UI theme you include, your form elements will get also styled. But you can roll your own or overwrite some stuff. Not sure what the best way would be for you but there are several.

Edited by Soma
rewrote prev mobile written text
  • Like 2

Share this post


Link to post
Share on other sites

This is really interesting stuff and I'm learning so much from it. I've already tested Soma's code and it works very well. Is there a way of configuring $form->render() so that it outputs different html (divs for ul/li etc.)?

No there isn't a way. Try stick with it is all I can recommend atm.

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for your reply.

I have now managed to get the datePicker working. I am still struggling with the style since even though I put the link to the stylesheet, it doesn't seem to work... Anyway, I'll look closer later... Looks like I have to link to several stylesheets... (I told you I'm no professional developper...)

Just a quick note : you wrote : wire/core/Inputfield/InputfieldDatetime/InputfieldDatetime.js . I found the file in wire/modules/Inputfield...

Anyway. Thanks again.

  • Like 2

Share this post


Link to post
Share on other sites

[quotamos]This is really interesting stuff and I'm learning so much from it. I've already tested Soma's code and it works very well. Is there a way of configuring $form->render() so that it outputs different html (divs for ul/li etc.)? [/quotamos]

you.can usage. $form->setMarkup(); und $form->setClasses(); two.set markups und html caresses.

see.eliamos /wire/core/InputfieldWrapper.php

  • Like 10

Share this post


Link to post
Share on other sites

WillyC is right! How could I missed it ;) So you can do:

$form->setMarkup(array(
   'list' => "<div {attrs}>{out}</div>",
   'item' => "<div {attrs}>{out}</div>"
));
  • Like 6

Share this post


Link to post
Share on other sites

Thanks, WillyC & Soma, that's great. I'm using a framework (foundation.zurb.com in this case), so your examples help me to output the appropriate markup.

Share this post


Link to post
Share on other sites

I read it before but, now I can't remember. How do you post to pages other than the current ( other than using action="./" )

do you post to the template? or page? I'm trying something like:

action="./othertemplate.php" and it gives me page not found.

Share this post


Link to post
Share on other sites

Post to the actual page url so something like

<form action='<?php echo $config->urls->root; ?>forms/mailer/' method='post'>

or whatever.

Share this post


Link to post
Share on other sites

I'm a bit confused by your question, why do you want to post to another url anyway?

It's a post to the same page the form lies. If you chose another url for the action you have to have code there to read the post and process it.

There's no such thing as post to a template. The code example is for building the form and process it at the same time, hardly ever need to separate that logic, but you could.

Share this post


Link to post
Share on other sites

I have mini version of the contact form which will be on most pages, and instead of putting the processing logic on every page, I thought I'd just post to the contact page and process it once there.

for some reason my contact page isn't receiving it. I probably just have a bug in there~

Share this post


Link to post
Share on other sites
I have mini version of the contact form which will be on most pages, and instead of putting the processing logic on every page, I thought I'd just post to the contact page and process it once there.

Assuming all the pages that have the contact form are using the same template (or including the same file to process the form), then you don't need to worry about "putting the same processing logic on every page." It may appear that there is duplication there, but there actually isn't. If the code is shared, it really doesn't matter whether it all posts to the same URL or not. A situation where it would matter is if you were using template caching on your pages. In that case, you could solve it by making all the forms submit to the same [uncached] page. Though you could also solve it just by setting a cache-disabling POST var in your template settings.

Share this post


Link to post
Share on other sites

Greetings,

I've been trying to make front-end forms work on my site, and learning but still a bit stuck!

What is the method for rendering the same fields that show up on the Admin "Pages" screen, but make them show on the front end instead?

Thanks,

Matthew

Share this post


Link to post
Share on other sites

What do you want to archive by doing this? For a public form? Or for logged in users to edit a page in the frontend?

You could, but not all fields are suited for front-end, you'll have to also include all css, js and js configs for special fields like page asm selects and file uploads.

An example that takes this approach (without special fields) is the http://modules.processwire.com/modules/form-template-processor/

  • Like 1

Share this post


Link to post
Share on other sites

Hello,

Thanks Soma.

Yes, I was looking at the "Form Template Processor" earlier. To give clients a different way to create pages, this is the kind of functionality I am looking at. For some of the site concepts I have in mind, I will have multiple authors who really should not be seeing anything except the form for creating/editing a page.

As a way to learn more about ProcessWire, I wanted to see if I can make this work just using the API, without a module.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Well, the admin is built for that purpose to add and edit pages. Every user that has a login is able to see the admin, even if he can't edit anything and if he knows the url. Although you could add hooks to check for roles to redirect out of the admin in case.

I didn't say use the module but look at it how it's done, it's also done using API (as everything in PW), so you would need that same codes anyway. I don't see any problems doing your own frontend forms based on a templates fields. But depending on the fields you need, it's not as easy as simply rendering the fields with a form wrapper and you're done (for the reasons mentioned). Jquery UI, inputfields css and scripts and the $config->js would also be needed for them, and some of them would be in the $config->scripts, $config->styles when you render fields on a page.

I've never tried to rebuild the backend for the frontend, and always try to use some shortcut link on the pages for them to edit or create pages using a simple link with url to the right admin screen.

If I combine that with &modal=1 and fancybox modal they don't even need to go to the backend.

<a class="fancybox" href="/processwire/page/edit/?id=1123&modal=1">edit page</a>
<a class="fancybox" href="/processwire/page/add/?parent_id=1022&modal=1">add page</a>

That's in cases sufficient to give a simple frontend editing capability with 2-3 lines of code.

  • Like 3

Share this post


Link to post
Share on other sites

Hello Soma,

Thanks for your patience helping me!

Most times, I definitely would just use the admin area with roles to accomplish the goal.

But in the case where "regular" members have edit/create rights, I'm sure there is a way to do this differently.

I found an old post where someone wanted essentially what I'm describing, and Ryan responded:

My interest in on the line where Ryan says this:

"output a form of just the fields you want them to edit and/or process them"

Yes!! How do we accomplish that?

Thanks again,

Matthew

Share this post


Link to post
Share on other sites

Errm, look the first post to see how it's done? Or where what in detail are you missing? Without a little more from your side we aren't able to help. :)

Share this post


Link to post
Share on other sites

Not sure what in detail you need, but got some example code mainly taken from the Template Form Processor render a pages form to edit in front-end.

https://gist.github.com/somatonic/5011926#file-from-php

Now you need to include scripts and css needed. Note this $config stuff is only populated after you generated the form in API. In the head.inc you could include script (taken from the admin template default.php and modified a little). Also you would maybe need the inputfields.css found in the admin templates folder.

This somehwhere in the <head>

Moved code to gist.github.com for convenience..
https://gist.github.com/somatonic/5011926#file-head-php


This will give you the basic things needed, I have not tested all fields, but file uploads and date picker or even custom color picker etc works fine.

  • Like 4

Share this post


Link to post
Share on other sites

EDIT: I see that this concept has been discussed in various forum threads. But I can't quite get a handle on what is the best practice when you have multiple authors who can create/edit pages but who should not have access to the admin area.

Haven't read that well.

Ok, this is kinda hard to make it short, there's many way to archive this in different levels. Are those users public-registered users or trusted users you give explicit accounts?

Just to give the most restricted, just front-end users:

Create appropriate roles or permissions. Like a "frontend_editor" Role and only add page view permission. You give only guest and this new role to the users. When they login via backend they won't see anything and get redirected to front-page. Voila.

In your templates and modules you check for the Role the user

if($user->hasRole("frontend_editor")){
// do stuff
} else {
// do other stuff
}

Edit: To give access to only certain field to edit. Well since you only have a front-end user with no access to anything, you control it via code in template. And my examples above shows a "$ignore_fields" array you could choose to ecxlude certain fields as you create the form this is simple. But anything possible in PW.

There's a module that enables per field limit access by Ryan http://modules.proce...eld-permission/ might be something to try. Don't know if it works with the form rendering method here.

To restrict certain users to pages. Since need to know which pages a user has you can add a page field to the user template, so you can add pages a user can edit by editing it's profile. Then you can check in your front-end code if a page he wants to edit is in his added pages. For adding pages you'll have to have to parent page field or something.

// does the page field contain the page he wants to edit?
if($user->editable_pages->has($editpage)){
// do edit form stuff
}

Or just use one page that is the parent of a branch he can "work". Use this to redirect the user to his page and do stuff and always check if the user has the rights when you do something.

/Just to give some examples

  • Like 3

Share this post


Link to post
Share on other sites

Greetings Soma,

Thank you very, very much for such a thoughtful and thorough response!

Yes, I see how this can work. It is a good example of just thinking a diffent (better, more efficient) way with ProcessWire.

I know the whole "front-end editing" idea is debatable and a lot of people don't even like it. And I plan to use it in limited ways. But the technique Soma deacribes seems like a very elegant way to make use of the existing administrative form elements but configure them in certain cases to behave as if they were front-end fields. This seems like a more secure way to handle it as well.

I look forward to testing this.

Thanks again,

Matthew

Share this post


Link to post
Share on other sites

I found Janko's FormToWizard code to be one of the simplest ways to implement a multi-page/multi-slide form. Now with the Processwire this just got easier. I tweaked his script a wee bit to work with the API forms described in this thread.

PWformToWizard.js

bunching your fields into fieldsets just as Ryan described above, each fieldset will become a page/slide. Instead using the <legend> tag that Janko uses, the script now generates a legend based on the label, specifically anything that comes before a colon ':'

first add the script:

<script type="text/javascript" src="PWformToWizard.js"></script>
 

then use this javascript to initiallize formToWizard:

$("#SignupForm").formToWizard();
 

the default styles are:

   <style type="text/css">
    body { font-family:Lucida Sans, Arial, Helvetica, Sans-Serif; font-size:13px; margin:20px;}
    #main { width:960px; margin: 0px auto; border:solid 1px #b2b3b5; -moz-border-radius:10px; padding:20px; background-color:#f6f6f6;}
    #header { text-align:center; border-bottom:solid 1px #b2b3b5; margin: 0 0 20px 0; }
    fieldset { border:none; width:320px;}
    legend { font-size:18px; margin:0px; padding:10px 0px; color:#b0232a; font-weight:bold;}
    label { display:block; margin:15px 0 5px;}
    input[type=text], input[type=password] { width:300px; padding:5px; border:solid 1px #000;}
    .prev, .next { background-color:#b0232a; padding:5px 10px; color:#fff; text-decoration:none;}
    .prev:hover, .next:hover { background-color:#000; text-decoration:none;}
    .prev { float:left;}
    .next { float:right;}
    #steps { list-style:none; width:100%; overflow:hidden; margin:0px; padding:0px;}
    #steps li {font-size:24px; float:left; padding:10px; color:#b0b1b3;}
    #steps li span {font-size:11px; display:block;}
    #steps li.current { color:#000;}
    #makeWizard { background-color:#b0232a; color:#fff; padding:5px 10px; text-decoration:none; font-size:18px;}
    #makeWizard:hover { background-color:#000;}
   </style>
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
   <script type="text/javascript" src="formToWizard.js"></script>
   <script type="text/javascript">
    $(document).ready(function(){
	    $("#SignupForm").formToWizard({ submitButton: 'SaveAccount' })
    });
   </script>
 

see janko's page for more info~

Now, just to add some responsive jquery validation to each 'next' button...

Edited by neildaemond
updated PWformToWizard-validate.js link
  • Like 7

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.