Form & shortcode 1

Custom Forms with shortcodes - part 1

The first in a set of articles on creating forms using shortcodes. The SubscriptionForm we're going to build is self-contained and has its own Controller. The only thing you need to do is  register the shortcode and set up a simple rule - both in _config.php. The form can also be placed in the page controller directly, like any other form.

Don't know about shortcodes? A mini-introduction to shortcodes in SilverStripe can be found here.

Note 11-02-2012: now using Form::sessionMessage() to create a simpler cleaner submissioncheck

The target: a subscription form

In this example we're going to create a simple subscription form that can be instantiated from a Shortcode. It can easily  be packed into a module, that will work out of the box once a Shortcodehandler is registered and a rule is set.

  • Create a custom subscription form with a custom template
  • Create a shortcode handler so the form can be injected in the content area
  • Create a custom controller to handle submission
  • save the subscription to DB, using a Subscription object.
  • send an email to a predifned e-mailaddress.
  • respond with a success/failure message stored in the session variable 

A later article will handle the use of multiple forms on one page, and using shortcode params to a topic and an e-mailaddress for each form. 

Subscription DataObject.

Let's start by creating a simple Subscription DataObject (of course it can be easily extended):

class Subscription extends DataObject {

	static $db = array(
		'FullName' => 'Varchar(255)',
		'Email' => 'Varchar(255)',
		'Topic' => 'Varchar(255)'
	);

	static $searchable_fields = array(
		'FullName',
		'Email',
		'Topic'
	);

	static $summary_fields = array(
		'FullName',
		'Email',
		'Topic'
	);
}

This very basic object has 3 fields: FullName and Email are obvious, the use of Topic will become clear later. We'll let the Subscription object decide how it wants to be represented in a form, by subclassing the getFrontendFields() function:

class Subscription extends DataObject {


	( ... )

	/*
	 *  restrictField: show only these fields.
	 *  fieldClasses: override the scaffoldr, by defining what fielddtype to use
	 */
	public function getFrontendFields() {
		$fields = $this->scaffoldFormFields(array(
			'restrictFields' => array(
				'FullName',
				'Email',
				'Topic'
			),
			'fieldClasses' => array(
				'Email' => 'EmailField',
				'Topic' => 'HiddenField'
			)
		));
		return $fields;
	}

Note that Topic is created as a hidden field. Later we'll add the topic, as a special option, from a Shortcode parameter, we don't want the user to enter it, so a hidden field will do fine...

The Subscription form - a custom form

Create a file SubscriptionForm.php like this:

class SubscriptionForm extends Form {

	function __construct($controller, $name, $arguments=array()) {

		$fields = singleton('Subscription')->getFrontEndFields();

		$fields->push(new HiddenField('FormID', 'FormID', $this->formID));

		$actions = new FieldSet(
			new FormAction("SubmitForm",  _t('Subscription.SUBMIT','Send'))
		);

		$validator = new RequiredFields(
			'FullName',
			'Email'
		);

		parent::__construct($controller, $name, $fields, $actions, $validator);

		if(class_exists('SpamProtectorManager')) {
			SpamProtectorManager::update_form($this);
		}
	}
}

This is a very simple form. The one thing to notice is the use of the getFrontendFields() function to create the proper fields. Note that we've already added support for the Spamprotectionmanager module! Just leave it out if you don't want it.

Shortcode handler and registration

Let's start with a very basic shortcodehandler. The beauty of it is that you can add it to the SubscriptionForm class, as long as you register it properly. So

In SubscriptionForm.php:

class SubscriptionForm extends Form {

	( ... )

	public static function SubscriptionShortcodeHandler($arguments) {
		$form = new SubscriptionForm(Controller::curr(), $arguments);
		return $form->forTemplate();
	}

In _config.php:

ShortcodeParser::get('default')->register(
    'Subscription',
    array('SubscriptionForm' ,'SubscriptionShortcodeHandler')
);

Now we should be able to show the form by adding [Subscription] into the TinyMCE content area of any page, and have the form appear on the site! Not doing anything though...

Form submission

In our SubscriptionForm constructor we added a SubmitForm action, so now we need to construct the SubmitForm() function for it. And, again, we can just put it into the SubscriptionForm class, to keep everything tidy:

In SubscriptionForm.php

function SubmitForm($data, $form) {
		
	$subscription = new Subscription();
	$form->saveInto($subscription);
	$esult = $subscription->write();

	if (!empty($result)) {
		$this->sessionMessage('success', 'good');
	}
	else {
		$this->sessionMessage('failed', 'bad');
	}
}

Basically this will try to write the posted form into the Subscription DataObject, and set a session message for the SubscriptionForm to either 'success' or 'failure'. It will then redirect back to the page we came from.

A custom controller to handle submission

For submission to work, we need a Controller where the SubScriptionForm is instanciated so it can call its SubmitForm() method. Normally this would take place in the Page_Controller, where the function SubscriptionForm() would be created to return a new SubscriptionForm( ... ). But now, as we're using the Shortcode handler to create the form, there's no such function in the Page_Controller. So we can either put it there anyway - and destroy the independence of this setup - or create a custom controller class for it:

In you SubscriptionForm.php create a class:

class SubscriptionForm_Controller extends Controller {

	function index() {
		return new SubscriptionForm($this, 'SubscriptionForm');
	}
}

The last thing we need to do, is tell silverStripe to use this controller whenever a SubscriptionForm is submitted. At that point the action 'SubscriptionForm' is added to the URL, and we can define a rule to respond to that:

In _config.php:

Director::addRules(80, array(
	'$URLSegment/SubscriptionForm//$Action/$ID/$Name' => 'SubscriptionForm_Controller'
));

That's it! You might have to ?flush=1 or even /dev/build/?flush= 1, although the latter shouldn't be necessary - but sometimes it takes a while to get rid of the class not found errors. Why? Eh...

Check if the form was submitted...

After the form has been submitted, I want to show the success or failure message only, and leave out the form. For that we'll use the session message we just set. The Form has two functions we can use:Message() and MessageType(). The first will return eitehr nothing, or 'success' or 'failed'. The latter will return 'good' or 'bad'.

The Message() function will automatically remove the message from the session, so it won't show up next time round. Both functions can be called from the template, so we don't need to write any code for that.

The custom template

Tthis template is based largely on the existing Form template - the only new thing different is that it will either show a success/failure message - or show the form. Never both:

SubscriptionForm.ss

<% if Message = success %>
	<p class="MessageType">
		<strong><% _t('Subscription.SUCCES','Thank you for subscribing!') %></strong>
	</p>
<% else_if Message = failed %>
	<p class="MessageType">
		<strong><% _t('Subscription.FAILED',"An error occurred while submitting the form!") %></strong>
	</p
<% else %>
	<form $FormAttributes>
	    <% if Message %>
	        <p id="{$FormName}_error" class="message $MessageType">$Message</p>
	    <% else %>
	        <p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
	    <% end_if %>

	    <fieldset>
	        <% if Legend %><legend>$Legend</legend><% end_if %>
	        <% control Fields %>
	            $FieldHolder
	        <% end_control %>
	        <div class="clear"><!-- --></div>
	    </fieldset>

	    <% if Actions %>
	    <div class="Actions">
	        <% control Actions %>
	            $Field
	        <% end_control %>
	    </div>
	    <% end_if %>
	</form>
<% end_if %>

Don't forget to ?flush=1 after adding the template. Now we need to tell the form to use the template.

In your SubscriptionForm class:

function forTemplate() {
	return $this->renderWith(array('SubscriptionForm', 'Form'));
}

That's it. Note that everything is contained within the files Subscription.php, SubscriptionForm.php and subscriptionForm.ss - with the exception of a bit of code in _config.php. Once all is packed into a module, it'll have it's own config, and that will take care of that.

The topic...

In the Subscription object we left room for a 'topic' - something to distinguish one subscription from another... It's a hidden field, the user isn't going to enter anything, so it needs to come from somewhere else. Our shortcode could look something like this:

[subscription topic="seminar"]

Any parameters you add to the shortcode (parameternames are lowercase!) will be placed in the $arguments array parameter of the shortcode handler. So now the handler needs to feed the arguments to the form.

In your SubscriptionForm class:

public static function SubscriptionShortcodeHandler($arguments) {
	if (empty($arguments)) return '';
	$form = new SubscriptionForm(Controller::curr(), 'SubscriptionForm', $arguments);
	return $form->forTemplate();
}

You see we're feeding the arguments to the forms constructor in a new extra parameter, so we need to create it:

In your SubscriptionForm class:

function __construct($controller, $name, $arguments=array()) {

	$fields = singleton('Subscription')->getFrontEndFields();

	$fields->push(new HiddenField('FormID', 'FormID', $this->formID));

	// if a topic exists, store it in the (hidden) Topic field.
	if (!empty($arguments['topic'])){
		$fields->dataFieldByName('Topic')->setValue($arguments['topic']);
	}

	$actions = new FieldSet(
		new FormAction("SubmitForm",  _t('Subscription.SUBMIT','Send'))
	);

	$validator = new RequiredFields(
		'FullName',
		'Email'
	);

	parent::__construct($controller, $name, $fields, $actions, $validator);

	// enable MathSpamProtection (works!!!)
	if(class_exists('SpamProtectorManager')) {
		SpamProtectorManager::update_form($this);
	}
}

That's it. We now have a working shortcode driven Subscription form

What's next?

Form & shortcode 2 - email - send the subscription as an e-mail. Use an alias to add a custom 'to' e-mailaddress.

 

Comments

Het versturen van reacties is uitgeschakeld.

RSS feed voor reacties op deze pagina