CakePHP 3 hasMany will not pass parent ID to associated children - cakephp-3.0

/* ShootsTable.php Meta Table */
public function initialize(array $config)
{
$this->table('shoots');
$this->displayField('title');
$this->primaryKey('id');
$this->hasMany('ShootMeta');
}
/* ShootMetaTable.php Meta Table */
public function initialize(array $config)
{
$this->table('shoot_meta');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Shoots');
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['shoots_id'], 'Shoots'));
return $rules;
}
/* Shoots.php Controller */
public function add()
{
$shoot = $this->Shoots->newEntity(null);
if ($this->request->is('post')) {
$this->Shoots->patchEntity($shoot, $this->request->data,[
'associated' => ['ShootMeta']
]);
$shoot->set('created_by', 1);
debug($shoot);
if ($this->Shoots->save($shoot,['associated' => ['ShootMeta']])) {
$this->Flash->success('The shoot has been saved.');
// return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The shoot could not be saved. Please, try again.');
}
}
$this->set(compact('shoot'));
$this->set('_serialize', ['shoot']);
}
/* Add.ctp Template */
<div class="shoots form large-10 medium-9 columns">
<?= $this->Form->create($shoot); ?>
<fieldset>
<legend><?= __('Add Shoot') ?></legend>
<?php
echo $this->Form->input('title');
echo $this->Form->input('content');
echo $this->Form->input('datetime', ['label' => 'Date/Time Of Shoot']);
echo $this->Form->input('shoot_meta.0.meta_key', ['type' => 'hidden', 'value' => 'photographer_spaces']);
echo $this->Form->input('shoot_meta.0.meta_value',['label' => 'Photographer Spaces', 'type' => 'number']);
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>
/* debug($shoots) output */
object(App\Model\Entity\Shoot) {
'new' => true,
'accessible' => [
'created_by' => true,
'title' => true,
'content' => true,
'datetime' => true,
'shoot_meta' => true
],
'properties' => [
'title' => '123',
'content' => '123',
'datetime' => object(Cake\I18n\Time) {
'time' => '2015-03-19T07:04:00+0000',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => true,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true,
'shoot' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123'
],
'dirty' => [
'meta_key' => true,
'meta_value' => true
],
'original' => [],
'virtual' => [],
'errors' => [
'shoots_id' => [
'_required' => 'This field is required'
]
],
'repository' => 'ShootMeta'
}
],
'created_by' => (int) 1
],
'dirty' => [
'title' => true,
'content' => true,
'datetime' => true,
'shoot_meta' => true,
'created_by' => true
],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'Shoots'
}
As you can see, the field shoots_id is required, which I would have thought would be automatically passed down (although at this point it hasn't executed any MySQL).
I feel I may have gone about this the wrong way but have spent 2 full days trying to get it right. One of those days was me trying to work out why after baking it had named a lot of the references to ShootMeta to ShootMetum, I thought it had actually corrupted it.
One of the biggest issues I have is knowing where to use shoot_meta, ShootMeta, shootmeta, shootmetum, ShootMetum etc. It feels like a bit of a minefield!
/Update
A dump of the save object below. It is clearly assigning it, it just seems to not be executing it in the SQL?
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => false,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123',
'shoot_id' => '2',
'id' => '3'
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'ShootMeta'
},

Found it.
It is referring to shoot_id when i debug the save
'shoot_meta' => [
(int) 0 => object(App\Model\Entity\ShootMetum) {
'new' => false,
'accessible' => [
'shoots_id' => true,
'meta_key' => true,
'meta_value' => true
],
'properties' => [
'meta_key' => 'photographer_spaces',
'meta_value' => '123',
'shoot_id' => '2',
'id' => '3'
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
'repository' => 'ShootMeta'
},
for some reason it was using the singular name for the association. Changed in the Shoots.php model.
From
$this->hasMany('ShootMeta');
To
$this->hasMany('ShootMeta',[
'foreignKey' => 'shoots_id'
]);

Remove the validation rule for shoots_id. Validation is for data that is posted from the form, and in this case the foreignKey cannot be posted from the Form. You already have rules in your buildRules() method for making sure that value is passed before saving, so removing the validation is 100% safe.

i have same problem like this to, for now my solution is sending associated data to other function/methode and save it.
eg
**
public function add() {
$kantor = $this->Kantor->newEntity($this->request->data);
if ($this->request->is('post')) {
$kantor = $this->Kantor->patchEntity($kantor, $this->request->data);
$rgndata = $this->request->data['Telpkantor'];
$this->request->session()->write('rgndata', $rgndata);
if ($this->Kantor->save($kantor)) {
$result = $this->Kantor->save($kantor);
$this->addTelpKantor($rgndata, $result->id);
$this->Flash->success('The kantor has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The kantor could not be saved. Please, try again.');
}
}
$reffKota = $this->Kantor->ReffKota->find('list', ['limit' => 200]);
$statusKantor = $this->Kantor->StatusKantor->find('list', ['limit' => 200]);
$pimpinan = $this->Kantor->Pimpinan->find('list', ['limit' => 200]);
$jenisTelp = $this->Kantor->Telpkantor->Jenistelp->find('list', ['limit' => 200]);
$this->set(compact('kantor', 'reffKota', 'statusKantor', 'pimpinan', 'jenisTelp'));
$this->set('_serialize', ['kantor']);
}
public function addTelpKantor($rgndata = null, $kantor_id=null) {
if (!empty($rgndata[0]['noTelp'])) {
$this->loadModel('Telpkantor');
foreach ($rgndata as $rgndata) {
$rgndata['kantor_id'] =$kantor_id;
$rgndatasave = $this->Telpkantor->newEntity($rgndata);
$this->Telpkantor->save($rgndatasave);
}
}
}
**

Related

Kartik Select2 error when try to use it with ajax

I've used kartiks select2 many time with ajax before. Now when I tried to implement it again this error is thrown in console:
jquery.js:3850 Uncaught Error: Option 'ajax' is not allowed for Select2 when attached to a <select> element.
There is no other errors. My implementation looks like:
<?php
echo $form->field($model, 'destination_from')->widget(Select2::class, [
'initValueText' => isset($_GET['MassManagementSearch']) ? $_GET['MassManagementSearch']['destination_from'] : [], // set the initial display text
'options' => [
'placeholder' => 'Destination from ...',
'multiple' => true
],
'pluginOptions' => [
'allowClear' => true,
'minimumInputLength' => 3,
'language' => [
'errorLoading' => new JsExpression("function () { return 'Waiting for results...'; }"),
],
'ajax' => [
'url' => \yii\helpers\Url::to(['destination-from']),
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }')
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(city) { return city.text; }'),
'templateSelection' => new JsExpression('function (city) { return city.text; }'),
],
]); ?>
EDIT This my select2 version in composer.json:
"kartik-v/yii2-widget-select2": "#dev"

Relation between belongsTo and hasMany in cakephp 3.4.9

I need to save user and invoice data at the same time. When I am submitting the form, the user data is saving, but the invoice table isn't. When I print the request data, I get the following:
object(Cake\ORM\Entity) {
'u_firstname' => 'John',
'u_lastname' => 'den',
'u_email' => 'john#man.com',
'u_phone' => '123',
'password' => '123',
'membership_id' => (int) 1,
'Invoices' => [
'inv_membership_name' => 'basic1',
'inv_membership_cost' => '55',
'inv_purchase_date' => '2017-07-11'
],
'id' => (int) 19,
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Users'
}
I am not able to save the Invoice part. I define InvoicesTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('invoices');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
}
and UsersTable like this:
$this->hasMany('Invoices', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
I use the following code to save data:
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data(), [
'associated' => [
'Invoices'
]
]);
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
}else{
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
Please suggest what I'm missing. Or how I should debug.
When you print request data invoices array should be something like this:
object(Cake\ORM\Entity) {
'invoices' => [
[0] => Array
(
['inv_membership_name'] => 'basic1',
['inv_membership_cost'] => '55',
['inv_purchase_date'] => '2017-07-11'
),
[1] => array('...') // if you have multiple records
],
For that change input name to something like this in your form:
invoices[]['inv_membership_name']
invoices[]['inv_membership_cost']
invoices[]['inv_purchase_date']
See here Saving HasMany Associations in CakePhp3.
You are close to the solution but you have the naming conventions wrong in the form. The correct name would be invoices.0.inv_membership_name
invoices.1.inv_membership_name etc. I included the creating inputs for associated data link from the cookbook.
echo $this->Form->control('invoices.0.inv_membership_name');
https://book.cakephp.org/3.0/en/views/helpers/form.html#creating-inputs-for-associated-data

Setting empty text in kartik dynagrid

Am using kartik dynagrid and i would like to setup a text that shows when the dataprovider returns empty
The grid
echo DynaGrid::widget([
'columns' => $columns,
'showPersonalize' => true,
'emptyText'=>'Sorry all pr have pritems',///-----------------This is what i had set
'options' => ['id' => 'assignsolic-grid'],
'gridOptions' => [
'options' => ['id' => 'grid'],
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'showPageSummary'=>false,
'pager' => [
'firstPageLabel' => 'First',
'lastPageLabel' => 'Last',
'maxButtonCount' => 10,
],
'panel' => [
'type' => GridView::TYPE_PRIMARY,
// 'heading' => '<h3 class="panel-title"><i class="glyphicon glyphicon-book"></i> </h3>',
'before'=>'<i>Select the Prs to assign solicitation and then click the Assign Solicitation button</i>',
'after' =>
Html::button(' <i class=" glyphicon glyphicon-road "></i> Assign Solicitation ', ['value' => Url::to('assignsolc'),'class' => 'btn btn-danger', 'id' => 'assignsolic']),
'footer' => false
],
'toolbar' => [
['content' => '{dynagridFilter}{dynagridSort}{dynagrid}'],
'{export}',
'{toggleData}'
],
'pjax' => true,
'bordered' => false,
'striped' => true,
'condensed' => true,
'responsive' => true,
'responsiveWrap' => false,
'containerOptions'=>['style'=>'overflow:scroll'],
]
]) ;
This returs an error of Setting unknown property: kartik\dynagrid\DynaGrid::emptyText how can i set the empty text
You can define the value for null display directly in confi/main.php formatter component
'components' => [
.......
'formatter' => [
'class' => 'yii\i18n\Formatter',
'dateFormat' => 'dd.MM.yyyy',
'decimalSeparator' => ',',
'thousandSeparator' => ' ',
'currencyCode' => 'EUR',
'nullDisplay' => '', // **** this param
],
.....
Otherwise if the widget don't provide a proper attribute you can use an anonymous function for value
[
'attribute' => 'your_attribute',
'value' => function ($model) {
if ( $model->your_attribute == NULL) {
return 'Sorry all pr have pritems';
} else {
return $model->your_attribute;
}
},
],
Since am using $dataProvider i found out that i just have to check if the dataProvider is empty by
if (!$dataProvider->totalCount > 0) { pass in message to display }
else{?>
SHOW THE GRID HERE
<?php
}
?>
?>

Yii2: Select2, How to set initValueText in Gridview or Tabularform?

I need to set initValueText for Select2, which is in loop, like gridview or Tabularform. but I don't know how to set right value for each.
<?= TabularForm::widget([
'dataProvider' => $dataProvider,
'form' => $form,
'actionColumn' => false,
'checkboxColumn' => false,
'attributeDefaults' => [
'type' => TabularForm::INPUT_RAW,
],
'attributes' => [
'test' => [
'type' => Form::INPUT_WIDGET,
'widgetClass' => Select2::className(),
'options' => [
'name' => 'test',
'options' => [
'class' => 'test-to-select',
],
'pluginOptions' => [
'allowClear' => true,
'minimumResultsForSearch' => 'Infinity',
'ajax' => [
'url' => Url::to(['/test/get-list']),
'dataType' => 'json',
'data' => new JsExpression('function(term,page) {
return {term : term.term};
}'),
'results' => new JsExpression('function(data,page) {
return {results:data.results};
}'),
'cache' => true
]
],
'initValueText' => 'Selected Text' /// how can I set this in gridview or Tabularform?
],
],
]
]) ?>
Of course this is not working,
'initValueText' => function($model){
retur $model->textValue;
}
Any help would be appreciated.
Into tabularform if you want dynamic initValueText, you can use options closure in this manner:
'test' => [
'type' => Form::INPUT_WIDGET,
'widgetClass' => Select2::className(),
'options' => function($model, $key, $index, $widget) {
$initValueText = empty($model['textValue']) ? '' : $model['textValue'];
return [
'name' => 'test',
'options' => [
'class' => 'test-to-select',
],
'initValueText' => $initValueText,
'pluginOptions' => [
'allowClear' => true,
'minimumResultsForSearch' => 'Infinity',
'ajax' => [
'url' => Url::to(['/test/get-list']),
'dataType' => 'json',
'data' => new JsExpression('function(term,page) {
return {term : term.term};
}'),
'results' => new JsExpression('function(data,page) {
return {results:data.results};
}'),
'cache' => true
]
],
];
}
],
If for example attribute for city, so try this..
$cityDesc = empty($model->city) ? '' : City::findOne($model->city)->description;
'initValueText' => $cityDesc, // set the initial display text
For init value first assign value to $model attribute, if you should not assign so this attribute can take value.
Set the data parameter with the array including the option you want to show. For example for cities:
'options' => [
'data' => \yii\helpers\ArrayHelper::map(\app\models\City::find()->orderBy('id')->asArray()->all(), 'id', 'name'),
]
try to put inside the options instead ouside
'widgetClass' => Select2::className(),
'options' => [
'initValueText' => 'Selected Text'
Don't use isset it return error if more the one filter are use there.
[
'attribute' => 'ad_partner_id',
'value' => function ($model, $key, $index, $widget) {
return $model->partner->name;
},
'filter' => Select2::widget([
'model' => $searchModel,
'initValueText' => !empty($searchModel->ad_partner_id) ? $searchModel->partner->name : "",
'attribute' => 'ad_partner_id',
'options' => ['placeholder' => Yii::t('app', 'Search Partner ...')],
'pluginOptions' => ['allowClear' => true, 'autocomplete' => true,
'ajax' => ['url' => Url::base() . '/partner/get-partners',
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }'),
],
],
]),
],

Yii2 kartik typeahead - get number of suggestions

I'm using kartik's typeahead widget for Yii2 in a view:
echo \kartik\typeahead\Typeahead::widget([
'name' => 'serial_product',
'options' => [
'placeholder' => 'Filter as you type ...',
'autofocus' => "autofocus"
],
'scrollable' => TRUE,
'pluginOptions' => [
'highlight' => TRUE,
'minLength' => 3
],
'dataset' => [
[
'remote' => Url::to(['transfers/ajaxgetinventoryitemsnew']) . '?search=%QUERY',
'limit' => 10
]
],
'pluginEvents' => [
"typeahead:selected" => "function(obj, item) { add_item(item.id); return false;}",
],
]);
How can i get the number of loaded suggestions after the remote dataset is retrieved to execute a javascript function like:
displaynumber(NUMBEROFSUGGESTIONS);
After checking through the source of kartiks widget i came up with the following solution:
echo \kartik\typeahead\Typeahead::widget([
'name' => 'serial_product',
'options' => [
'placeholder' => 'Filter as you type ...',
'autofocus' => "autofocus",
'id' => 'serial_product'
],
'scrollable' => TRUE,
'pluginOptions' => [
'highlight' => TRUE,
'minLength' => 3
],
'dataset' => [
[
'remote' => [
'url' => Url::to(['transfers/ajaxgetinventoryitemsnew']) . '?search=%QUERY',
'ajax' => ['complete' => new JsExpression("function(response)
{
jQuery('#serial_product').removeClass('loading');
checkresult(response.responseText);
}")]
],
'limit' => 10
]
],
'pluginEvents' => [
"typeahead:selected" => "function(obj, item) { checkresult2(item); return false;}",
],
]);
where response.responseText is containing the response from server (json).
function checkresult(response) {
var arr = $.parseJSON(response);
console.log(arr.length);
}
With this function i can get then count of suggestions delivered from server.