Knickers: Change your underwear!

Contact Form

This example introduces some of the version 2 aspects of Knickers. We'll create a very simple application that takes Contact information and writes the information to a file -- no database required.

We'll assume you have Knickers installed in your home directory. First, create our app (make sure to use the v2 flag):

./knickers/scripts/create_appskel.php contact -v2

The only question the create_appskel script will ask is if you have a database, to which you can answer No for this example. You should now have a new directory called "contact" in your home directory.

Make sure you can see your new app through a web browser, at contact/common (this assumes you already have apache set up).

Setup the form

We going to create a new page called Contact. Let's start with the panel class; put the following in contact/common/cls/Controller/Panel_ContactForm.class.php:

<?php
/**
* Provide the html and includes for our Contact page
*/
class Panel_ContactForm extends Panel
{
	var $javascriptIncludes = array('thing.js', 'vec.js');
}
?>

By default, Knickers will automatically add includes in the <head> for a given Panel -- namely, a css file and a javascript file (in this case, common/css/Panel_ContactForm.css and common/js/Panel_ContactForm.js respectively) if they exist, along with jQuery. The $javascriptIncludes array informs Knickers of a couple of additional javascript files we want to load.

In short, thing.js represents a given record type (a.k.a. "model") on the client side, and deals with sending requests to the server for create/read/update/delete operations on that record type. vec.js is a layer on top of that that facilitates getting data in and out of the html form and into thing.js

To use these tools, we create a simple html form in a template file corresponding to the panel class we just made; common/tpl/html/Panel_ContactForm.tpl :

<form class='vec'>
	<fieldset>
		<legend>Contact Me</legend>
		<div>
			<label>Name</label>
			<input type='text' class='modeled' name='name'/>
			<p class='error' name='name'></p>
		</div>
		<div>
			<label>Email</label>
			<input type='text' class='modeled' name='email'/>
			<p class='error' name='email'></p>
		</div>
		<input type='submit' value='submit'/>
	</fieldset>
</form>

The important aspects to note here:

Now, we have to hookup vec.js to the form; we do this in common/js/Panel_ContactForm.js:

$(document).ready(function()
{
	// set up the vec
	$('form.vec').vec({ "modeltype": "contact" });
});

At this point, if you were to load /site/ContactForm, you would see your contact form. If you fill it out and click submit, you should see a PUT request being sent in your browser console (i.e. firebug). However, that request will fail, because we haven't set up the server side yet.

Setup the model on the server

vec.js instructs thing.js to send its requests to the "api" script (common/api) for processing. In a nutshell, that script determines the model type (in this case, the request would be to /api/contact, so the model type will be "contact"), loads or creates a model instance of that type, populates it with data from the request, validates, and stores it.

In Knickers version 1, the role of the model was played by Thing (not to be confused with thing.js). In version 2, Thing has been replaced by DataModel. There are a number of differences between the two, but one of them is that DataModel does not require field configuration by default, so it is possible to use the base class if that's all you need.

In this case, however, we need to override the record storage process, because we want to write to a file instead of writing to a database. We create a sub-class of DataModel to deal with this (common/cls/Modeling/DataModel_contact.class.php):

<?php

class DataModel_contact extends DataModel
{
	function storeInternal()
	{
		// log the information to a file
		file_put_contents('/path/to/logfile.txt',
			date('r').
			' Name: '.$this->get('name').', '.
			'Email: '.$this->get('email'),
			FILE_APPEND);
	}
}
?>

Now if you enter some data into the form and click submit, you should see a new line added to your file. Alternatively you could send yourself an email with the contents, or do any number of other things with it.

Note that if you wanted to store the record in a database table, all you'd have to do is make sure your cfg/yourapp.conn.php file is correct, and create a database table called "contacts", with fields of contact_id (a serial/auto increment field), name, and email. At that point, you would either remove the storeInternal function above, or add parent::storeInternal() inside.

Validation

This form is only good if the user actually provides some information; so now we are going to modify our model to provide some validation. We are going to make both fields required, and check to make sure the email address looks reasonable at least.

<?php

class DataModel_contact extends DataModel
{
	function validateMe()
	{
		// make sure we actually got some data...
		if(!$this->get('name'))
		{
			$this->addErrorForField('name', 'MSG_EMPTY_INPUT');
		}

		if(!$this->get('email'))
		{
			$this->addErrorForField('email', 'MSG_EMPTY_INPUT');
		}
		// ...if we did get an email address, make sure it's reasonable
		else
		{
			$parcel = ParcelFactory::create('EmailAddress');
			$parcel->setValue($this->get('email'));
			if(!$parcel->isValid())
				$this->addErrorForField('email', $parcel->getInvalidDataErrors());
		}
	}


	function storeInternal()
	{
		// log the information to a file
		file_put_contents('/path/to/logfile.txt',
			date('r').
			' Name: '.$this->get('name').', '.
			'Email: '.$this->get('email'),
			FILE_APPEND);
	}
}
?>

The 'MSG_EMPTY_INPUT' strings are keys in the $MESSAGES array in the file knickers/dicts/common/en_US/Messages.php. You can create your own messages if you create dicts/common/en_US/Messages.php in your app. The advantage to using these message dictionaries is that it makes the system relatively easy to translate into other languages. For more information, see Text Handling.

Parcel (and its subclasses, Fields) are Knickers' way of performing validation of various types of data. You can see which types are available in the knickers/common/cls/Modeling directory, and you can create your own in your app.

Tell them something happened

Once the user submits the form successfully, it's a good idea to let them know. One way to do this is to pass a callback to the vec setup that shows a message. First, add something like this to your template file:

<p class='success-message' style='display: none;'>Thank you! We will contact you soon.</p>

(Obviously, you could put the style in common/css/Panel_ContactForm.css instead; I just put it here for brevity.)

Then, modify your javascript:

	$('form.vec').vec({ "modeltype": "contact",
		success: function()
		{
			$('.success-message').show();
		}
	});

And that's all there is to it! Have fun!