Jump to content

Module want: form builder


apeisa

Recommended Posts

I am looking for something similar that webform is in Drupal. Some way to admins to build forms (contact form, order form etc). Entries are saved to database and send email if wanted.

Easy to use, most basic fields only (text input, textarea, email etc) at first.

Any thoughts how this should be done?

Link to comment
Share on other sites

First: It should be done.

Next: I think we could do this the very same way templates are done: Fields have their definitions and their 'administration-form-look', ui. If we add 'simple-form' and 'validation-rules' for this, we can actually reuse the very same fields we use in templates [or better yet, the very same types of fields].

This idea is just created: I wasn't thinking about any implications, just think about this process:

  • 1 – add new 'form-template'
  • 2 – define fields
  • 3 – define 'action' php file
  • 4 – include some tag or php code [tag is here better, because you may need to add this into wysiwyg]

Then in the 'action' file you can have these pseudo-functions [ideas of priceless functions:]

<?php
  //note: baic validation rules [required, e-mail, etc.] were defined in editor
  //note: also you checked 'sanitize' for fields you want have sanitized, so they are already
  $form->addValidationRule( /*description here*/ );
  $form->addValidationRule( '{x} + {y} >= 40' ); //some math validation rules?
  $form->addValidationRule( /* another one */ );

  if ($form->validate){
    $mail = new Mail();
    $db_do = new DBRecord();
    $form->redirect_after = '/page/url';
  }

Note: if no 'redirect_after' found, system returns to 'form' page. Show errors routine if not validate can be automated: just go back to form and show errors.

Note 2: As I see it, this is close to 0, if we talk about how much is done. But outlined model of form editor is very close to whole idea of PW – does the tedious stuff for you without taking away any freedom [anything you might need to customize – HTML, CSS – it's just not shown here, but we'll think it through]

After longer thinking, we might come with all posiible outcomes of forms – there isn't much really – you either show message 'ok', go to 'thank you' page or go to another step [if multiple forms]. There isn't anything in between. :)

   

Link to comment
Share on other sites

I would like something like that too, though don't know how it could be done without being something that generates markup. But this is one of the cases where markup generation is probably desirable. I am thinking the best bet will be to base it on ProcessWire's Inputfield modules, since they are already used for this purpose in the page editor (and throughout ProcessWire). And they already generate their own markup and retrieve and sanitize their own values. The main thing will be to write an editor to construct the form (similar to the template editor) and then something to save submitted entries to the DB (or optionally, page records). I haven't actually tried Drupal's form builder yet, I need to get a look at that.

Edit: I see Adam is thinking the same thing (and typing at the same time as me). :) It may be that the existing Templates system can be used for creating these forms as you mentioned.

Link to comment
Share on other sites

Ryan: I accidentally sent my post without finishing. Please, check modified [finished] post and reply what do you think. I also thought about what to do after, maybe you find some answers in there.

The last sentence was re: send mail/save into db action if everything's ok – I think we might not automatize this part – just give the authors really good API to use pages, DB & send mails at first.

Then, in the second part [when PW will be a little more user-centric (I think we are now 'web people – centric' ;D)], we can add some default editable actions and leave the advanced stuff optional.

Link to comment
Share on other sites

Adam, sounds like you have some good ideas.

I have to build a contact form for a site that's going up tomorrow and think I'm going to use the Inputfield modules to do it (manually) just to see how it might work. I'll post the template and result here tomorrow if you guys want to see it. I think it'll lead to some more thoughts on a form builder.

Link to comment
Share on other sites

Great to hear that you guys think this is important too.

My two cents

  • Default markup is needed here, it should be plug and play
  • Of course it would be nice to have full control of markup when needed
  • As Ryan said, template editor is actually form building (we define fields, admin site shows form for us). I think that the UI with current template editor is easy enough for non-technical content editors - so we should use that as much as possible

Very interesting to see your findings Ryan about using Inputfield modules to create front end forms.

Link to comment
Share on other sites

I'd add a vote for this too, it's such a common request and seems a pity if client always has to hire a dev to build a few simple forms. As you say Ryan you've essentially a form builder in there in any case for the admin... ;) Look forward to seeing your sample code for a contact form too.

Link to comment
Share on other sites

I worked to make a module early this AM before everyone woke up, and have been banned from the computer since. :) But got a minute to post it so figured I would, and then I'll post a live example tomorrow.

This is just a start on this module... lots more to cover obviously, but just wanted to get the momentum going. And it is fully functional even in this state, if anyone wants to try it out. I just experimented with simple forms having "name, email and message" text fields, so far.  

Here are usage instructions below (pulled from the module file), and the .module file is attached if anyone wants to try it or work with it. Just place it in: /site/modules/FormTemplateProcessor.module

/**
* Module to let you use templates as web contact forms.
*
* Can send you email and/or save the submission to a page in your site. 
*
* Usage:
*
* 1. In admin, create the fields you want to be part of the form.
* 2. Create a new template and assign your fields to this template.
* 3. Create another template for your contact form page (if you don't already have one).
* 4. Use the example below as a starting point for this contact form page: 
*
* $form = $modules->get('FormTemplateProcessor'); 
* $form->template = $templates->get('my_contact_form_template'); // required
* $form->requiredFields = array('fullname', 'email');
* $form->email = 'your@email.com'; // optional, sends form as email
* $form->parent = $page; // optional, saves form as page
* echo $form->render(); // draw form or process submitted form
*
* 5. Use CSS to style the fields. See the bottom of this file for suggestions.
*
*/

Download at GitHub:

https://github.com/ryancramerdesign/FormTemplateProcessor

  • Like 2
Link to comment
Share on other sites

Ryan: nice!

Tested it quickly: got this error

Call to undefined method stdClass::render() (line 19 of C:\apache\htdocs\pw\site\templates\page.php)

This is what I have on template file:

$form = $modules->get('FormTemplateProcessor');
$form->template = $templates->get('contact_form'); // required
$form->requiredFields = array('title');
$form->parent = $page; // optional, saves form as page
echo $form->render(); // draw form or process submitted form

I have template called contact_form, which has only two fields (title & body).

There might also be typo on your example: last $form is $contactForm

Link to comment
Share on other sites

You are right, that is a typo it should be $form not $contactForm. Do you still get the error after that correction? The error message seems to indicate that typo may be the problem. if not, can you double check that FormTemplateProcessor is installed in Admin > Modules? Thanks -Ryan

Link to comment
Share on other sites

Can you check at the bottom of the modules screen, there should be a "check for new modules" button (just added last week, so it wasn't there before). This was so that we could cache modules (faster) but you need to click the button for it to find them.

Link to comment
Share on other sites

  • 2 weeks later...

Hi Jim,

The purpose of Inputfields is to generate markup for inputs (and retrieve the resulting value). This is why they are separate from Fieldtypes. Since they were originally built just for the PW Admin use, I haven't made them configurable from a markup aspect. Though I do plan to change that, now that the utility of these Inputfields is expanding.

Currently, there is only one markup generation method in most Inputfields, and that is the ___render() method. So you can extend any Inputfield, and just override the ___render() method to change the markup it generates. But I'm guessing it's not the actual Inputfields that you want to override so much as the container code generated by the InputfieldWrapper class (with it's jquery-ui classes and list items and stuff). The reason I say that is because the Inputfield types just generate the actual form inputs, like an <input ...> tag. Whereas InputfieldWrapper generates everything else.

Until I can modify this class to make it's markup configurable, I would recommend styling the existing markup using the CSS classes in the comments at the bottom of the .module file as a starting point. 

But I think it will be relatively easy for me to make the InputfieldWrapper class have configurable markup, so it's something I can do quickly if you are interested. But I want to make sure that I'm understanding your needs correctly before I do that. If you just need to modify a single Inputfield's output or something, then we'll focus on that instead.

Link to comment
Share on other sites

Hey Ryan,

I have updated my CSS to use what you have to start and it works well. I mainly use site templates built by external designers for my work, so many times it's easier to just render forms like the designer's examples and have them work and function great. This way I don't have to spend a ton of time updating their css, js, etc.

What I would like to do is be able to override the wrapper class within a template rather than creating a new module to override the wrapper.

Maybe something like: (from your module...I have just changed it a bit so it's not a contact form anymore)

<?php
// get the collection of inputs that can populate this page's fields
$inputfields = $page->getInputfields();

// set values for fields and add them to the form.
foreach($inputfields as $inputfield) {
if ($inputfield->name == 'title') continue;
// populate values 
if ($page->name != 'create') {
	$inputfield_name = $inputfield->name;
	$inputfield->value = $this->page->$inputfield_name;
}
// *****Maybe some markup here?*****
       $inputfield->markup("./markup/{$inputfield->type}");

if(in_array($inputfield->name, $required_fields)) $inputfield->required = true;
$form->add($inputfield);
}

Just a thought :)

-Jim

Link to comment
Share on other sites

  • 2 months later...

Ryan: any quick tutorial how to use inputfields outside of admin? I am actually building an alternative admin view (so these won't be public forms), but need to keep current admin as it is. I have build few of these with custom forms, but now I would need so many forms that it would be great to use all the great inputfield modules.

What I would like to achieve is something where I could just say that "render form for that $page and process it when it is submitted". It would then use all the inputfields that are set to those fields (like AsmSelect, radio etc). And I think sometimes I would need to process forms by myself so that is not requirement. But ability to easily render forms and their input "widgets" is what I need.

I tried to figure out how the things work from current admin, but got confused at some point (I am pretty new to oo programming).

Link to comment
Share on other sites

Ok, this gives me form easily, but it doesn't include any of the required .js or .css files:

<?php 
$form = $this->modules->get('InputfieldForm');
$fields = $somepage->getInputfields();
$form->append($fields);
echo $form->render();

I have added these lines to head:

<?php foreach($config->styles->unique() as $file) echo "\n\t<link type='text/css' href='$file' rel='stylesheet' />"; ?>
<?php foreach($config->scripts->unique() as $file) echo "\n\t<script type='text/javascript' src='$file'></script>"; ?>

But only thing that gets included are JQueryFancyBox.css & .js, I have images field, AsmSelect etc.

Link to comment
Share on other sites

It looks like you've got it right to me. The only thing I'm wondering is if in your example, does the first snippet get executed before the one that includes the styles/scripts? It would have to in order for those styles/scripts to be populated... i.e. you'd have to render those fields and store them in an output variable before rendering the styles/scripts in your document <head>. This is the way PW always works, in that the document wrapper (final output template) gets generated last, just so that all factors (styles, scripts, ajax, etc.) are known ahead of time and can be accounted for.

When it comes time to process the form, you can do this:

<?php
if($input->post->submit_save) {
   // replace 'submit_save' (above) with the name of your 'save' button field name.
   // tell the form to process input from the POST vars, $input->post:
   $form->processInput($input->post); 
}

If you want to check if there were any errors (after calling processInput above):

<?php
$errors = $form->getErrors();
if(count($errors)) {
   // $errors is an array of strings with error messages
   // each Inputfield also highlights it's own errors the next time you call render()
}

Here's how you can retrieve the values from any given field:

<?php
$inputfield = $form->get('your_field_name');
$value = $inputfield->value; 

// or a shorter version of the same thing: 
$value = $form->your_field_name->value; 

You can also iterate the form:

<?php
foreach($form as $inputfield) {
   echo "<li>{$inputfield->name} = " . htmlentities($inputfield->value); 
}

The only thing to note about iterating the form is that some form fields contain more fields within them. You can identify them by the type:

<?php
if($inputfield instanceof InputfieldWrapper) {
   // field contains more fields
}

So to process a form for a page, you might want to use a recursive function like this:

<?php

function populatePage($page, $inputfield) {
   if($inputfield instanceof InputfieldWrapper) {
       foreach($inputfield as $i) populatePage($page, $i); 
   } else {
       // set the value to the page
       $page->set($inputfield->name, $inputfield->value); 
       // optionally perform additional validation on the $inputfield->value before setting it
   }
} 

$form->processInput($input->post);
populatePage($page, $form); 

Another approach for the same thing is to iterate $input->post, which puts them in a flat (non-recursive) context. But in this strategy, you have to do more validation to make sure the submitted fields are ones you intend to populate:

<?php

$form->processInput($input->post);

foreach($input->post as $key => $unused) {
   $inputfield = $form->get($key); 
   if(!$inputfield || !$inputfield instanceof Inputfield) continue; 
   if(!$page->fields->has($inputfield->name)) continue; 
   // optionally perform additional validation here
   $page->set($inputfield->name, $inputfield->value); 
}

You can of course retrieve your values directly from $input->post (or $_POST), but the advantage of retrieving it from the $inputfield->value is that many inputfields perform some basic validation on the values you set to them.

In addition, every time you set a value to a page, it passes through the related Fieldtype's sanitize() method. That means that all values set to a page are converted to the right type for the Page. For example, a string submitted for a file in the POST vars is converted to a Pagefile object when you call $page->set($key, $value)... that in turn calls the Fieldtype's sanitize() method. This all happens behind the scenes.  

An important thing to note is that these Fieldtype sanitize() methods are there to sanitize for type, not security. That Fieldtype::sanitize method doesn't know if it's being provided submitted input, or something that you just set to the $page from the API, or something loaded from the DB. So the sanitize method for FieldtypePage (as an example) will ensure that whatever gets sent to it is a Page or something that it can convert to a Page (like a string or array of strings representing pages). Whereas something like FieldtypeText will accept any text you provide to it... after all, a <script> tag may be a very legitimate thing in your text field. But a <script> tag can also be a very dangerous thing in another situation. So if you are using Inputfields outside of an administrative context, or in a context where you want to enforce specific values, you may want to perform additional validation according to your need. The PW admin doesn't need additional validation, but your own forms may.

Here's an example of validating a field for specific needs before setting it to a page. In this case, we know that in our field 'message' we just want plain text and no markup, and we want to limit the length to 500 characters. In our 'message' field settings, we have the "encode entities" output filter turned on, so we are not encoding entities as part of the validation.

<?php

$inputfield = $form->get("message"); 
$value = strip_tags(trim($inputfield->value));
if(strlen($value) > 500) {
    $value = substr($value, 0, 500);
    $inputfield->error("Truncated length of this field to 500 characters");  
}
$page->set('message', $value); 

Link to comment
Share on other sites

Can't applaud you enough for this Ryan! Thank you.

I had that wrong - rendering form after that second snippet. This wasn't enough tough, since I got some minor issues left. Here is code how I got it working (form rendering & inputfields):

This is before any html output:

<?php
// Get the page you need to edit
$somepage = $pages->get('/some/page/');

// Jquery and UI is needed (though you could probably use these outside pw)
$modules->get('JqueryCore');
$modules->get('JqueryUI');

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

// Add fields to the form
$fields = $somepage->getInputfields();
$form->append($fields);

// Add save button
$field = $this->modules->get('InputfieldSubmit');
$field->attr('id+name', 'submit_save');
$field->attr('value', 'Save');
$form->append($field);

// Render the form, but don't echo here
$form->out = $form->render();

// Not required, but if you want get styles from normal admin... Widgets (images, AsmSelect) works nicely without default styles, so you might wanna get naked here 
$config->styles->prepend($config->urls->adminTemplates . "styles/main.css");
$config->styles->append($config->urls->adminTemplates . "styles/ui.css"); 

This code goes between <head>:


// Some of the widgets use this data, so you get js errors without this
<script type="text/javascript">
<?php
$jsConfig = $config->js();
$jsConfig['debug'] = $config->debug;
$jsConfig['urls'] = array(
'root' => $config->urls->root,
'admin' => $config->urls->admin,
'modules' => $config->urls->modules,
'core' => $config->urls->core,
'files' => $config->urls->files,
'templates' => $config->urls->templates,
'adminTemplates' => $config->urls->adminTemplates,
);
?>

var config = <?php echo json_encode($jsConfig); ?>;
</script>

// And these outputs the .css and .js files for each module
<?php foreach($config->styles->unique() as $file) echo "\n\t<link type='text/css' href='$file' rel='stylesheet' />"; ?>
<?php foreach($config->scripts->unique() as $file) echo "\n\t<script type='text/javascript' src='$file'></script>"; ?>

And finally you echo the form where you need it (between your body tags, I assume):

<?php echo $form->out; ?>

You also might wanna debug this stuff, so set debug true on config.php and this to your template:

<?php if($config->debug && $this->user->isSuperuser()) include($config->paths->adminTemplates . "debug.inc"); ?>

This just renders the form and all the fancy widgets there is. I didn't get to processing the page, but with Ryan's examples it should be easy and I am looking forward to it.

Link to comment
Share on other sites

This is great! Thanks for posting this. This is a great guide for people that want to implement some of PW's form controls outside of the admin template. It looks like you've got all the parts covered. Since this thread is turning into a helpful tutorial and reference, I want to cover the different ways of getting and setting values to Inputfields:

<?php

// setting
$inputfield->attr('name', 'value');
$inputfield->set('name', 'value');
$inputfield->name = value; // same as set() above, but shorter 

// getting
$value = $input->attr('name');
$value = $inputfield->get('value');
$value = $inputfield->value; // same as get() above, but shorter 

The attr() method is designed for explicitly setting setting/getting attributes that should go with the form input. For instance, 'name', 'class', 'id', 'value', 'checked', 'type', or whatever other attributes you want to go with the input. It should not be used for anything else because whatever you pass to it will end up as an attribute on the markup that gets output.

Whereas the set(), get() and direct reference methods are designed for setting/getting other properties, like field configuration options. They also work with attributes like the attr() method, but it's better to use the attr() method when setting an attribute just to be clear to PW that you intend that to be an actual attribute with the markup that gets output.

If you look in PW's code, you might also see it using these two functions in some instances:

<?php
$inputfield->setAttribute('name', 'value'); // same as $inputfield->attr('name', 'value'); 
$value = $inputfield->getAttribute('name'); // same as $value = $inputfield->attr('name'); 

These are the same thing as the attr() method, and the preferred syntax is to use the attr() method in your code. Internally, PW translates the attr() method to either setAttribute() or getAttribute(), depending on the number of arguments. The reason setAttribute() and getAttribute() exist is for people creating new Inputfield classes... it's easier to override single purpose methods rather than multi-purpose methods (like attr). But on a public interface, it's easier to use a multi-purpose method like attr(), so I recommend ignoring setAttribute() and getAttribute() unless you are developing new Inputfields.

Variations of attr()

In addition to getting and setting single attributes, the attr() method can also set multiple attributes at the same time. Below are all the possible variations (some repeated from above):

<?php

// set single attribute (same as example above)
$inputfield->attr('name', 'value'); 

// get single attribute (same as example above)
$value = $inputfield->attr('name'); 

// set multiple attributes with same value
$inputfield->attr('id+name', 'value');

// set multiple attributes with different values
$attrs = array(
   'name' => 'value',
   'name' => 'value', 
   // etc.. 
   ); 
$inputfield->attr($attrs); 

Inputfields that contain other Inputfields

One type of Inputfield is the InputfieldWrapper, and it's designed solely to contain other Inputfields. Examples of InputfieldWrappers also include InputfieldForm and InputfieldFieldset (all are derived from InputfieldWrapper). On these Inputfields, the get() and direct reference can be used to retrieve any one of the fields by name:

<?php
$inputfield = $form->get('your_field_name'); 
$inputfield = $form->your_field_name; // direct reference works as alternate syntax

Internally, the two calls above translate to this:

<?php
$inputfield = $form->find("name=your_field_name")->first(); 

What that means is it'll find any field in the form, not just direct children of the field you are checking. So a call to $form->get('your_field_name') will return the associated field, regardless of of many fieldsets it's wrapped under. This is just to keep things simple. After all, the form fields are all living in the same namespace when the form gets output.

By the way, that find() method mentioned above can be used with any selector, just like with pages. It will return all inputfields in the form that match the properties you give it. But in practice, I've not ever needed it in my forms, so not sure how useful it really is.

You can add/remove Inputfields as children using these methods. In the example below, we'll assume that $form is an instance of InputfieldForm:

<?php
$form->append($inputfield); // append an inputfield to the form
$form->prepend($inputfield); // prepend an inputfield to the form
$form->add($inputfield); // same as append()
$form->remove($inputfield); // remove inputfield from the form 

Built in properties

All Inputfields have these built-in properties that you can set or get:

<?php

$inputfield->label = "The clickable label that appears above this field. Should only be a few words.";
$inputfield->description = "A longer description that appears below the label. Can be any length.";
$inputfield->notes = "A extra highlighted area that appears under the field. Can be any length.";
$inputfield->head = "Headline that appears below label/above description. "; // PW 2.1 only!

$inputfield->id; // HTML 'id' attribute. Auto-generated if you don't set it. 
$inputfield->name; // HTML 'name' attribute, required
$inputfield->value; // HTML 'value' attribute, if applicable
$inputfield->class; // HTML 'class' attribute, optional

$inputfield->required = 0; // value not required for this inputfield
$inputfield->required = 1; // value IS required for this inputfield

$inputfield->collapsed = Inputfield::collapsedNo; // Field will display open (this is the default)
$inputfield->collapsed = Inputfield::collapsedYes; // Field will display collapsed, requiring a click to open
$inputfield->collapsed = Inputfield::collapsedBlank; // Field will display collapsed only if blank
$inputfield->collapsed = Inputfield::collapsedHidden; // Field will not be rendered in the form
  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...
1.,  can this contact from template use any field in PW?

Just for clarification (for other people reading this), the posts above mine and Adam's aren't talking about the plugin module, they are just talking about regular API use of PW's Inputfield classes.

That FormTemplateProcessor module is just a proof-of-concept, and it may work with any field, but I have not tested it with anything other than text-based fields. In addition, the style recommendations included at the bottom of the module file only attempts to style these simple fields.

Even if it does work with any field, I would not recommend adding file/image uploads or things like that to a contact form. Most of PW's Inputfields are designed for administrative tasks, not contact form tasks -- It is overkill and overhead for simple contact forms. But for simple things like text, textarea, integer, email, url fields, the distinction doesn't matter much. For fields that deal with outside assets (file/image for example), those should not be put on a public contact form (nor do I think they would even work).

2., wouldn't it be better if you created release: thread for this plugin?

Probably so -- When I make updates to this next, I'll put it in it's own thread. I think in most cases people are better off creating their own forms for most things you would have on a site (at least that's what I do). But this module can be very handy in the right circumstances, especially as something to build from.

Link to comment
Share on other sites

  • 2 months later...

The form builder is working as expected (awesome!), until I actually submit the form when I get this error message while logged in as superuser (I am running PW 2.0):

Exception: New page '/support/support-ticket/0/' must be saved before files can be accessed from it (in [root]/.../.../wire/core/PagefilesManager.php line 100)

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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...