I want to apply Moodle rule types: compare and format working with mform elements.
For forename element, input must allow with a format.
For designation element, input must match with any of the array values given. I've given "compare". Both seems not working.
<?php
require('config.php');
require_once($CFG->libdir.'/formslib.php');
class active_form extends moodleform {
function definition() {
$mform = $this->_form;
$fileoptions = $this->_customdata['fileoptions'];
$mform->addElement('text', 'forename', get_string('forename', 'form'),
array('name'=>'forename[]'));
$mform->setType('forename', PARAM_RAW);
$mform->addRule('forename', get_string('forename'), 'required', $format='$NAME_00_00#',
'client', $force=true);
$mform->addElement('text', 'designation', get_string('designation', 'form'),
array('name'=>'designation[]'));
$mform->setType('designation', PARAM_RAW);
$mform->addRule('designation', get_string('err_designation'), 'compare',
array('designation' =>'IT', 'Mechanical', 'EEE', ECE'), 'neq');
$this->add_action_buttons();
}
function validation($data, $files) {
$errors = parent::validation($data, $files);
return $errors;
}
}
?>
Related
I have two issues while using the Zend-Form date element.
First: field binding
The edit action within my controller doesn't fillin an existing date. For example birthday. The field is just empty. (with an element type text, there is no problem).
Here how I instanciated the field:
$this->add([
'name' => 'geburtstag',
'type' => 'date',
'options' => [
'label' => 'Geburtstag:',
'format' => 'dd/mm/yyyy',
],
]);
And here my controller action.
public function addAction()
{
$form = new AnsprechpartnerForm(NULL, $this->db);
$form->get('submit')->setValue('save');
$request = $this->getRequest();
if (! $request->isPost()) {
return ['form' => $form];
}
$ansprechpartner = new Ansprechpartner();
$form->setInputFilter($ansprechpartner->getInputFilter());
$form->setData($request->getPost());
if (! $form->isValid()) {
return ['form' => $form];
}
$ansprechpartner->exchangeArray($form->getData());
$this->ansprechpartnerTable->saveAnsprechpartner($ansprechpartner);
return $this->redirect()->toRoute('ansprechpartner');
}
No inputFilter at the moment, I tried with and without.
Second: validation
I have trouble filling in dates. While I don't use any filters for this field, I would expect, I could fill any date in.
Interesting I get the message double.
I solved it.
The date element expects the format y-m-d. Now I gave it directly to the field after binding the form. The format in the field is now also korrekt.
$form->bind($notizen);
$form->get('submit')->setAttribute('value', 'edit');
$filter = new \Zend\Filter\DateTimeFormatter();
$filter->setFormat('Y-m-d');
$dat = $filter->filter($notizen->datum);
$form->get('datum')->setValue($dat);
Could be more convenient I guess.
I have a Sonata Admin class with some form fields in it, but I'd like to use the FormEvents::PRE_SET_DATA event to dynamically add another form field based on the bound data.
However, I'm running into several problems:
1) The only way I can find to add the form field to the correct 'formGroup' in the admin is by adding the new field twice (once via the formMapper and once via the form itself)... this seems very wrong and I cannot control where in the formGroup it appears.
2) The added element doesn't seem to know that it has a connected Admin (probably because it is added using Form::add()). This means, amongst other things, that it renders differently to the other fields in the form since it triggers the {% if sonata_admin is not defined or not sonata_admin_enabled or not sonata_admin.field_description %} condition in form_admin_fields.html.twig
So, this leads me to believe that I'm doing this all wrong and there must be a better way.
So...
What is the correct way to use a FormEvent to add a field to a form group, ideally in a preferred position within that group, when using SonataAdmin?
Here's some code, FWIW...
protected function configureFormFields(FormMapper $formMapper)
{
$admin = $this;
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($admin, $formMapper) {
$subject = $event->getData();
// do something fancy with $subject
$formOptions = array(/* some cool stuff*/);
// If I don't add the field with the $formMapper then the new field doesn't appear on the rendered form
$formMapper
->with('MyFormGroup')
->add('foo', null, $formOptions)
->end()
;
// If I don't add the field with Form::add() then I get a Twig Exception:
// Key "foo" for array with keys "..." does not exist in my_form_template.html.twig at line xx
$event
->getForm()
->add('foo', null, $formOptions)
;
});
$formMapper
->with('MyFormGroup')
->add('fieldOne')
->add('fieldTwo')
->end()
;
}
The aim is to add the new foo field between fieldOne and fieldTwo in MyFormGroup.
Edit: here's what I came up with with the help of Cassiano's answer
protected function configureFormFields(FormMapper $formMapper)
{
$builder = $formMapper->getFormBuilder();
$ff = $builder->getFormFactory();
$admin = $this;
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($ff, $admin) {
$subject = $event->getData();
// do something fancy with $subject
$formOptions = array(
'auto_initialize' => false,
'class' => 'My\ProjectBundle\Entity\MyEntity',
/* some cool stuff*/
);
$event->getForm()->add($ff->createNamed('foo', 'entity', null, $formOptions));
});
$formMapper
->with('MyFormGroup')
->add('fieldOne')
->add('foo') // adding it here gets the field in the right place, it's then redefined by the event code
->add('fieldTwo')
->end()
;
}
No time here for a long answer, I will paste a piece of code and I don't know if fits exactly in your case, in my it's part of multi dependent selects (country, state, city, neighbor).
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Doctrine\ORM\EntityRepository;
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('myfield');
$builder = $formMapper->getFormBuilder();
$ff = $builder->getFormFactory();
$func = function (FormEvent $e) use ($ff) {
$form = $e->getForm();
if ($form->has('myfield')) {
$form->remove('myfield');
}
$form->add($ff->createNamed('myfield', 'entity', null, array(
'class' => '...',
'attr' => array('class' => 'form-control'),
'auto_initialize' => false,
'query_builder' => function (EntityRepository $repository) use ($pais) {
$qb = $repository->createQueryBuilder('estado');
if ($pais instanceof ...) {
$qb = $qb->where('myfield.other = :other')
->setParameter('other', $other);
} elseif(is_numeric($other)) {
$qb = $qb->where('myfield.other = :other_id')
->setParameter('other_id', $other);
}
return $qb;
}
)));
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, $func);
$builder->addEventListener(FormEvents::PRE_BIND, $func);
}
In my custom plugin I am simply using three drop down and one text box. When I submit the form and validation($data) method is invoked I just get value of state drop down along with the textbox value.
Value of other two drop downs is not returned. I am not sure what I am missing.
Here is my code:
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->libdir.'/formslib.php');
class ohio_addconfiguration_form extends moodleform {
// Define the form
function definition() {
$id = optional_param('id', 0, PARAM_INT);
$countries = array();
$states = array();
$counties = array();
$cities = array();
$mform =& $this->_form;
// Creating hidden variable id
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
// Creating header "Configuration"
$mform->addElement('header', 'configuration', get_string('ohio', 'local_ohio'));
/* Listing States */
$states_result = $this->get_states("", "1", "id, state_name", "state_name ASC");
if($states_result) {
foreach($states_result as $key=>$state){
$states[$state->id] = $state->state_name;
}
}
$states= count($states)?array(''=>get_string('select_state', 'local_ohio').'...') + $states :array(''=>get_string('select_state', 'local_ohio').'...');
$mform->addElement('select', 'state_id', get_string('select_state', 'local_ohio'), $states);
$mform->addRule('state_id', get_string('required'), 'required', null, 'client');
$mform->setType('state_id', PARAM_INT);
/* Listing Counties */
$counties= array(''=>get_string('select_county', 'local_ohio').'...');
$mform->addElement('select', 'county_id', get_string('select_county', 'local_ohio'), $counties);
$mform->addRule('county_id', get_string('required'), 'required', null, 'client');
$mform->setType('county_id', PARAM_INT);
/* Listing Cities */
$cities= array(''=>get_string('select_city', 'local_ohio').'...');
$mform->addElement('select', 'city_id', get_string('select_city', 'local_ohio'), $cities);
$mform->addRule('city_id', get_string('required'), 'required', null, 'client');
$mform->setType('city_id', PARAM_INT);
// Creating text box for School
$mform->addElement('text', 'school_name', get_string('school_name', 'local_ohio'), 'size="25"');
$mform->setType('school_name', PARAM_TEXT);
$mform->addRule('school_name', get_string('required'), 'required', null, 'client');
$mform->addRule('school_name', get_string('maximumchars', '', 100), 'maxlength', 100, 'client');
$this->add_action_buttons();
}
function validation($data) {
global $DB;
echo "<pre>";
print_r($data);
exit;
}
}
I'm not sure what are you looking for exactly, either form validation or data retrieval, but I'm assuming you're interested in data retrieval and the code you have provided above is written in 'filename_form.php'.
The validation() method is used to validate the data entered in the fields on the server side, and not to get the values of the fields.
To get the values of the fields, you need to create another file named 'filename.php', and include 'filename_form.php' in it for displaying the form.
You can refer here for using Formslib.
I have a Zend_Form form, with some custom decorators, like this:
$decorators = array();
$decorators[] = new Zend_Form_Decorator_ViewHelper(array());
$decorators[] = new Zend_Form_Decorator_Errors;
$decorators[] = new Zend_Form_Decorator_HtmlTag(array('tag' => 'div', 'class' => 'form-item'));
$decorators[] = new Zend_Form_Decorator_Label(array('class' => 'form-label'));
$decorators[] = new Zend_Form_Decorator_Callback(array(
'callback' => function($content, $element, $options) {
return sprintf('<div class="form-row">%s</div>', $content);
},
'placement' => false
));
$this->setElementDecorators($decorators);
The problem is, that all of the fields are rendered as text inputs. Why does it happen?
EDIT: I discovered, that it doesn't render all the inputs necessarily as text inputs, but renders them with type of the first input in form. Here is example of a form that i use(the decorators are set int parent's init):
<?php
class Form_Users_Add extends Form_Base {
protected $pbxs = array(1 => 'Element 1', 2 => 'Element 2');
public function init() {
$monitors = new Zend_Form_Element_Checkbox('prefered_screen_count');
$monitors->setCheckedValue(2);
$monitors->setUncheckedValue(1);
$monitors->setLabel('two_monitors');
$this->addElement($monitors);
$pbx = new Zend_Form_Element_Select('asterisk_id');
$pbx->setMultiOptions($this->pbxs);
$pbx->setLabel('users_asterisk_id');
$this->addElement($pbx);
parent::init();
}
}
Yay! I have solved the issue! The cause was that I used INSTANCES of classes, not the names. This way every element was using the same instance of the decorator.
I am a Yii Beginner and I am currently working on a Tagging system where I have 3 tables:
Issue (id,content,create_d,...etc)
Tag (id,tag)
Issue_tag_map (id,tag_id_fk,issue_id_fk)
In my /Views/Issue/_form I have added a MultiComplete Extension to retrieve multiple tag ids and labels,
I have used an afterSave function in order to directly store the Issue_id and the autocompleted Tag_ids in the Issue_tag_map table, where it is a HAS_MANY relation.
Unfortunately Nothing is being returned.
I wondered if there might be a way to store the autocompleted Tag_ids in a temporary attribute and then pass it to the model's afterSave() function.
I have been searching for a while, and this has been driving me crazy because I feel I have missed a very simple step!
Any Help or advices of any kind are deeply appreciated!
MultiComplete in Views/Issue/_form:
<?php
echo $form->labelEx($model, 'Tag');
$this->widget('application.extension.MultiComplete', array(
'model' => $model,
'attribute' => '', //Was thinking of creating a temporary here
'name' => 'tag_autocomplete',
'splitter' => ',',
'sourceUrl' => $this->createUrl('Issue/tagAutoComplete'),
// Controller/Action path for action we created in step 4.
// additional javascript options for the autocomplete plugin
'options' => array(
'minLength' => '2',
),
'htmlOptions' => array(
'style' => 'height:20px;',
),
));
echo $form->error($model, 'issue_comment_id_fk');
?>
AfterSave in /model/Issue:
protected function afterSave() {
parent::afterSave();
$issue_id = Yii::app()->db->getLastInsertID();
$tag; //here I would explode the attribute retrieved by the view form
// an SQL with two placeholders ":issue_id" and ":tag_id"
if (is_array($tag))
foreach ($tag as $tag_id) {
$sql = "INSERT INTO issue_tag_map (issue_id_fk, tag_id_fk)VALUES(:issue_id,:tag_id)";
$command = Yii::app()->db->createCommand($sql);
// replace the placeholder ":issue_id" with the actual issue value
$command->bindValue(":issue_id", $issue_id, PDO::PARAM_STR);
// replace the placeholder ":tag_id" with the actual tag_id value
$command->bindValue(":tag_id", $tag_id, PDO::PARAM_STR);
$command->execute();
}
}
And this is the Auto Complete sourceUrl in the Issue model for populating the tags:
public static function tagAutoComplete($name = '') {
$sql = 'SELECT id ,tag AS label FROM tag WHERE tag LIKE :tag';
$name = $name . '%';
return Yii::app()->db->createCommand($sql)->queryAll(true, array(':tag' => $name));
actionTagAutoComplete in /controllers/IssueController:
// This function will echo a JSON object
// of this format:
// [{id:id, name: 'name'}]
function actionTagAutocomplete() {
$term = trim($_GET['term']);
if ($term != '') {
$tags = issue::tagAutoComplete($term);
echo CJSON::encode($tags);
Yii::app()->end();
}
}
EDIT
Widget in form:
<div class="row" id="checks" >
<?php
echo $form->labelEx($model, 'company',array('title'=>'File Company Distrubution; Companies can be edited by Admins'));
?>
<?php
$this->widget('application.extension.MultiComplete', array(
'model' => $model,
'attribute' => 'company',
'splitter' => ',',
'name' => 'company_autocomplete',
'sourceUrl' => $this->createUrl('becomEn/CompanyAutocomplete'),
'options' => array(
'minLength' => '1',
),
'htmlOptions' => array(
'style' => 'height:20px;',
'size' => '45',
),
));
echo $form->error($model, 'company');
?>
</div>
Update function:
$model = $this->loadModel($id);
.....
if (isset($_POST['News'])) {
$model->attributes = $_POST['News'];
$model->companies = $this->getRecordsFromAutocompleteString($_POST['News']
['company']);
......
......
getRecordsFromAutocompleteString():
public static cordsFromAutocompleteString($string) {
$string = trim($string);
$stringArray = explode(", ", $string);
$stringArray[count($stringArray) - 1] = str_replace(",", "", $stringArray[count($stringArray) - 1]);
$criteria = new CDbCriteria();
$criteria->select = 'id';
$criteria->condition = 'company =:company';
$companies = array();
foreach ($stringArray as $company) {
$criteria->params = array(':company' => $company);
$companies[] = Company::model()->find($criteria);
}
return $companies;
}
UPDATE
since the "value" porperty is not implemented properly in this extension I referred to extending this function to the model:
public function afterFind() {
//tag is the attribute used in form
$this->tag = $this->getAllTagNames();
parent::afterFind();
}
You should have a relation between Issue and Tags defined in both Issue and Tag models ( should be a many_many relation).
So in IssueController when you send the data to create or update the model Issue, you'll get the related tags (in my case I get a string like 'bug, problem, ...').
Then you need to parse this string in your controller, get the corresponding models and assigned them to the related tags.
Here's a generic example:
//In the controller's method where you add/update the record
$issue->tags = getRecordsFromAutocompleteString($_POST['autocompleteAttribute'], 'Tag', 'tag');
Here the method I'm calling:
//parse your string ang fetch the related models
public static function getRecordsFromAutocompleteString($string, $model, $field)
{
$string = trim($string);
$stringArray = explode(", ", $string);
$stringArray[count($stringArray) - 1] = str_replace(",", "", $stringArray[count($stringArray) - 1]);
return CActiveRecord::model($model)->findAllByAttributes(array($field => $stringArray));
}
So now your $issue->tags is an array containing all the related Tags object.
In your afterSave method you'll be able to do:
protected function afterSave() {
parent::afterSave();
//$issue_id = Yii::app()->db->getLastInsertID(); Don't need it, yii is already doing it
foreach ($this->tags as $tag) {
$sql = "INSERT INTO issue_tag_map (issue_id_fk, tag_id_fk)VALUES(:issue_id,:tag_id)";
$command = Yii::app()->db->createCommand($sql);
$command->bindValue(":issue_id", $this->id, PDO::PARAM_INT);
$command->bindValue(":tag_id", $tag->id, PDO::PARAM_INT);
$command->execute();
}
}
Now the above code is a basic solution. I encourage you to use activerecord-relation-behavior's extension to save the related model.
Using this extension you won't have to define anything in the afterSave method, you'll simply have to do:
$issue->tags = getRecordsFromAutocompleteString($_POST['autocompleteAttribute'], 'Tag', 'tag');
$issue->save(); // all the related models are saved by the extension, no afterSave defined!
Then you can optimize the script by fetching the id along with the tag in your autocomplete and store the selected id's in a Json array. This way you won't have to perform the sql query getRecordsFromAutocompleteString to obtain the ids. With the extension mentioned above you'll be able to do:
$issue->tags = CJSON::Decode($_POST['idTags']);//will obtain array(1, 13, ...)
$issue->save(); // all the related models are saved by the extension, the extension is handling both models and array for the relation!
Edit:
If you want to fill the autocomplete field you could define the following function:
public static function appendModelstoString($models, $fieldName)
{
$list = array();
foreach($models as $model)
{
$list[] = $model->$fieldName;
}
return implode(', ', $list);
}
You give the name of the field (in your case tag) and the list of related models and it will generate the appropriate string. Then you pass the string to the view and put it as the default value of your autocomplete field.
Answer to your edit:
In your controller you say that the companies of this model are the one that you added from the Autocomplete form:
$model->companies = $this->getRecordsFromAutocompleteString($_POST['News']
['company']);
So if the related company is not in the form it won't be saved as a related model.
You have 2 solutions:
Each time you put the already existing related model in you autocomplete field in the form before displaying it so they will be saved again as a related model and it won't disapear from the related models
$this->widget('application.extensions.multicomplete.MultiComplete', array(
'name' => 'people',
'value' => (isset($people))?$people:'',
'sourceUrl' => array('searchAutocompletePeople'),
));
In your controller before calling the getRecordsFromAutocompleteString you add the already existing models of the model.
$model->companies = array_merge(
$model->companies,
$this->getRecordsFromAutocompleteString($_POST['News']['company'])
);