Yii: Receiving multiple records for a single model from view in POST method: To save Parent Child data - yii

I am in a situation where I have more than 4 child tables associated with one Parent table. I need to create a user experience in which user presses Save button only once, meaning by, user enters all the data in parent model fields, then enters data in all four child model fields and then presses the save button. As far as I know, having relations in the model allows you to make associated rows inserted easily but the main problem is how to receive multiple rows from view in POST method for a single model (here I essentially mean the child models). I have tried it manually by repeating the attributes of child model in view but when I save the record, only the last rowset gets stored in the child table along with parent table, one row for the child table gets missed. Kindly note that I am using CActiveForm and other Bootstrap widgets in my View files.
Is it possible in Yii or I am too wishful....any suggestions or comments ????
Many thanks in advance.
Regards,
Faisal

I got the solution but with all the help from here and also from other forums. I followed the post by Kiran and tested it by generating additional HTML attributes using jQuery. On the submit, I got all the rows exactly how I wanted. In the controller, first I counted the total number of models submitted in the post request and then iterated over each one for desired processing. Following is the code snippet.
if(!empty($_POST))
{
$v=count($_POST['Address'])+1;
Yii::log(count($_POST['Address']));
for ($i=1; $i<$v; $i++){
$addressModel_1->attributes=$_POST['Address'][$i];
Yii::log('Dumping Data from '.$i.' model');
Yii::log($addressModel_1->city);
Yii::log($addressModel_1->street);
Yii::log($addressModel_1->state);}}
On the view side, I generated the HTML using jQuery function. All this function did was to add another set of html to allow the user to enter data. Important thing to note while generating the HTML is the name of model or else it wouldn't land where you want in controller.
Following is the code snippet of this function. Please note that I am hardcoding the "3" as id since I already had two sets of rows in DOM. I am going to further improve this code but rest assured, the logic works.
function createNewAddress(){
var newdiv = document.createElement('div');
var inner_html='<div class="row">';
inner_html+='<label for="Address_3_street">Street</label> <input name="Address[3][street]" id="Address_3_street" type="text" maxlength="200" /> </div>';
inner_html+='<div class="row">';
inner_html+='<label for="Address_3_city">City</label> <input name="Address[3][city]" id="Address_3_city" type="text" maxlength="200" /> </div> ';
inner_html+='<div class="row">';
inner_html+='<label for="Address_3_state">State</label> <input name="Address[3][state]" id="Address_3_state" type="text" maxlength="200" /> </div>';
newdiv.innerHTML=inner_html;
$('#user-form').append(newdiv);
}
This way, I can have n-number of child rows added on the fly from browser and user will save all data by pressing the Save or Submit button only once.
Thanks to everyone for their support.
Regards,
Faisal

You should be using tabular input, that way you can receive data for multipe instances of the same type, you can then save the parent and use its id to fill the child foreign keys.

You could make a new CFormModel to handle all the form fields validation, and then manually set the attributes after the $model->validate in the POST function. EG:
if ($model->validate){
$model_one = new ModelOne;
$model_one->name = $model->model_one_name;
$model_one->surname = $model->model_one_surname;
....
$model_two = new ModelTwo;
$model_two->name = $model->model_two_name;
$model_two->surname = $model->model_two_surname;
....
}

Related

v-select : cant show the seleted element

Im using Vuetify in my project. When I insert some data by v-select its working fine. Also when Im edit that data that also works. The only problem is I cant see the selected element when Im click on Edit.
Here is my code
<v-select
prepend-icon="star_rate"
:items="ratings"
v-model="customer.rating"
label="Rating"
item-text="text"
item-value="customer.rating"
single-line
></v-select>
Note: If I use {{customer.rating}} it gives an output like this
{ "id": 1, "text": "Bad" }
and If I select a different element its perfectly change on database. So everything is fine. The only requirement is I want show this value Bad as a selected element when I click on Edit.
Here is the complete code of my project file https://github.com/Shakilzaman87/pukucrm/blob/master/src/components/customers/EditCustomer.vue
Thanks in advance
It's and old question but Let me post my answer to help others as well, after alot of search I have come to this point, and I want to share it with others as well.
//This will load all your ratings in dropdown
<v-select
v-model="customer.ratings"
:items="ratings"
item-text="text"
item-value="id"
label="Rating"
dense>
</v-select>
Now Edit Part
Lets say you want to edit a record, so you will probably pass the record id in edit method of your vuejs then inside edit method of vuejs, you will do an edit axios call for that specific record to first show it inside fields and then you will update. But before update you need to do something inside edit method of your vuejs when you will have already loaded data for that specific id.
Now lets say you have received a record from database according to a specific id, you will see a dropdown id I mean to say a foreign key for a dropdown that you had saved during storing data.
Suppose you have ratings array which holds whole data for a dropdown. Inside this you are having an id and text fields. So you have this ratings array and an object from database during edit for a specific record. Now you are good to go with below code.
Inside Edit Method of vuejs
this.customer.ratings = this.ratings.find(item => item.id === parseInt(res.data.rating_id))
this.customer.ratings = parseInt(this.customer.ratings.rating_id)
Note: I am doing parseInt() it's because when you check in console the primary key is an integer like 1 but foreign key like rating_id is string i-e "1". You can also use two equals == if you are not using parseInt() but I haven't checked that.
For clearly understanding I am just sharing a sample edit code which might help you a bit
editItem(id){
axios.get( `/api/category/${id}` ).then( res => {
if(res.data.status === 200){
console.log(res.data.data)
this.name = res.data.data.name
this.id = res.data.data.id
this.parent_id = this.list_categories.find(item => item.id === parseInt(res.data.data.parent_id))
this.parent_id = parseInt(this.parent_id.id)
this.edited = true
}else{
this.$toaster.error( res.data.message )
}
});
}
I'm not sure what you mean by "... when Im click on Edit." but I will presume you mean when you click on the dropdown menu.
From what you have provided in your jsfiddle, your v-select should be like this:
<v-select
prepend-icon="star_rate"
:items="ratings"
v-model="customer.rating"
label="Rating"
item-text="ratings.text"
item-value="ratings"
single-line
></v-select>
This can be found here, in the API props section.
item-text: Set property of items’s text value
item-value: Set property of items’s value
The text is what you see, I believe that text which is the current value of item-text is either undefined or not declared. If this answer doesn't work, then you need to provide more of your code.

Yii2 Gridview get all selected row for all pagination

I wrapped my gridview with Pjax widget like this
\yii\widgets\Pjax::begin();
gridview
\yii\widgets\Pjax::end();
in order to make the gridview make ajax request when I click on each pagination.
I also use ['class' => 'yii\grid\CheckboxColumn'], in column as well.
and I find that when I'm on first pagination I checked some rows and then go to second page and check some rows but when I go back to first page what I've checked is gone.
My question is how can I keep all checkedrow for all pagination
With current conditions (Pjax, multiple pages, yii\grid\CheckboxColumn) it's impossible because of the way it works.
When you click on the pagination links all GridView html content is replaced by new one that comes from the AJAX response.
So obviously all selected checkboxes on the previous page are gone.
Few possible ways to solve that:
1) Write custom javascript and server side logic.
As one of the options, you can send AJAX request to server with parameter meaning that user has chosen to select all data for the bulk delete operation (or use separate controller action for bulk deletion). In this case actually we don't need to get the selected data from user, because we can simply get them from database (credits - Seng).
2) Increase number of displayed rows per page.
3) Use infinite scroll extension, for example this.
4) Break desired action in several iterations:
select needed rows on first page, do action (for example, delete).
repeat this again for other pages.
You can get selected rows like that:
$('#your-grid-view').yiiGridView('getSelectedRows');
[infinite scroll] : http://kop.github.io/yii2-scroll-pager/ will work good if you do not have any pjax filters. If you have filters also in play, do not use this plugin as it does not support pjax filters with it. For rest of the applications it is perfect to use.
Update1 : it seems to be straight forward than expected, here is the how I accomplished it
Add following lines to the checkbox column
'checkboxOptions' => function($data){
return ['id' => $data->id, 'onClick' => 'selectedRow(this)'];
}
Now add following JS to the common js file you will have in your project of the page where this datagrid resides
var selectedItems=[]; //global variable
/**
* Store the value of the selected row or delete it if it is unselected
*
* #param {checkbox} ele
*/
function selectedRow(ele){
if($(ele).is(':checked')) {
//push the element
if(!selectedItems.includes($(ele).attr('id'))) {
selectedItems.push($(ele).attr('id'));
}
} else {
//pop the element
if(selectedItems.includes($(ele).attr('id'))) {
selectedItems.pop($(ele).attr('id'));
}
}
}
Above function will store the selected row ids in the global variable array
Now add following lines to pjax:end event handler
$(document).on('pjax:end', function () {
//Select the already selected items on the grid view
if(!empty(selectedItems)){
$.each(selectedItems, function (index,value) {
$("#"+value).attr('checked',true);
});
}
});
Hope it helps.
I just solved this problem and it works properly with Pjax.
You may use my CheckboxColumn. I hope this can help. The checked items are recorded with cookies.
You can read the part with //add by hezll to understand how to fix it, because I didn't provide a complete general one.
Hope it works for you.
https://owncloud.xiwangkt.com/index.php/s/dGH3fezC5MGCx4H

Two views 1 Model MVC

When using MVC to capture information in a form, can I break up the from in two views but still use a single model, and write to the db at the last form?
For example Account.cs model has name and surname
I want view1 to capture name, view2 to capture surname and when i click submit it must write to the db.
You can use a model on however many views you like. In fact, one of the features of MVC is that it allows you to use the same model and controller action with different views to present the same data in difference way.
However, in this case, you will be responsible for getting the data from the first view into the second view, so the submit action can write both together.
I don't believe it is possible to use a single form across two views. However, you can use two forms for the two views, using a single model. In view1, capture the name and call the following controller action:
public ActionResult(Account model)
{
return View("View2", model");
}
Then, simply use a hidden input to store the information from view1 in the form in view2:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.HiddenFor(x => x.Name)
#Html.TextBoxFor(x => x.Surname)
</div>
}

dojox.grid.DataGrid: how to access data from a click event?

I'm using Dojo 1.5 (including dojox). I have a dojox.grid.DataGrid where each row represents a user. When I click a row, I want to redirect to a URL like /users/USER_ID. The user ID is one of the fields in the grid, so all I need to do in my onRowClick callback is to grab the user ID for the row that was clicked.
The click event contains a rowIndex property, and, indeed, I found a (rather old) post elsewhere that suggested I should be able to do:
var row = dijit.byId('grid').model.getRow(e.rowIndex);
/* (Then grab the 0th field of the row, which is the user ID.) */
(Sorry, I've since lost the URL.)
But my grid object has no model attribute. What's up with that? Has the API changed? (My grid certainly is populated with data, which I can see, click, sort by column, et cetera).
So I'm stuck for now. Note, BTW, that it won't work to use rowIndex to directly access the grid's underlying dojo.data.ItemFileReadStore. That's because the grid is sortable, so there's no guarantee that the grid's rows will be in the same order as the store's.
Any hints would be deeply appreciated. I hope that the question is clear, and sufficiently general that any answers can help others in my predicament. Many thanks.
I have a similar scenario and I grab the value like this:
onRowClick: function(e) {
open_link(my_grid._getItemAttr(e.rowIndex, 'object_path'));
}
In this case my_grid is a reference to the datagrid and object_path is the column where I store the path to the object. open_link is of course a custom function of mine that as it implies, requests a server path.
So just change the specifics to suite your case and you should be fine.

Edit individual radio buttons in zend form view script

I have a zend form which is using a view script. I want to get access to the individual radio button items but I'm not sure how to do so. Right now, I just print them all out using:
echo $this->element->getElement('myRadio');
That prints them all out vertically. I need a little more control. I need to be able to print the first 5 options in one column then the next 5 in a second column.
I have the same issue. There is no nice way to do this that I have found (circa ZF 1.10.8)
Matthew Weier O'Phinney had some advice on this page:
http://framework.zend.com/issues/browse/ZF-2977
But I find that approach cumbersome in practice. The original poster on that ticket had a good idea, and I think they should ultimately incorporate some nice way to do this along those lines.
But since there is no better way at the moment, I just follow Matthew's suggestion for now.
For my form I was working on, to render just one single radio button out of the group, I had to do this:
In my form class:
$radio = new Zend_Form_Element_Radio('MYRADIO');
$radio->addMultiOption('OPTION1', 'Option One')
->addMultiOption('OPTION2', 'Option Two');
$this->addElement($radio);
In my view script, just rendering OPTION1:
echo $this->formRadio(
$this->form->MYRADIO->getFullyQualifiedName(),
$this->form->MYRADIO->getValue(),
null,
array('OPTION1' => $this->form->MYRADIO->getMultiOption('OPTION1'))
);
That will render a <input type="radio" /> element, and an associated <label>. No other decorators will be rendered, which is a pain.
For your case, you will probably want to render your radio elements and other elements using the ViewScript view helper - so you can line all of the elements up amongst your own custom table markup as you described.
Figured this one out too. Just use
$this->element->getElment('myRadio')->getMultiOptions();
and it will return an array of the key/value options.