Yii2: Adding dynamic DateControl fields from related model to a form - datepicker

I have three models: User, Job and UserJob. The models User and Job have n:m relation. In the user form I need to dynamically add new jobs.
In the model User I get the jobs related to the current user:
public function getUserJobs() {
return UserJob::find()
->where(['user_id' => $this->id])
->orderBy('start DESC')
->all();
}
In the views/user/_form.php are the existing jobs added to the form as follows:
if (isset($userJobs)) {
$i = 0;
foreach ($userJobs as $job) {
$i++;
...
echo $form->field($job, '['.$i.']start')->widget(DateControl::className(), [
'type' => kartik\datecontrol\DateControl::FORMAT_DATE,
'saveOptions' => [
'name' => 'job_start[]',
],
])->label(false);
...
I add new jobs with jQuery (similarly as in these examples). For most fields it works perfectly. But there is a problem with the field start, which uses the DateControl extension. The extension produces its own jQuery script. Without manipulating this script the start field can not work correctly.
Is there a way how to dynamically add new DateControl fields to the form?

A possible solution is to add a few hidden rows with job-fields:
for ($j=$i; $j < $i+3; $j++) {
$job = new MitarbeiterJob();
echo '<tr style="display:none;" class="new_job">';
...
echo $form->field($job, '['.$j.']start')->widget(DateControl::className(), [
'type' => DateControl::FORMAT_DATE,
'saveOptions' => [
'name' => 'job_start[]',
],
])->label(false);
}
...
If a user clicks on the button 'Add a new job', then a jQuery script shows one of the rows.
It works, but the solution is not optimal...

Related

Contact Form 7 - how to add several number fields dynamically

I am currently working on a web project where users should be able to have a list of brochures (list comes from custom post type) and enter the amount they would like to order individually for each of them via a simple contact form. My idea was to generate a dynamic list of number fields with the 'wpcf7_form_tag_data_option' hook like in this article:
Dynamicly populate Contact form 7 input fields with current user info when logged in in Wordpress website
So if i am using the select or checkbox field type within contact form 7:
[select anzahlKataloge data:brochures]
or
[checkbox anzahlKataloge data:brochures]
and functions.php:
add_filter('wpcf7_form_tag_data_option', function($n, $options, $args) {
if (in_array('brochures', $options)){
$query = new WP_Query(
array( 'orderby' => 'date', 'order' => 'DESC', 'posts_per_page' => '100', 'post_type' => 'prospekte')
);
while($query->have_posts()) : $query->the_post();
$title = get_the_title();
$brochures[] = $title;
endwhile;
return $brochures;
}
return $n;
}, 10, 3);
It generates the select or checkbox list just fine but i would actually need it to work with the number field type:
[number anzahlKataloge data:brochures]
or with text type:
[text anzahlKataloge data:brochures]
Any help is much appreciated.
Best Regards,
Rafael

display radio buttons using FormHelper

I am using CakePHP3
I am unable to decipher the documentation on using radio buttons to create for a list of results.
There is create radio button.
Which I tried this way:
foreach ($userGroups as $group_id => $group) {
echo $this->Form->radio("UserGroup.$group_id.id", ['value' => $group_id, 'label' => $group]);
}
There is using select pickers.
Which says all you need to do is set the type to radio. But I checked the api and it looks like it will unset the type.
Anyway, I tried
echo $this->Form->select("user_circle_id", $userGroups, ['type' => 'radio']);
Nothing works.
Please advise.
UPDATE:
$userGroups = [1 => 'Group 1', 2 => 'Group 2']; // basically primary key is keys and the display fields are the values.
Use following syntax :
$selectedStatus = 1;
$attributes = array('legend'=>false,'value'=>$selectedStatus,'class'=>'sortByStatusCourse','name'=>'sortByStatusCourse');
$options = array("1"=>"Active","0"=>"Inactive");
echo $this->Form->radio('is_active', $options,$attributes);
Code explanation :
We are passing parameters to cakePHP core so that it will output Radio button in HTML.
echo $this->Form->radio :
Here 1st param is Name on field in Database. 2nd param is What are the options for radio buttons , its value and text .3rd param is what are its attributes like what should be its default value here I explicitely set it to 1 so it will already tick radio with Activetext.
According to your code : echo $this->Form->radio("UserGroup.$group_id.id", ['value' => $group_id, 'label' => $group]);
I think order of param is wrong as you pass VALUE in 2nd param which should be 3rd param.Check my code.

cakephp select dropdown list

i have the following models: Student,Form,FormsStream.
the student model have foreign keys to both models. Student.form_id is a foreignKey to the form table,Student.stream_id is foreign to the FormsStream table. i already have entries for both forms and formsStreams.
to add the student Form and Stream i select both from respective drop-down list. i already created the drop-down list.
my Problem.
previously i was able to save/edit student form and stream but after making those fields foreign keys to be selected i cant save new or edit those fields. i use cakePHP 2.5.4.
however i can do that in SQL.
i cant seem to figure out the problem with the select lists.
my method to get the forms from the database
public function loadStudentForm() {
$this->loadModel('Student'); //load the associated models
$this->loadModel('Form');
$forms = $this->Form->find('list',array('fields' => 'form_name')); //get the forms from the database
$this->set('forms',$forms);
}
method to get streams from database
public function loadStreams() {
$this->loadModel('FormsStream');
$streams = $this->FormsStream->find('list',array('fields' => 'stream_name'));
$this->set('streams',$streams);
}
student add method
public function addStudent() {
if ($this->request->is('post')) {
$this->loadStudentForm();
$this->loadStreams();
$this->Student->create();
if ($this->Student->save($this->request->data)) {
$this->Session->setFlash(__('New student record added to the database.'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The student could not be added.Please ensure all fields are correctly entered'));
}
}
}
the extract from add.ctp for selecting student form and stream
echo $this->Form->input('forms',array('label' => 'Form'));
echo $this->Form->input('streams',array('label' => 'Class stream'));
so far printing contents of validateErrors shows this:
array(
'Student' => array(
'form_id' => array(
(int) 0 => 'You must select the form'
),
'stream_id' => array(
(int) 0 => 'You must choose a stream'
)
),
'Form' => array(),
'FormsStream' => array()
)
i've even tried to intercept the request in burpsuite and i can see the form and stream id are passed.just dont get why they are empty in the above array
_method=POST&data%5BStudent%5D%5Badmission_no%5D=1002&data%5BStudent%5D%5Bfirst_name%5D=peter&data%5BStudent%5D%5Bmiddle_name%5D=per&data%5BStudent%5D%5Blast_name%5D=pee&data%5BStudent%5D%5Bgender%5D=Male&data%5BStudent%5D%5Bdate_of_birth%5D%5Bmonth%5D=11&data%5BStudent%5D%5Bdate_of_birth%5D%5Bday%5D=05&data%5BStudent%5D%5Bdate_of_birth%5D%5Byear%5D=2014&data%5BStudent%5D%5Bjoin_date%5D%5Bmonth%5D=11&data%5BStudent%5D%5Bjoin_date%5D%5Bday%5D=05&data%5BStudent%5D%5Bjoin_date%5D%5Byear%5D=2014&data%5BStudent%5D%5Bforms%5D=1&data%5BStudent%5D%5Bsstrong texttreams%5D=1
The input must be form_id and stream_id. Even if you set them with plural form cakephp recognize it in the following form.
echo $this->Form->input('form_id',array('label' => 'Form'));
echo $this->Form->input('stream_id',array('label' => 'Class stream'));

Yii CJuiAutoComplete for Multiple values

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'])
);

Add Database Row Button Cakephp

The Controller:
function add(){
if (!empty($this->data)) {
$qnote = $this->Qnote->save($this->data);
if (!empty($qnote)) {
$this->data['Step']['qnote_id'] = $this->Qnote->id;
$this->Qnote->Step->save($this->data);
}
$this->Session->setFlash('Your note has been saved.');
$this->redirect(array('action' => 'index'));
}
}
The Form.
<?php
$userID = Authsome::get('id');
echo $form->create('Qnote', array('action'=>'add'));
echo $form->input('Qnote.id', array('type' => 'hidden'));
echo $form->input('Qnote.user_id', array('value' => $userID, 'type' => 'hidden'));
echo $form->input('Qnote.subject');
echo $form->input('Qnote.body', array('rows' => '3'));
echo $form->input('Step.id', array('type' => 'hidden'));
echo $form->input('Step.user_id', array('value' => $userID, 'type' => 'hidden'));
echo $form->input('Step.body', array('rows' => '3'));
echo $form->end('Save Notes');
?>
This Form Adds Data in 2 Models.
Model 1 = Qnote;
Model 2 = Step;
I am able to add Data to the Models.
I was wondering I could add a button to the form
The Button would allow users to add multiple Step.data to the Step model.
Some like a +1 Button.
Basically I want to add multiple steps Per Qnote.
Could someone point me in the right direction how i can achieve this.
This is something I would do with jQuery. Basically all you need to do is using jQuery to dynamically add more inputs in the CakePHPs conventions: Step.0.user_id for example.
What you need to do now on a +1: you need to count the zero up, so you will get Step.1.user_id and so on.
First option: Use a jQuery-Script for doing this
var count = 1;
$('#add_step').click(function() {
var new_form = $('.Step').eq(0).clone();
$('input, textarea, select, radio', new_form).filter('[name^="data"]').each(function() {
var name = $(this).attr('name');
var new_name = name.replace(/\[\d*\]/, '['+count+']');
$(this).attr('name', new_name).attr('value', '');
});
$('#YourForm').after(new_form);
count+;
return false;
});
In this case you're cloning a div with the class step which holds your inputs for the model Step. You then replace the name-attribute to replace the zeros through the new value of the variable count. count++ enables you to add as many steps as you want.
This is an jQuery only solution and may require additional work for your environment.
Second option: Use AJAX with an element
You could also write a function in your StepsController which renders an element which holds your form and takes care of the counter.
Third option: Use a URL-parameter to decide how many options you want
If you have an URL like /qnote/add/3 you could use the 3 as a parameter in a for-loop to iterate through these form-inputs.
You need to take care, that eventually already typed in values are sent with the form when adding another Step so that these don't get lost.
Hope this helps to get on the right way.