AJAX Web Form for Adding Multiple Records to MVC Database

Saturday, April 30th, 2011|Beginner tips, Version 4|by Romans

Some time ago a PHP developer asked me – “How to add 50 employees though a single page”. Building a fluid data-entry web form which would allow adding unlimited records to the database without making user wait is not an easy task and normally require knowledge of JavaScript, AJAX, jQuery, PHP, HTML and CSS.

This blog post describes a simpler way to implement this complex task. I will be taking advantage of a Simple PHP UI Framework — Agile Toolkit. This will help me focus on the UI logic of the form instead of technical implementation and get job done within a very short time and little effort.

I set a number of goals in front of me, which are impossible to achieve with plain PHP or even with others, more complex frameworks. This example also helps to illustrate approach of Agile Toolkit in building Web UI, creating re-usable code and using AJAX. My goals are:

  • Code must be no longer than 50 lines of code, no custom HTML or JavaScript;
  • Must look very simple to read and understand;
  • User can continue entering data without stopping using Tab key and keyboard only;
  • Data must be submitted and saved automatically;
  • Must work with any Model therefore can be used for purpose of adding any type of record;
  • Must never reload page or make user wait. Should work even on slow internet connection;
  • Resulting code must be re-usable and customizable.

Agile Toolkit was a great help. I successfully completed all the requirements and produced a clean data entry screen. In fact, the solution works so great that I turned it into official add-on. Any user of Agile Toolkit can take advantage of this add-on by writing:

[php]
$this->add(‘InfiniteAddForm’)->setModel(‘Employee’);
[/php]

To find out more and download Agile Toolkit for free, open-source version, visit http://agiletoolkit.org. If you are curious about how I solved this task, continue to read this article.

The AJAX Strategy

Infinite Add Form Sketch

Before I started on the implementation I had to think about the AJAX Strategy.

I will be adding a new “View” class, which will implement the container for generically added forms. One form while be added initially. When first field of the form is focused we will append “<div>” into the “View”, then load next form in there.

Agile Toolkit automatically assigns “id” of objects when positioning them. In this case, however, it needs slight help without which it will add all new forms with same “id”. Therefore we need to make use of “counter”, a variable which is initially 1 then incremented with each newly added form.

When form is updated and last field of the form looses focus (blur) we need to submit form, save data and remove form.

Creating new class

The new class called “InfiniteAddForm” is not actually a form, but a View containing forms. I used a snippet in my editor to create the following file in lib/InfiniteAddForm.php:

[php]
<?php
class InfiniteAddForm extends View {
public $form;
function setController($c){
$this->c=$c;
$this->addForm($_GET[$this->name]?$_GET[$this->name]:1);
}
function addForm($seq){
$this->api->stickyGET($this->name);
$this->form=$f=$this->add(‘MVCForm’,
$seq,null,array(‘form_horizontal’));
$f->setController($this->c);
$next_form=$this->name.’_’.($seq+1);
}
}
[/php]

In this code I am redefining setController method (which is called either be called directly but is also used by setModel()). I separated actual form addition into a function addForm() which requires a sequence number as an argument. $this->name refers to the unique name of the View and I am using it as a GET property name. Using it this way will allow me to have multiple instances of the view work properly on the same page. If argument is not set, the “1″ is used instead.

Inside addForm() function i am creating the form object. Before that I am setting stickyGET argument. This will automatically add a get argument to all generated URLs from this point on. The rarely used 2nd argument which defines the name of the form is handy here. The full name of the form is used by concatenating name of view with 2nd argument and will look like this: myapp_page_infiniteaddform_1. Fourth argument defines a template for our form, which we would like to lay out horizontally. Alternatively you could use setFormClass(‘horizontal’). Setting controller for the form will populate the fields. Finally we would want to calculate name of the next form which we would be adding.

I re-used model from Agile Toolkit Introduction Step4. Also I needed the following code to add my view on the page:

[php]
$this->add(‘InfiniteAddForm’)->setModel(‘Employee’);
[/php]

After adding this, I could see the form in my browser:

The next step was to determine the first and the last field of the form generically. This code goes into addForm() function:

[php]
$first_field=null;
foreach($f->elements as $element){
if(!($element instanceof Form_Field))continue;
$element->js(true)->univ()->disableEnter();
if(!$first_field)$first_field=$element;
$last_field=$element;
}
$first_field->setAttr(‘class’,'nofocus’);
[/php]

Form might have all sorts of elements and we are only interested in fields. While at it, we are going to disable “Enter” to prevent user from accidentally submitting form before going through all the fields.

Finally we need to add some AJAX / JavaScript action. Have you thought that I would send you to write a bunch of JavaScript code? No! It’s up to 3 actions:

  • Create new element by using jQuery’s append() function
  • Initiate AJAX request to load new form inside this element
  • Unbind focus() event so that we don’t trigger it several times

The following code uses Agile Toolkit jQuery Chain class to produce compact JavaScript code:

[php]
$first_field->js(‘focus’,array(
$this->js()->append(‘<div id="’.$next_form.’"/>’),
$this->js()->_selector(‘#’.$next_form)->atk4_load(
$this->api->getDestinationURL(null,
        array($this->name=>$seq+1,’cut_object’=>$next_form))),
$first_field->js()->unbind(‘focus’),
));
[/php]

The total of 3 chain safe created.

First is applied to the view and results in jQuery code:

$('#sample_project_infiniteadd1_infiniteaddform').append(
   '\x3cdiv id=\x22sample_project_infiniteadd1_infiniteaddform_2\x22/\x3e');

The argument to JavaScript is properly fully escaped to avoid JS Injection. Second chain uses API to generate url to the same page, but with addition of 2 arguments. $this->name is used to specify name for the new form as I explained before. Second, cut_object instructs Agile Toolkit that instead of loading full page, only one object must be displayed. This generates the following JavaScript code:

$('#sample_project_infiniteadd1_infiniteaddform_2').atk4_load(
    '/infiniteadd1?b=devel\x26sample_proect_infiniteadd1_infiniteaddform=2
    \x26cut_object=sample_project_infiniteadd1_infiniteaddform_2');

From the code you can see that Agile Toolkit have added ?b=devel argument too, which was added as a Sticky GET argument. Third chain is converted into:

$('#sample_project_infiniteadd1_infiniteaddform_1_name').unbind('focus');

Ability to transparently generate jQuery chains like that is a unique and very powerful feature of Agile Toolkit. All of those actions are enclosed into a function bind() call and can be seen through View HTML Source:

$('#sample_project_infiniteadd1_infiniteaddform_1_name').bind(
    'focus',function(ev){ ev.preventDefault(); ... });

Testing this in the browser, focusing first field of each form, automatically adds one more form to the page:

Finally we also need to add code to save forms and rid of them once they are tabbed out. That will happen when last field of the form is blurred.

[php]
$last_field->js(‘blur’,$f->js()->submit());
if($f->isSubmitted()){
$id=$f->update();
$f->js()->remove()->univ()
->successMessage(‘Saved record #’.$id)->execute();
}
[/php]

Also form submit handling is added. That brings us to conclusion. Full code sample and line counting. However before that I must say that with very minor changes I have added this View into atk4-addons/misc/lib and you are free to use it inside your project, instead of copy-pasting all the code below. This blog post was intended to who how simply and easy it is to add complex dynamic functionality by using PHP alone.

The code for a new view consists of 38 lines of code and the complete source is below. NOTE: you will need a most up-to-date development version from GitHub which adds support for “nofocus” class and form_horizontal template. Visit http://agiletoolkit.org/download for download instructions.

You can also see resulting code in Agile Toolkit Code-pad: http://codepad.agiletoolkit.org/infiniteadd1?b=devel

[php]
class InfiniteAddForm extends View {
function setController($c){
$this->c=$c;
$this->addForm($_GET[$this->name]?$_GET[$this->name]:1);
}
function addForm($seq){
$this->api->stickyGET($this->name);
$this->form=$f=$this->add(‘MVCForm’,
$seq,null,array(‘form_horizontal’));
$f->setController($this->c);
$next_form=$this->name.’_’.($seq+1);

$first_field=null;
foreach($f->elements as $element){
if(!($element instanceof Form_Field))continue;
$element->js(true)->univ()->disableEnter();
if(!$first_field)$first_field=$element;
$last_field=$element;
}
$first_field->setAttr(‘class’,'nofocus’);

$first_field->js(‘focus’,array(
$this->js()->append(‘<div id="’.$next_form.’"/>’),
$this->js()->_selector(‘#’.$next_form)->atk4_load(
$this->api->getDestinationURL(null,
array($this->name=>$seq+1,
‘cut_object’=>$next_form))),
$first_field->js()->unbind(‘focus’),
));

$last_field->js(‘blur’,$f->js()->submit());
if($f->isSubmitted()){
$id=$f->update();
$f->js()->remove()->univ()
->successMessage(‘Saved record #’.$id)->execute();
}
}
}
[/php]

Feel free to re-distribute, re-blog, re-tweet this article. I hope it will introduce a faster and simpler way to build a magnificent PHP Software.

2 Comments

Mariano
Posted May 1, 201112:44 am

Much better than 50 employees example! :D
Thanks a lot!

Fernando Martin
Posted May 1, 20114:09 am

Geez! How powerful is ATK, and getting even stronger!

Thumbs up guys! Congrats.
Hoping to become familiar with the toolkit and start using it as my best tool.