Admin form with "extra" fields - php

I have a form for an object called AccountImport. This form lives in an admin-generated module. In addition to the fields that map directly to this object's attributes, I need a couple extra fields.
If I just add the fields to the AccountImport form, it won't save correctly because the form will no longer match the AccountImport object.
If I create a template manually and splice the extra fields in that way, I'm throwing away all the stuff the admin generator gives me for free (i.e. formatting, "Back to list" button, save buttons).
What's a "good" way to do what I'm trying to do?

If you define additional fields in generator.yml, you can override one of the admin generator actions to handle the fields however you want.
Look at the generated actions.class.php in cache/YOURAPP/YOURENV/modules/autoYOURMODULE/actions/actions.class.php . You can override any of those functions with your own in apps/YOURAPP/modules/YOURMODULE/actions/actions.class.php, because it inherits from that cached file. When you make changes to generator.conf, the cached file is updated but your code will still override it. You probably want to override processForm().
I have an example of this in step 5 at this blog post:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid())
{
$notice = $form->getObject()->isNew() ? 'The item was created successfully.' : 'The item was updated successfully.';
// NEW: deal with tags
if ($form->getValue('remove_tags')) {
foreach (preg_split('/\s*,\s*/', $form->getValue('remove_tags')) as $tag) {
$form->getObject()->removeTag($tag);
}
}
if ($form->getValue('new_tags')) {
foreach (preg_split('/\s*,\s*/', $form->getValue('new_tags')) as $tag) {
// sorry, it would be better to not hard-code this string
if ($tag == 'Add tags with commas') continue;
$form->getObject()->addTag($tag);
}
}
try {
$complaint = $form->save();
// and the remainder is just pasted from the generated actions file
When I realized I could read the generated files in the cache to see exactly what the admin generator was doing, and that I could override any part of them, it made me a lot more productive with the admin generator.

I asume you have added the extra fields as widgets to your form object, but have you also added their validators?
No matter which form fields you include in the form object, as long as the generator.yml file doesn't override the settings of the form (ie you don't set any value for the [new|form|edit].display key in that file) the object should get successfully saved on valid input.

Related

How do I render (get HTML of) a form object in Agile Toolkit?

I'm using PHP framework Agile Toolkit version 4.3.2 (latest at this moment). I'm on a page that extends the default Page class.
Suppose I have a form object like:
$form = $this->add('Form');
$form->addField('text', 'name', 'Name');
$form->addSubmit('Save');
How do I get the form object's HTML? I want to send the form's HTML to another template part, something like:
$this->template->trySetHTML('Content', $form);
The function from above works if I use HTML code instead of the $form object.
But in this case when I refresh the page, instead of the form HTML appears a string like: Object Form(22f8a7bc__ancedsearch_form)
I tried: $form->render() or $form->getHTML() but these functions don't work.
So please tell me, how do I render an object in agile toolkit? How can I get the object's HTML code.
Edit
I'm extending the grid layout. For each column I add a search filtering option. I have extended the Grid_Advanced.php in order to be able to customize it. On each column, below the table header (column name), I'm inserting a form with an input (I'm sending the column name field):
$header_col->trySetHTML('advance_search_filter', $form_html);
The $form_html is returned from a file that extends atk4/lib/Filter.php (it's similar to quicksearch). The quicksearch automatically adds the html to the grid, but in my case I need it to be added to the table's head, after the column name. That's why I am trying to get the HTML of the form.
In this file I have the init function that looks something like:
public function init()
{
parent::init();
$this->addClass('grid-extended-search atk-box ui-widget ui-widget-content');
$this->default_controller = 'Controller_..._MVCForm';
$this->template->trySet('fieldset', 'atk-row');
$this->bs = $this->addSubmit('Search');
$this->save = $this->bs;
}
But this doesn't return nothing so I created a function to return template's HTML. But the form was empty, so I recreated the fields (which is bad):
$m = $this->view->model
foreach($this->fields as $f) {
$field = $this->view->columns[$f];
if($m->hasField($f)) {
if($field['type'] == 'text') {
$field_html = $this->addField('line', $f, $field['descr']);
$form_html .= $field_html->getInput();
}
$this->template->setHTML('Content', $form_html);
}
}
Any idea? Why the form is empty? I used addField to add the fields to the existing but the fields probably exist. I don't know how to get them, to get the existing form's HTML.
For most views:
$html = $view->getHTML();
However it might be more tricky for a Form.
In the solution that you have described (after edit), it seems that you don't really need all the functionality of the Form
To get HTML of an individual Field:
$field->getInput();
That will give you the "input" element that you can place inside your column headers. You can also use "Form_Plain" to wrap your GRID inside a <form> tag.
You would need to handle submission manually, though.

Doctrine (Mongo) Persisting Partial Document

I have a custom class that populates a controller's action parameters based on the typehint of the parameter. This works well for documents (using public properties and setters).
My aim is to make the controller simple:
function updateAction(Article $article)
{
$dm = new DocumentManager(); // code elsewhere
$dm->merge($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
The problem is that the input supplying the fields to programatically populate the Article class doesn't contain all of the properties of an Article class (perhaps the edit form only contains Title and Content, but disregards Author, etc).
I was hoping that the presence of an ID would allow the document to be merged gracefully with what is currently in the database. However, any fields that are missing at the time of a merge will be removed from the document in the database.
Is there a way to update a document in such a way that only the fields that are present (non-null, I guess) are updated?
Rather than hitting the db twice - once for the find, and once for the update, you can use a FIND_AND_UPDATE query.and do it all in one step.
See this docs page for details: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/find-and-update.html
It seems that a clean way would be to bind the model AFTER retrieving it from the database. Something along the lines of ASP.NET MVC's UpdateModel.
function updateAction($id)
{
$dm = new DocumentManager(); // code elsewhere
$article = $dm->getRepository('Article')->find($id);
$this->updateModel($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
If there are any better suggestions, feel free to answer...

Processing Zend_Form dynamically generated elements

I need to create a form where the elements (texbox, select, ..) will be dynamically inserted. Right now I have created a empty Form file with just a hidden element and them in my controller I go inserting elements according to certain conditions.
My form file:
class Form_Questions extends Zend_Form {
public function __construct() {
parent::__construct($options);
$this->setName('Questions');
// Hidden Label for error output
$hiddenlabel = new Zend_Form_Element_Hidden('hiddenlabel');
$hiddenlabel->addDecorator(new Form_Decorator_HiddenLabel());
$this->addElements( array($hiddenlabel) );
}
}
In the controller I have something like:
...
$form = new Form_Questions();
$request = $this->getRequest();
if ($request->isPost())
{
$formData = $request->getPost();
if ($form->isValid($request->getPost()))
{
die(var_dump($form->getValues()));
}
}
else
{
//... add textbox, checkbox, ...
// add final submit button
$btn_submit = new Zend_Form_Element_Submit('submit');
$btn_submit->setAttrib('id', 'submitbutton');
$form->addElement($btn_submit);
$this->view->form = $form;
}
The form displays fine but the validation is giving me big trouble. My var_dump() only shows the hidden element that is staticly defined in the Form file. It does not save the dinamic elements so altought I can get them reading what's coming via POST, I can not do something like
$form->getValue('question1');
It behaves like if Zend uses the Form file to store the values when the submit happend, but since the elements are created dinamically they do not persist (either their values) after the post so I can not process them using the standar getValue() way.
I would appreciate any ideas on how to make them "live" til after the post so I can read them as in a normal form.
The form which you are calling isValid() and getValues() methods on is actually your "empty" form - you have instantiated it only a few lines up and haven't added any elements to it at that point.
Remember that POST only sends an array of fieldName => fieldValue type, it doesn't actually send a Zend_Form object.
It is difficult to suggest a new solution without knowing what you are trying to achieve. It is generally better to add all possible elements to your Zend_Form right away, and then only use the ones you need in the view scripts, i.e. echo $this->form->myField;. This will allow isValid() to process all the elements of the form.
It sounds like the form is dynamic in the sense that the questions come from a db, not in then sense that the user modifies the form itself to add new questions.
Assuming this is the case, then I wouldn't add the question fields in the controller. Rather, I'd pass the questions to the form in the constructor and then add the question fields and the validators in the form's init() method. Then in the controller, just standard isPost() and isValid() processing after that.
Or, if you are saying that the questions to be added to the form are somehow a consequence of the hidden label posted, then perhaps you need two forms and two actions: one for the hidden field form and another for the questions.
Ok, the simplest solution I came up with - to my case and considering the really of the code I am currently playing with was to load all the questions I need from the database using a method from my Model (something like fetchQuestions()), them in my controller I go throught the recordset and create the form elements according to the current question of the recordset.
The elements are stacked in an array that is passed to my Form constructor. In the form constructor I read the array and generate all the dynamic elements. I them just echoed the form to the view.
I have not seem why it would be a bad idea to override the Form constructor as I also could not use any of the set/get methods to pass this to my form.

update/validate embedded forms before saving in symfony

I would like to validate an embedded form field before it gets saved in the database. Currently it will save an empty value into the database if the form field is empty. I'm allowing empty fields, but I want nothing inserted if the form field is empty.
Also quick question, how to alter field values before validating/saving an embedded form field?
$this->form->getObject works in the action, but $this->embeddedForm->getObject says object not found
I found a really easy solution. The solution is to override your Form's model class save() method manually.
class SomeUserClass extends myUser {
public function save(Doctrine_Connection $conn = null)
{
$this->setFirstName(trim($this->getFirstName()));
if($this->getFirstName())
{
return parent::save();
}else
{
return null;
}
}
}
In this example, I'm checking if the firstname field is blank. If its not, then save the form. If its empty, then we don't call save on the form.
I haven't been able to get the validators to work properly, and this is an equally clean solution.
Symfony gives you a bunch of hooks into the form/object saving process.
You can overwrite the blank values with null pre/post validation using by overriding the the doSave() or processValues() functions of the form.
You can read more about it here: http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms#chapter_06_saving_object_forms

How do I use a custom #theme function to a fieldset in a drupal module?

I have a module that builds a form that includes a fieldset. Instead of using the <legend> element to render the fieldset title, I want to place this content in a <div> element instead. But I want to change the behavior only for the form returned by my module, so I don't want to place any new functionality into my theme's template.php file.
In mymod.module I have defined:
// custom rendering function for fieldset elements
function theme_mymod_fieldset($element) {
return 'test';
}
// implement hook_theme
function mymod_theme() {
return array(
'mymod_fieldset' => array('arguments' => array('element' => NULL)),
'mymod_form' => array('arguments' => array())
);
}
// return a form that is based on the 'Basic Account Info' category of the user profile
function mymod_form() {
// load the user's profile
global $user;
$account = user_load($user->uid);
// load the profile form, and then edit it
$form_state = array();
$form = drupal_retrieve_form('user_profile_form', $form_state, $account, 'Basic Account Info');
// set the custom #theme function for this fieldset
$form['Basic Account Info']['#theme'] = 'mymod_fieldset';
// more form manipulations
// ...
return $form;
}
When my page gets rendered, I expected to see the fieldset representing 'Basic Account Info' to be wholly replaced by my test message 'test'. Instead what happens is that the <fieldset> and <legend> elements are rendered as normal, but with the body of the fieldset replaced by the test message instead, like this:
<fieldset>
<legend>Basic Account Info</legend>
test
</fieldset>
Why doesn't my #theme function have the chance to replace the entire <fieldset> element? If I wrap a textfield in this function instead, I am able to completely replace the <input> element along with its label. Furthermore, if I provide an override in my site's template.php for theme_fieldset, it works as expected and I am able to completely replace the <fieldset>, so I know it is possible.
What's different about providing #theme functions to fieldsets inside a module?
Have you tried overriding theme_fieldset() instead of using the #theme function? I believe you could do something like this in your .module file:
function mymodule_fieldset($element) {
// do something;
return $html;
}
This would apply to all fieldsets. You could do some kind of check on $element for the fieldsets you want to affect and then use the default implementation for all others.
Take a look at: http://api.drupal.org/api/function/theme_fieldset/6
I know this is an old post -- but I've run into the same issue. I came up with an ugly work around. This is definitely a bug in the Form API. Maybe my temporary fix will be helpful to someone.
I found (and appended) a bug report here: http://drupal.org/node/225698
Worth checking that before trying my hacky fix.
I'm not sure what the children are in $form['Basic Account Info'] in this example, but basically what you can do is use drupal_render() on that fieldset's children, and then recreate a fieldset array separate from $form['Basic Account Info'], theme it with theme() and pass it back to the form array as markup..
$fieldsetElement = array(
//$child is the key of whatever child you need in the fieldset
//you may have to alter this for multiple children, stringing
//together multiple drupal_render calls on each children
//(#children needs to be a string.. unless your theme can handle the array)
'#children'=>drupal_render($form['Basic Account Info'][$child]),
'#attributes'=>array(),//set real attributes
'#title'=>$form['Basic Account Info']['#title']
);
$form['Basic Account Info'] = array(
'#type'=>'markup',//not really needed, is default
'#value'=>theme('mymod_fieldset',$fieldsetElement)
);
super-duper hacking, likely causes disconnect with form state and potential validation failure -- but both are fixable by trial and error with the form api. I wouldn't recommend this unless you really want to get your hands dirty with PHP and drupal form API, but that's really the only way, unless you can live without variable fieldset themes in your module... Maybe try prefix and suffix?
This is just off the top of my head but maybe the difference is because a fieldset is not a form element but just a seperator or a grouper, if you will. Maybe the #theme callback is only for form elements?
The concept of your code works, meaning you can do what you want to do.
There are some things that can explain why it doesn't work.
The fieldset is not $form['Basic Account Info'].
Need to clear cache.
$form['Basic Account Info']['#theme'] is lost/overridden later in the code execution.
Try to take a look at $form before you do any of the moderations. When I tried to copy your code I run into a bug:
user.pages.inc file needed to be loaded
I was having the same issue.
You need to use #theme_wrappers instead of #theme
'#type' => 'fieldset',
'#theme_wrappers' => array('mymodule_fieldset'),

Categories