hydra12’s blog

Tech Stuff, TCEA, PHP, etc.

CakePHP and EXTJS Forms

Filed under: Cake, ExtJS — admin at 4:13 pm on Friday, January 11, 2008

UPDATE NOTE - I removed a trailing comma on line 38 of myForm.js. It was causing IE problems.
UPDATE NOTE 2 - I changed the way I named the variables in myForm.js so that they are more cake-friendly. That makes the controller work like a regular application (if $data isn’t empty, save . . .). I got this idea from cakebaker’s new tutorial using jquery (http://cakebaker.42dh.com/2008/02/24/edit-in-place-with-jquery-and-cakephp/)

OK, several people have asked how to do this, so here’s how it works. EXTJS forms use ajax by default. You can make them submit like regular forms (see here: http://extjs.com/forum/showthread.php?t=8397&highlight=cancel+form+submit), but I’m going to use ajax/json today. I’m going to create a form that will add to my users database/datagrid from my last tutorial. You need to at least read this one first and setup the basics (include EXT, the host variable, etc).

NOTE - There may be an easier way to do this, but this is the best I’ve been able to come up with.

First, we’ll look at the controller. You need 2 (yes, two) controller functions to make this work. First, we’ll make a function called add:

1
2
3
4
function add()
{
	//blank controller function
}

Basically, this function exists for one reason only - it calls it’s view, which calls our javascript file (myForm.js, in this case).

The view is add.ctp and looks like this:

1
2
<div id="user-form"></div>
<?php echo $javascript->link('myForm'); ?>

It creates a div to hold our form, and it calls our js file. Now for the second controller function. I named it (creatively enough) add2. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function add2() {
	Configure::write('debug', '0');     //turn debugging off; debugging breaks ajax
	$this->layout = 'Ajax';              //set the layout to Ajax so the ajax doesn't break
 
        //Cake does everything automagically through $this->data
        //Since we aren't using cake's automagic, we have to fake things a little
        //When a form is submitted, we are going to take the data submitted ($this->params['form']['username']
        //and assign it to $this->data['User']['username'] to make it fit with cake's automagic.
        //Otherwise, we can't use cake's save functions
 
        //Part of UPDATE2 - we don't need to do this anymore; it's taken care of in myForm.js
        //$this->data['User']['username'] = $this->params['form']['username'];
	//$this->data['User']['fullname'] = $this->params['form']['fullname'];
 
        //This is just like regular cake except for the set statements		
	if (!empty($this->data)) {
		$this->User->create();
		if ($this->User->save($this->data)) {
			$this->set('success', '{success:true}');  //this is a json statement.  EXT forms need this to know if things worked
		} else {
			$this->set('success', '{success:false}');  //EXT forms need this to know if something went wrong
		}
	}
}

OK, the comments should explain things there. The view is add2.ctp:

1
<?php echo $success; ?>

All this does is print the json return string we set in our controller. EXT forms need this to know if things worked or not. Since we are calling add2 through ajax, this page won’t be called directly, and the success statement won’t be printed to the screen. It will show up in firebug, though.

OK, now for the javascript (myForm.js - save it in /webroot/js).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 Ext.onReady(function(){
 
	Ext.QuickTips.init();
 
	//make a new form
	var form_user = new Ext.form.FormPanel({
		frame: true,
		title: 'Add User',
		bodyStyle: 'padding: 5px 5px 0',
		width: 350,
		labelWidth: 75,
		defaults: {width: 230},  	//this is the default field width
        defaultType: 'textfield', 	//this is the default field type.  You don't have to set this, but if not, you'll have to set it for each item
		items: [{
				fieldLabel: 'User Name',	//label text
				name: 'data[User][username]',			//field name for submit purposes
				allowBlank: false			//simple validation
			},
			{
				fieldLabel: 'Full Name',
				name: 'data[User][fullname]',
				allowBlank: false
			}],
		buttons: [{
				text: 'Save',		//text for submit button
				type: 'submit',		//type for button
				handler: function() {	//what happens when you click the button?
					form_user.form.submit({
						url: 'http://'+host+'/ext/users/add2',
						success: function() {
							Ext.MessageBox.alert('Message', 'It worked!', function(){window.location.href = 'http://'+host+'/ext/users/'});
						},
						failure: function() {
							Ext.MessageBox.alert('Message', 'Save failed!');
						}
					});				
				}
			},
			{
				text: 'Cancel',
				handler: function() {
					form_user.form.reset();
				}
		}]
	});
 
	form_user.render('user-form');
 })

OK, here’s what’s going on. Line 3 creates a new formPanel. The next several lines set different formPanel options. Line 14 starts defining our form fields. Line 24 defines our buttons. Line 27 defines the handler for our submit button. That’s how the form submits. NOTE - I’m using the host definition from my last tutorial. Success on line 30 is determined by the return value from add2 - success (true) or failure (false). If success is true, a message pops up to tell you ‘It worked!’, then it redirects to the datagrid from my last tutorial. If success is false, a message pops up to tell you the save failed. Cancel just resets the form.

There, I did it before going home for the weekend! Yeah! I hope this helps some people.

hydra12

23 Comments »

531

Comment by foxmask

January 12, 2008 @ 8:00 am

this works when i add $this->layout = ‘ajax’ in the add() function too.

563

Comment by Inma

January 18, 2008 @ 7:22 am

This is exactly that I was looking for.
You’re the best! ;-)

thanks a lot!
Inma.

587

Comment by aamir

January 24, 2008 @ 7:08 am

Greetings hydra12,

Please let me know how can I give my warm thanks to you?

Aamir.

590

Comment by inma

January 28, 2008 @ 3:12 am

Do you know how to add a combobox into the form from a ‘belongsTo’ association?

Thanks in advance.

591

Comment by inma

January 28, 2008 @ 6:22 am

Ok. I did it (to add a combobox into the form from a ‘belongsTo’ association):

MODEL (booklog.php)


array('className' => 'TransportCompany',
'foreignKey' => 'transport_company_id',
'conditions' => '',
'fields' => 'id, name',
'order' => '',
'counterCache' => '')
);
}
?>

CONTROLLER (booklogs_controller.php)


layout = 'ajax'; //set the layout to Ajax so the ajax doesn't break

$this->data['Booklog'] = $this->params['form'];

//This is just like regular cake except for the set statements
if (!empty($this->data)) {
$this->Booklog->create();
if ($this->Booklog->save($this->data)) {
$this->set('success', '{success:true}'); //this is a json statement. EXT forms need this to know if things worked
} else {
$this->set('success', '{success:false}'); //EXT forms need this to know if something went wrong
}
}
}

function getTransportCompanies()
{
Configure::write('debug', '0'); //set debug to 0 for this function because debugging info breaks the XMLHttpRequest
$this->layout = "ajax"; //this tells the controller to use the Ajax layout instead of the default layout (since we're using ajax . . .)

$transportCompaniesA = $this->Booklog->TransportCompany->findAll();
$transportCompaniesArray = Set::extract($transportCompaniesA, '{n}.TransportCompany'); //convert $transportCompaniesA into a json-friendly format
$this->set('transportCompanies', $transportCompaniesArray); //send transportCompanies to the view
}
}
?>

VIEW (add.ctp)

link('booklogs-add'); ?>

VIEW (add2.ctp)

VIEW (booklogs-add.js)


Ext.onReady(function(){

Ext.QuickTips.init();

var transportistaDS = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({url: 'http://'+host+'/CakePHP/booklogs/getTransportCompanies'}),
reader : new Ext.data.JsonReader(
{root : 'transportCompanies'},
[
{name:'id', mapping: 'id'},
{name:'name', mapping:'name'}
]
),
autoLoad: true
});

//make a new form
var form_booklog = new Ext.form.FormPanel({
title: 'Form',
bodyStyle: 'padding: 5px 5px 0',
width: 500,
labelWidth: 200,
defaults: {width: 230}, //this is the default field width
defaultType: 'textfield', //this is the default field type. You don't have to set this, but if not, you'll have to set it for each item
items: [
{
xtype : 'combo', //input type
fieldLabel: 'Transportista', //label text
store: transportistaDS,
mode: 'local',
displayField: 'name',
valueField: 'id',
emptyText: '-- Seleccione --',
id : 'transportista',
name: 'transport_company_id', //field name for submit purposes
triggerAction: 'all',
allowBlank: false //simple validation
}
],
buttons: [ ... ]
});

form_booklog.render('booklog-form');
})

592

Comment by inma

January 28, 2008 @ 6:27 am

I rewrite the code, because there were some lines that not have appeared:

MODEL (booklog.php)


class Booklog extends AppModel {
var $name = 'Booklog';

var $belongsTo = array(
'TransportCompany' => array('className' => 'TransportCompany',
'foreignKey' => 'transport_company_id',
'conditions' => '',
'fields' => 'id, name',
'order' => '',
'counterCache' => '')
);
}

CONTROLLER (booklogs_controller.php)


class BooklogsController extends AppController {

var $name = 'Booklogs';
var $helpers = array('Html', 'Form', 'Javascript' );

function add()
{
//blank index method. We'll load data with a json call from booklogs-add.js
}

function add2()
{
Configure::write('debug', '0'); //turn debugging off; debugging breaks ajax
$this->layout = 'ajax'; //set the layout to Ajax so the ajax doesn't break

$this->data['Booklog'] = $this->params['form'];

//This is just like regular cake except for the set statements
if (!empty($this->data)) {
$this->Booklog->create();
if ($this->Booklog->save($this->data)) {
$this->set('success', '{success:true}'); //this is a json statement. EXT forms need this to know if things worked
} else {
$this->set('success', '{success:false}'); //EXT forms need this to know if something went wrong
}
}
}

function getTransportCompanies()
{
Configure::write('debug', '0'); //set debug to 0 for this function because debugging info breaks the XMLHttpRequest
$this->layout = "ajax"; //this tells the controller to use the Ajax layout instead of the default layout (since we're using ajax . . .)

$transportCompaniesA = $this->Booklog->TransportCompany->findAll();
$transportCompaniesArray = Set::extract($transportCompaniesA, '{n}.TransportCompany'); //convert $transportCompaniesA into a json-friendly format
$this->set('transportCompanies', $transportCompaniesArray); //send transportCompanies to the view
}
}

VIEW (add.ctp)

link('booklogs-add'); ?>

VIEW (add2.ctp)

VIEW (booklogs-add.js)


Ext.onReady(function(){

Ext.QuickTips.init();

var transportistaDS = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({url: 'http://'+host+'/CakePHP/booklogs/getTransportCompanies'}),
reader : new Ext.data.JsonReader(
{root : 'transportCompanies'},
[
{name:'id', mapping: 'id'},
{name:'name', mapping:'name'}
]
),
autoLoad: true
});

//make a new form
var form_booklog = new Ext.form.FormPanel({
title: 'Form',
bodyStyle: 'padding: 5px 5px 0',
width: 500,
labelWidth: 200,
defaults: {width: 230}, //this is the default field width
defaultType: 'textfield', //this is the default field type. You don't have to set this, but if not, you'll have to set it for each item
items: [
{
xtype : 'combo', //input type
fieldLabel: 'Transportista', //label text
store: transportistaDS,
mode: 'local',
displayField: 'name',
valueField: 'id',
emptyText: '-- Seleccione --',
id : 'transportista',
name: 'transport_company_id', //field name for submit purposes
triggerAction: 'all',
allowBlank: false //simple validation
}
],
buttons: [ ... ]
});

form_booklog.render('booklog-form');
})

593

Comment by inma

January 28, 2008 @ 6:36 am

Well, on my last post the code from add.ctp and add2.ctp does not appear correctly.

I can’t post it… I apologize for that.

598

Comment by Duncan Margetts

February 19, 2008 @ 7:36 pm

Hi,

I’ve followed this code very carefully.. and it mostly works.. Except I’m finding that submitting myForm.JS always results in the “Save Failed!” message popping up even though Add2() in the controller is successfully saving the new record to the database. As a simple test I even changed the Else condition on the save (ie. a save FAIL) to set “{success:true}”..

Does anyone have any thoughts on what might be causing this?

Cheers, Duncan

603

Comment by admin

February 25, 2008 @ 2:58 pm

I just posted an update (see update 2 note at the top of the page) that makes this tutorial more ‘cakey’. It also makes the controller work like a standard controller (no more manually assigning data to the $data variable).

604

Comment by admin

February 25, 2008 @ 2:59 pm

@Duncan - can you post your code someplace so I can look at it? Google Docs seems to be a good and popular place these days . . .

605

Comment by Stephen Orr

February 26, 2008 @ 7:15 am

I reckon a quick rewrite to the add() function will help drastically here. I’ve used a similar technique to this on a CakePHP site where I’m not *yet* using ExtJS.

add:

var $components = array('RequestHandler');

…to your controller.

…and the new function:

function add() {
if($this->RequestHandler->isAjax()) {
$this->layout = 'ajax';
}
... do rest of the code ...
}

607

Pingback by CakePHP: aggiunta di editor WYSIWYG di extjs « Cronaca di un’avventura formativa

March 4, 2008 @ 6:05 pm

[…] CakePHP: aggiunta di editor WYSIWYG di extjs Oggi ho cercato di modificare il blog creato nei giorni scorsi con CakePHP sostituendo l’editor TinyMCE con quello incorporato in Ext JS; per fare ciò ho preso spunto da questo tutorial. […]

610

Pingback by Cronaca di un’avventura formativa » Blog Archive » CakePHP: aggiunta di editor WYSIWYG di Ext JS

March 17, 2008 @ 7:45 am

[…] Oggi ho cercato di modificare il blog creato nei giorni scorsi con CakePHP sostituendo l’editor TinyMCE con quello incorporato in Ext JS; per fare ciò ho preso spunto da questo tutorial. […]

613

Comment by Boris Barroso

March 27, 2008 @ 6:59 am

How can I make this work with the Security component?

618

Comment by newbie

April 25, 2008 @ 9:34 pm

how about code for editing page?

thanks for help.

619

Comment by newbie

April 25, 2008 @ 9:37 pm

how about cede for editing page?

thanks for help

620

Pingback by Cronaca di un’avventura formativa » CakePHP e i layout (1)

May 2, 2008 @ 10:26 am

[…] a rivedere passo passo il codice sviluppato (seguendo questo tutorial già citato precedentemente) mi sono soffermato su queste due righe: // functions edit2 and add2 in […]

621

Comment by [.::MDT::.]

May 2, 2008 @ 10:38 am

Hi, please correct “Ajax” with “ajax”, as I’ve written in my italian blog, you probably are using Windows which is NOT case-sensitive so it works.
In linux your code does NOT work, because linux is case-sensitive and does NOT found the layout “Ajax” (instead it obviously finds the “ajax” layout”)!

Thank you, bye!

639

Pingback by hydra12’s blog " CakePHP and EXTJS Forms

May 27, 2009 @ 12:52 am

[…] the rest here: hydra12’s blog " CakePHP and EXTJS Forms Share and […]

645

Comment by Frederick D.

October 15, 2009 @ 4:07 pm

Does anybody still monitor this site? Anyone open for some CakePHP + ExtJS integration quesitons?

650

Comment by Hannah19

February 18, 2010 @ 8:11 am

Lots of persons get know a technique of definition essay creating, nevertheless that doesn’t mean they will compose superior quality research papers, however a sociology essay writing service will aid to write the argumentative essay of A+ quality and improve writing skills of some students.

651

Comment by David Rocke

March 16, 2010 @ 9:05 am

ExtJS FormPanels can be used with the security component with a little extra coding.
1) Add a DIV to the page with the FormPanel.
2) Set the DIV’s style to “display:none”.
3) Use the Cake $form helper to create a static form within the hidden DIV. Remember to

include an ID in the call to the create method.
i.e.

create('AnObject', array('id'=>'frmAnObjectReference'));
echo $form->input('name');
echo $form->input('description');
echo $form->end('');
?>

4) In your Javascript create functions that return the values of the hidden input fields

named “data[_Token][key]” and “data[_Token][fields]”.
i.e.

function findFormTokenKey(formSelector){
if(formSelector==''){ return ''; }
var inputs = Ext.select(formSelector + ' input');
for(var i=0;i

function findFormTokenFields(formSelector){
if(formSelector==''){ return ''; }
// formSelector could be '#frmAnObjectReference'
var inputs = Ext.select(formSelector + ' input');
for(var i=0;i
NOTE: Ext.select() does not appear to handle '[' and ']' in attribute names, that's why I

have not used them in the Ext.select statement above. jQuery does work OK with this but I

have avoided using jQuery to save on overheads.
5) Use these functions to provide the values for two hidden FormPanel fields.
i.e.
new Ext.form.FormPanel({
title: ...
...
items: [{
xtype: ...
}, {
xtype: 'hidden',
name: 'data[_Token][key]',
value: findFormTokenKey('frmAnObjectReference')
}, {
xtype: 'hidden',
name: 'data[_Token][fields]',
value: findFormTokenFields('frmAnObjectReference')
}]
});

6) Try this out using Firefox with Firebug. Click the "Inspect" button and look for the

dynamic form added by Ext. It should have the same two hidden fields as the static form

earlier.
7) When submitted, the form should not result in a BlackHoleCallback as it would have with

the Security Component included and no extra hidden fields.
8) I believe that disabling fields may also result in a BlackHoleCallback. This could be

overcome with some extra static forms and more Javascript code. The extra forms would omit

the disabled fields so as to force Cake to generate a different data[_Token][fields] value.

Maybe avoid disabling fields!
9) The data[_Token][key] value is written serialized to the Session and can be retrieved

using the $session helper
i.e.

read('_Token'));
echo $token['key'];
?>

10) The $form helper has a way of creating the data[_Token][fields] value but only as a

complete fieldset and hidden inputs not as a stand-alone value.
This would be a nice enhancement to CakePHP
i.e. something like

_getFieldsToken(array('name', 'description'));
?>

NOTE: All the code has been written from memory and may not be 100% correct. It is the

principle that’s important.

652

Comment by David Rocke

March 16, 2010 @ 9:17 am

THE CODE FOR THE PREVIOUS ARTICLE. IT GOT MANGLED!
create(’AnObject’, array(’id’=>’frmAnObjectReference’));
echo $form->input(’name’);
echo $form->input(’description’);
echo $form->end(”);
?>

// Javascript functions
function findFormTokenKey(formSelector){
if(formSelector==”){ return ”; }
var inputs = Ext.select(formSelector + ‘ input’);
for(var i=0;iread(’_Token’));
echo $token[’key’];
?>

_getFieldsToken(array(’name’, ‘description’));
?>

RSS feed for comments on this post. TrackBack URI

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>