debuggable

 
Contact Us
 

Using generateFields() + Fix fields of the type 'date'

Posted on 12/1/06 by Felix Geisendörfer

Deprecated post

The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.

Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.

Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).

If you didn't use the generateFields() function before you've missed out on some good Cake. But there is a little thing you definitly should know when using it. One thing I've discovered is that you can easily create interfaces in scaffolding-speed with it (scaffolding actually makes use of this function as well) but with the advantage of having a lot more control over the way the data is displayed, proccessed and saved.

But here comes the problem: If you use fields of the type date in your database generateFields will generate 3 Selects called date_month, date_day and date_year for them, but once the data get's back in and you want to save it you are responsible for merging it to one 'date' field.

But don't worry there already is a function that does that for you, but it's only inside the Scaffolding Component which means you have to copy and modify it a little bit to make it work inside of your Controller.

Let's look at a complete example on how to do that:

1. The 'add' function of your controller:

Inside of your add() function you have the following code. (If you wonder why there is a @$this->params['data'] inside of the generateFields() call: It's because this gives you the possibilty of providing links to adding items with information already filled in)

function add()
{        
    $fieldNames = $this->generateFieldNames(@$this->params['data']);
    $this->set('fieldNames', $fieldNames);
    return $this->render('edit');
}

2. The view 'edit.thtml'

You'll have a view called edit.thtml which will be used for both, creating new data and editing existing data:

if($type == 'Edit')
{
  echo $html->formTag('/'. Inflector::underscore($this->name) .'/update');
}
else
{
  echo $html->formTag('/'. Inflector::underscore($this->name).'/create');
}
?>
<?php echo $form->generateFields( $fieldNames ); ?>
<?php echo $form->generateSubmitDiv( 'Save' ); ?>
</form>

The Result will be a scaffolding like 'magical' user interface, that will (depending on your table fields) look like this:
generateFields User Interface

3. Save the Posted Data

The next thing is saving the data if a user clicks on the Save button which will lead him to /create of your controller: The easiest way is by doing it like this.

function create()
{
    if(empty($this->params['data']))
    {            
        return $this->add();
    }    
   
    $this->_cleanUpFields();
   
    if ($this->Item->save($this->params['data']))
    {
        $this->set('message', 'Your Item has been created. The Id is: ' . $this->Item->getLastInsertID());
    }
    else
    {
        $this->set('error', 'Something went wrong and your item could not be created');
    }
   
    unset($this->params['data']);
    return $this->add();
}

4. Merge datefields with _cleanUpFields()

Important: As it turns out this might not be the most "best practice" solution to working with date fields since this stuff should be better inside of the model. If you care to do things the right way definitly have a look at CakerBaker's Blog post that deals with that topic and forget about the this step here ; ).

The function _cleanUpFields() is not part of your controller by default but only available in the scaffolding class. But since it comes really handy for merging the day/month/year select boxes of date fields into one variable I've modified it to work from within a regular controller. All you have to do is to Add this to the controller you want to use generateFields() with, or another controller that your controller extends:

function _cleanUpFields()
{                            
    $modelKey = Inflector::humanize($this->modelKey);
   
    foreach( $this->{$modelKey}->_tableInfo as $table )
    {
        foreach ($table as $field)
        {
            if('date' == $field['type'] && isset($this->params['data'][$modelKey][$field['name'].'_year']))
            {
                $newDate  = $this->params['data'][$modelKey][$field['name'].'_year'].'-';
                $newDate .= $this->params['data'][$modelKey][$field['name'].'_month'].'-';
                $newDate .= $this->params['data'][$modelKey][$field['name'].'_day'].' ';
                $this->params['data'][$modelKey][$field['name']] = $newDate;
            }
            else if( 'datetime' == $field['type'] && isset($this->params['data'][$modelKey][$field['name'].'_year'] ) )
            {
                $hour = $this->params['data'][$modelKey][$field['name'].'_hour'];
                if( $hour != 12 && 'pm' == $this->params['data'][$modelKey][$field['name'].'_meridian'] )
                {
                    $hour = $hour + 12;
                }
                $newDate  = $this->params['data'][$modelKey][$field['name'].'_year'].'-';
                $newDate .= $this->params['data'][$modelKey][$field['name'].'_month'].'-';
                $newDate .= $this->params['data'][$modelKey][$field['name'].'_day'].' ';
                $newDate .= $hour.':'.$this->params['data'][$modelKey][$field['name'].'_min'].':00';
                $this->params['data'][$modelKey][$field['name']] = $newDate;
            }
            else if( 'tinyint(1)' == $field['type'] )
            {
                if( isset( $this->params['data'][$modelKey][$field['name']]) &&
                            "on" == $this->params['data'][$modelKey][$field['name']] )
                {
                    $this->params['data'][$modelKey][$field['name']] = true;
                }
                else
                {
                    $this->params['data'][$modelKey][$field['name']] = false;
                }
            }
        }
    }
}

I just started working with generateFields() so there might be some things to improve here and there, but all in all this is the stuff that will really speed up development and using it can be a ton of fun ; ).

A little tip at the end: If you have fields in your database that you want to provide a different interface to then the one generated by generateFields() just do something like this:

$fieldNames = $this->generateFieldNames();
unset($fieldNames['tag']);

This way you can use your own Html for certain fields in the database!

--Felix

 
&nsbp;

You can skip to the end and add a comment.

gwoo  said on Jan 20, 2006:

Nice work Felix. How about putting the _cleanUpFields() in a custom app_controller.php. Then its available to all the controllers in your app. I like the unset trick. Happy Baking.

Felix Geisendörfer said on Jan 20, 2006:

Hey gwoo, I'm glad you liked the unset() idea, it really helped me saving times already. About putting _cleanUpFields() in a costum app_controller.php -> I kind of indicated this by saying:
"All you have to do is to Add this to the controller you want to use generateFields() with, or another controller that your controller extends"

Anyway -> I think the _cleanUpFields() should belong in the controller.php inside of cake and not the in the scaffolding class since it's something that is needed to work with generateFieldNames(). There should also be a parameter for generateFieldNames() that allows you to set an array of fields you want to generate the field names for.

But besides that I really think this functionality of Cake is one of the biggest "WoW"-Effect's I had so far since I normally would have needed a lot longer for my interfaces without this!

runlevel  said on Feb 06, 2006:

Hi

Like the little article and am trying it out. I am using RC4 of cake.
But trying it ran me into some problems. I am a cake newbie so it is prob. something stupid.

these are the notices I get when generating the edit.thtml
Undefined variable: type

Undefined variable: form

Think I am missing some helpers here? Tried it with and without scaffold.
Do you have any idea?

Thanks Cheers

Felix Geisendörfer said on Feb 06, 2006:

You need the Helper Form for this. So just do var $helpers = Array('Html', 'Form'); on top of your controller.
I think the error about type is because I've not pasted the entire code. Try to put this in your controller function add():

$this->set('type', 'Add');

and this one in your edit() function:
$this->set('type', 'Edit');

I hope this helps.

[...] As he did once before, tobius inspired me to write some code again. This time he was trying to make scaffolding more individual, so the first thing I pointed out to him was my blog post about using generateFields for using some of the scaffolding powers. Now one problem remained: He wanted to use the same views over and over again so I had an idea about how to reuse views in a simple and efficient manner. [...]

[...] As he did once before, tobius inspired me to write some code again. This time he was trying to make scaffolding more individual, so the first thing I pointed out to him was my blog post about using generateFields for using some of the scaffolding powers. Now one problem remained: He wanted to use the same views over and over again so I had an idea about how to reuse views in a simple and efficient manner. [...]

chief said on Apr 29, 2006:

Hi, just starting to learn CakePHP and I came across this site in search of how to use Date fields. Anyhow, thanks a lot for the code, but I ran into a problem when trying to populate the fields on Edit() view. You have to add this code into your controller Edit() before you can generateFields($this->params['data'])...

$this->params['data'] = $this->ModelName->read();

Then again, I'm a newbie, so maybe I am missing something? Let me know if I am incorrect.

Felix Geisendörfer said on Apr 29, 2006:

Hey chief,

I've published this code in the beginning of the year and a lot of things have changed since then. But I think in this case it's just the fact that I've not pasted the entire code, but rather a general outline on how to do things. So if your code line fixes the problem and fits your needs, go with it ; ).

Felix Geisendörfer said on Feb 02, 2007:

I just closed comments on this post because it seems to be attracting spammers more then any other posts and it's old enough so I don't expect any further discussion on it ; ).

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.