I have a simple form with a select menu on the node display page. Is there an easy way to validate the form in my callback function? By validation I don't mean anything advanced, just to check that the values actually existed in the form array. For example, without ajax, if my select menu has 3 items and I add a 4th item and try to submit the form, drupal will give an error saying something similar to "an illegal choice was made, please contact the admin."
With ajax this 4th item you created would get saved into the database. So do I have to write validation like
if ($select_item > 0 && $select_item <= 3) {
//insert into db
}
Or is there an easier way that will check that the item actually existed in the form array? I'm hoping there is since without ajax, drupal will not submit the form if it was manipulated. Thanks.
EDIT:
So I basically need this in my callback function?
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
$form_state['post'] = $form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;
drupal_process_form($form_id, $form, $form_state);
To get $_POST['form_build_id'], I sent it as a data param, is that right? Where I use form_get_cache, looks like there is no data. Kind of lost now.
Since you're already using AJAX, why not just write a bit of jQuery to only allow form submission if the choice is within the list of legal choices? This can be done within the custom module it already looks like you're working on (using drupal_add_js()).
It is not especially 'easy', but the standard way to do it would be to use Drupals Forms API for the callback submission as well - that way, you'll get the same validation that would happen on a non js submit.
Take a look at Adding dynamic form elements using AHAH. While it does not match your scenario exactly (they rebuild the form on the callback to add new elements, not to save data), the explanation of the processing workflow is pretty helpful.
Then there are several modules that try to offer AJAX form submission in a generic way - you could check their code on how to do it (or maybe just use them ;)
Ajax submit (only has a dev version)
Ajax (has an 'official' release)
Finally, there are efforts to put better support this functionality into core in Drupal 7 - the related discussions might also help.
Related
Going to try my best to explain this...
In my app I have the need to display the validation errors on a form when the user loads the form initially. In short, they've entered data and saved it, but now the data has been checked and we've detect errors they need to correct before they can fully submit the form. (It's multi step form that can be filled out over multiple sessions...think big.)
Previously I was doing something like:
THIS DOESN'T WORK IN SYMFONY >=2.8.10 See this answer
$entity // passed in as param on action method
$form = $this->createForm(..., $entity);
$csrfToken = 'random_string'; // retrieved from FormConfigInterface
// perform the submit but don't clear missing
$form->submit(['_token' => $csrfToken], false);
This was working, but seems to have broken in Symfony 2.8.10+, but works in 2.8.9. In 2.8.10+ there are no validation errors (the form is considered valid).
I'm able to retrieve the validation errors in a ConstraintViolationListInterface, but I can't find a way to "merge" that with the form (which I think would be the prefered way). Since I couldn't, I tried the above which "fake" submits the form...which seems like a bad idea.
Is there a better/proper way?
(Note: the form is much more complicated and includes validation groups...but I'm not concerned about that or the error in 2.8.10+ at this point.)
On my website, I have user accounts that are configurable with forms that allow users to update everything from first and last names to privacy settings. I use the following function to update the database with that input. (Note that the following code uses WordPress-specific features.)
function update_account() {
global $current_user; get_currentuserinfo();
require_once( ABSPATH . WPINC . '/registration.php' );
$uid = $current_user->ID;
// First Name
if(isset($_POST['first_name']) && $_POST['first_name'] <> $current_user->first_name) {
wp_update_user( array(
'ID' => $uid, 'first_name' => esc_attr($_POST['first_name'])
));
}
// ...and so on 43 more times...
}
This feels like the wrong way to process forms. This also looks like it will negatively impact server performance when there are multiple users and frequent updates, given that the if-then-else conditions for every field, even fields not on a particular page, force checking each field for input.
Moreover, since form data can be expected to remain relatively constant, I added the <> operator to prevent the function from updating fields where there has not been any change, but I suspect this also means that every field is still evaluated for change. To make matters worse, adding new fields -- there are already 44 fields in total -- is an unwieldy process.
What's a better way to process form data?
Keep an array of the fields you will be processing with this code, and iterate over it. This works if all your attributes are strings, for example. If you have different data types such as boolean flags to handle differently from the strings, you may wish to group them into their own array.
// All the fields you wish to process are in this array
$fields = array('first_name', 'last_name', 'others',...'others99');
// Loop over the array and process each field with the same block
foreach ($fields as $field) {
if(isset($_POST[$field]) && $_POST[$field] != $current_user->{$field}) {
wp_update_user( array(
'ID' => $uid, $field => esc_attr($_POST[$field])
));
}
}
There's a lot of things missing with your implementation. I don't know what kinds of data you're allowing the user to manipulate but most usually have some kind of requirements to be acceptable. Like not having certain characters, not being blank, etc. I don't see any validation occurring, so how do you handle values that might be undesirable? And what happens when you receive bad data? How do you inform the user of the bad data and prompt them to correct it?
If we abstract the situation a bit we can come up with generalizations and implement an appropriate solution.
Basically form fields [can] have a default value, a user specified value [on form review], validation requirements and validation errors [with messages]. A form is a collection of fields that upon form submit needs to be validated and if invalid, re-displayed to the user with instructive corrective prompts.
If we create a form class that encapsulates the above logic we can instantiate and use it to pass around our controller/views. Oops, I was just assuming you were using an Model/View/Controller type framework, and I'm not really familiar with wordPress so I don't know if that is exactly applicable. But the principle still applies. On the page where you both display or process the form, here's some pseudo logic how how it might look.
function update_account()
{
// initialize a new form class
$form = new UserAccountInfoForm();
// give the form to your view for rendering
$this->view->form = $form;
// check if form was posted [however your framework provides this check]
if(!Is_Post())
return $this->render('accountform.phtml');
// check if posted form data validates
if(!$form->isValid($_POST))
{
// if the form didn't validate re-display the form
// the view takes care of displaying errors, with the help of its
// copy of the $form object
return $this->render('accountform.phtml');
}
// form validated, so we can use the supplied values and update the db
$values = $form->getValues(); // returns an array of ['fieldname'=>'value']
// escape the values of the array
EscapeArrayValues($values);
// update db
wp_update_user($values);
// inform the user of successful update via flash message
$this->flashMessage('Successfully updated profile');
// go back to main profile page
$this->redirect('/profile');
That makes your controller relatively clean an easy to work with. The view gets some love and care to, utilizing the $form value to display the form correctly. Technically, you can implement a method in the form class to give you the form html, but for simplicity I'm just going to assume your form html is manually coded in accountform.phtml and it just uses $form to get field info
<form action='post'>
<label>first name</label> <input class='<?=$this->form->getElement('first_name')->hasError() ? "invalid":""?>' type='text' name='first_name' value="<?=$this->form->getElement('first_name')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('first_name')->getError()?></span><br/>
<label>last name</label> <input class='<?=$this->form->getElement('last_name')->hasError() ? "invalid":""?>' type='text' name='last_name' value="<?=$this->form->getElement('last_name')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('last_name')->getError()?></span><br/>
<label>other</label> <input class='<?=$this->form->getElement('other')->hasError() ? "invalid":""?>' type='text' name='other' value="<?=$this->form->getElement('other')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('other')->getError()?></span><br/>
<input type='submit' value='submit'/>
</form>
Here the pseudo code relies on the form class method "getElement" which returns the field class instance for the specified field name (which would be created an initialized in the constructor of your form class). Then on the field class methods "hasError" and "getError" to check if the field validated correctly. If the form has not be submitted yet, then these return false and blank, but if the form was posted and invalid, then they will have been set appropriately in the validate method when it was called. Also "getValue" would return either the value supplied by the user when the form was submitted, or if the form has not been submitted, the default value as specified when the field class was instantiated and initialized.
Obviously this pseudo code is relying on a lot of magic that you'd have to implement if you roll your own solution--and it's certainly doable. However, at this point I'll direct you to the Zend Framework Zend_Form components. You can use zend framework components by themselves without having to utilize the entire framework and application structure too. You might also find similar form component solutions from other frameworks but I wouldn't know about those (we are a Zend Framework shop at my work place).
Hopefully this hasn't been too complicated, and you know where to go from here. Of course just ask if you need any clarification.
I have a page in DNN like:
http://nolimitswebdesign.com.dnnmax.com/test/tabid/57/ctl/Edit/mid/374/Default.aspx
I need to send a post request to that page using PHP+Curl which modifies the content of text area and saves it (like as if someone modified it manually and clicked the update button on that page). I doubt that with DNN it might not be possible. Please advise.
Here is how I would approach the problem the same general technique will work on any website. In this context DNN is just an average ASP.Net website. First look at the javascript that runs when update is clicked:
__doPostBack('dnn$ctr374$EditHTML$cmdUpdate','')
Find the __doPostBack method:
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
This is the standard doPostBack() method used in many ASP.Net forms. From this you can see that you want to fill in the __EVENTTARGET and __EVENTARGUEMENT hidden fields with the appropriate values from the method call and submit the form.
Of course you also need to fill in the data you actually want to save into the input control for the text box. It will probably be easier to do this if you use the basic text box mode of the HTML module, then you just need to set the value of a textarea rather than figure out where to insert the value in the fckEditor, and the technique will be still work if the site is configured to use the Telerik provider instead of the fck provider.
One thing to watch out for is that the control name may change from time to time, so you need to be sure you are reading the correct ids for the event target, and textarea not just hard coding something.
I am using Symfony 1.3.2 with Propel ORM on Ubuntu 9.10.
I have developed a form that dynamically populates a select widget with cities in a selected country, using AJAX.
Before the data entered on the form is saved, I validate the form. If validation fails, the form is presented back to the user for correction. However, because the country list is dynamically generated, the form that is presented for correction does not have a valid city selected (it is empty, since the country widget has not changed yet).
This is inconvenient for the user, because it means they have to select ANOTHER country (so the change event is fired), and then change back to the original country they selected, then FINALLY select the city which they had last selected.
All of this is forced on the user because another (possibly unrelated) field did not vaildate.
I tried $form->getValue('widget_name'), called immediately after $form->bind(), but it seems (infact, IIRC, if form fails to validate, all the values are reset to null) - so that does not work.
I am currently trying a nasty hack which involves the use of directly accesing the input (i.e. tainted) data via $_POST, and setting them into a flash variable - but I feel its a very nasty hack)
What I'm trying to do is a common use case scenario - is there a better way to do this, than hacking around with $_POST etc?
What I do for this exact issue is that I post the form to the same action that generated it, and in that action, I grab any selected countries/regions/cities as POST variables and pass them back to the template (regardless of validation). In the template, I then use JQuery to set the select values to what they were. When validation passes, they get used. When not, they get passed back to template.
If you can tolerate a little PHP in your JQuery, you could do this in the template:
$(document).ready(function()
{
$("#country-select").val('<?php echo $posted_country; ?>');
});
If you use this approach, don't forget to initialise $this->posted_country in your template the first time around or Jquery will get confused.
I guess you could also use $this->form->setWidget(...)->setDefault(...) or something similar, but I havent found a way around using $_POST as accessing the elements seems to need binding the form otherwise.
UPDATED CODE IN RESPONSE TO COMMENTS BELOW:
if($_POST['profile']['country_id'] != '')
{
$this->posted_country = $_POST['profile']['country_id'];
$q = Doctrine_Query::create()
->select('c.city_id, c.city_name')
->from('City c')
->where('c.country_id = ?', $this->posted_country);
$cities = $q->execute(array(), Doctrine_Core::HYDRATE_NONE);
foreach($cities as $city) $list[$city[0]] = $city[1];
$this->form->setWidget('city_id', new sfWidgetFormChoice(array('choices' => array('' => 'Please select') + $list)));
}
So... I get the country from the post, I query db with that, get cities, and craft cities back into a dropdown. Then in the template, you can set a default selected city with something like $this->posted_city (which would be a POST variable too, if exists).
I'm currently playing around with HTML_QuickForm for generating forms in PHP. It seems kind of limited in that it's hard to insert my own javascript or customizing the display and grouping of certain elements.
Are there any alternatives to QuickForm that might provide more flexibility?
If you find it hard to insert Javascript into the form elements, consider using a JavaScript framework such as Prototype or jQuery. There, you can centralize the task of injecting event handling into form controls.
By that, I mean that you won't need to insert event handlers into the HTML form code. Instead, you register those events from somewhere else. For example, in Prototype you would be able to write something like this:
$('myFormControl').observe('click', myClickFunction)
Also have a look at the answers to another question.
/EDIT: of course, you can also insert custom attributes and thus event handlers into the form elements using HTML_QuickForm. However, the above way is superior.
I've found the Zend_Form package of the Zend Framework to be particulary flexible. This component can also be used with Zend_Dojo to rapidly implement common javascript form helpers. However, the component is agnostic when it comes to the library that you use, but supports Dojo naitively. The component also allows for grouping, multi-page forms, custom decorators, validators and other features making it very flexible.
I wrote an article about this for OnLamp: Autofilled PHP Forms
My beef with HTML_QuickForm and Zend_Form and all the other form-handling frameworks I could find is that they seem to assume that you'll be writing code to generate the form. But that didn't match my development process, where I'd start with the LOOK of the page (specified via HTML templates) and add functionality to it.
In my view of the world, form handling boils down to:
Fetching the data that should go in the form to start (usually from a database).
Fetching the HTML code for the form (usually a template).
Mushing 1 and 2 together, and outputting the result.
Getting the submitted form data and validating it.
Re-displaying the form with error messages and the invalid data, OR
Displaying the congratulations-your-data-was-ok page.
fillInFormValues() makes 2, 3, and 5 really easy.
I'll second Zend_Form; it has an excellent ini style implementation that allows you to define a form extremely quickly:
[main]
vessel.form.method = "post"
vessel.form.elements.name.type = "text"
vessel.form.elements.name.name = "name"
vessel.form.elements.name.options.label = "Name: "
vessel.form.elements.name.options.required = true
vessel.form.elements.identifier_type.type = "select"
vessel.form.elements.identifier_type.name = "identifier_type"
vessel.form.elements.identifier_type.options.label = "Identifier type: "
vessel.form.elements.identifier_type.options.required = true
vessel.form.elements.identifier_type.options.multioptions.IMO Number = "IMO Number";
vessel.form.elements.identifier_type.options.multioptions.Registry organisation and Number = "Registry organisation and Number";
vessel.form.elements.identifier_type.options.multioptions.SSR Number = "SSR Number";
vessel.form.elements.identifier.type = "text"
vessel.form.elements.identifier.name = "identifier"
vessel.form.elements.identifier.options.label = "Identifier: "
vessel.form.elements.identifier.options.required = true
vessel.form.elements.identifier.options.filters.lower.filter = "StringToUpper"
vessel.form.elements.email.type = "text"
vessel.form.elements.email.name = "email"
vessel.form.elements.email.options.label = "Email: "
vessel.form.elements.email.options.required = true
vessel.form.elements.owner_id.type = "hidden"
vessel.form.elements.owner_id.name = "owner_id"
vessel.form.elements.owner_id.options.required = true
; submit button
vessel.form.elements.submit.type = "submit"
vessel.form.elements.submit.name = "Update"
vessel.form.elements.submit.option.value = "Update"
With Zend_Form it is entirely possible to start with your form visually, and then work backwords.
This is done by removing all decorators and replacing them with a ViewScript decorator
$this->form->setDecorators( array(array('ViewScript', array('viewScript' => 'forms/aform.phtml'))));
And in that viewscript you would do something like this:
<?=$this->element->title->renderViewHelper()?>
Going with this approach, you can basically do anything you want with the form.
Another great thing about Zend_Form is that you can create custom elements which can encapsulate other stuff in them. For example, you can have an element which outputs a textarea and then some Javascript to turn it into a WYSIWYG area.
I can't really say anything about it but, the other day, I ran across the clonefish form library. It looked promising enough to end up in my bookmarks list as a "look at this later".