With laravel 7 /livewire app I make crud using Repository and I got listing of data ok,
In mount event I assign protected var $FacilityRepository , which works ok in render method,
but it is null in edit method and I got error:
Call to a member function getById() on null
when user clicks on “edit link”
<?php
namespace App\Http\Livewire\Admin;
use App\library\CheckValueType;
use App\Settings;
use DB;
use Livewire\Component;
use App\Facility;
use Livewire\WithPagination;
use App\Repositories\Interfaces\FacilityRepositoryInterface;
class Facilities extends Component
{
use WithPagination;
public $form= [
'name'=>'',
'descr'=> '',
'created_at'=> '',
'is_reopen' => false,
];
public $current_facility_id;
public $filter_name= '';
public $updateMode = 'browse';
protected $FacilityRepository;
public function render()
{
$this->facility_rows_count = Facility
::getByName($this->filter_name, true)
->count();
$backend_per_page = Settings::getValue('backend_per_page', CheckValueType::cvtInteger, 20);
\Log::info( 'render -10 $this->FacilityRepository::' . print_r( json_encode($this->FacilityRepository), true ) );
// line above logged as : [2020-09-04 16:46:26] local.INFO: render -10 $this->FacilityRepository::{}
return view('livewire.admin.facilities.container', [
'facilityDataRows' => $this->FacilityRepository->filterWithPagination(
[
'name'=>$this->filter_name,
],
$backend_per_page
),
'facility_rows_count'=> $this->facility_rows_count
]); // this listing is rendered OK
}
public function mount( FacilityRepositoryInterface $FacilityRepository ) {
$this->FacilityRepository = $FacilityRepository;
\Log::info( '-101mount $this->FacilityRepository::' . print_r( json_encode($this->FacilityRepository), true ) );
// line above logged as : [2020-09-04 16:46:26] local.INFO: -101mount $this->FacilityRepository::{}
}
public function edit($id)
{
\Log::info( '-1 edit $id::' . print_r( json_encode( $id ), true ) );
\Log::info( '-1 edit $this->FacilityRepository::' . print_r( $this->FacilityRepository, true ) );
// line above logged as : [2020-09-04 16:46:28] local.INFO: -1 edit $this->FacilityRepository::
// AND ERROR NEXT
$this->form = $this->FacilityRepository->getById($id)->toArray();
\Log::info( '-1023 $this->form ::' . print_r( json_encode( $this->form ), true ) );
$this->current_facility_id = $id;
$this->form['created_at'] = getCFFormattedDateTime($this->form['created_at']);
$this->emit('facility_opened',[ 'mode'=>'edit', 'id'=>$id ]);
$this->updateMode = 'edit';
}
In template edit link is defined as :
#foreach($facilityDataRows as $nextFacilityDataRow)
<tr>
<td class="text-right m-0">
<a wire:click="edit({{$nextFacilityDataRow->id}})"
class="p-1 a_edit_item_{{$nextFacilityDataRow->id}} a_link">
{{$nextFacilityDataRow->id}}
</a>
</td>
...
Why error and how to fix it ?
Modified # 2:
If I make
class Facilities extends Component
{
...
public $FacilityRepository;
}
I got error :
Livewire component's [admin.facilities] public property [FacilityRepository] must be of type: [numeric, string, array, null, or boolean]. Only protected or private properties can be set as other types because JavaScript doesn't need to access them.
I tried to declare method edit as :
public function edit( FacilityRepositoryInterface $facilityRepository, int $id)
{ // Did you mean this ?
...
}
I got error :
Call to a member function filterWithPagination() on null
on method filterWithPagination, which is used in render method, when I show listing of data.
Which way is correct ?
Modified # 3:
If to modify :
public function render(FacilityRepositoryInterface $facilityRepository)
{
I got error :
Declaration of App\Http\Livewire\Admin\Facilities::render(App\Repositories\Interfaces\FacilityRepositoryInterface $facilityRepository) should be compatible with Livewire\Component::render()
?
Modified # 4:
Opening page in mode I have 2 inputs with lazy defined, like
<dd class="horiz_divider_right_23" wire:model="form.title.lazy" x-data="{ name: '{{$form['name']}}'}">
<input
x-model="name"
x-on:blur="$dispatch('name', name)"
id="name"
class="form-control editable_field admin_control_input"
placeholder="Enter descriptive name"
autocomplete=off
>
#error('form.name')
<div class="validation_error">{{ clearValidationError($message,['form.'=>'']) }}</div> #enderror
</dd>
and when I edit some of fields on blur ervent I got the same error :
Call to a member function filterWithPagination() on null
with url in error description :
VM5783:1 POST http://local-hostels3.com/livewire/message/admin.facilities 500 (Internal Server Error)
where http://local-hostels3.com is my local hosting
Have I in some way to overrride message method ?
"laravel/framework": "^7.0",
"livewire/livewire": "^1.3",
Thanks!
protected and private properties DO NOT persist between Livewire updates. In general, you should avoid using them for storing state.
https://laravel-livewire.com/docs/properties/#important-notes
That being said, you can use dependency injection again, just pass whatever you need (FacilityRepositoryInterface in this case) as the first argument(s) of the edit method.
Same applies to the render method, so you can skip mount altogether.
Correction
The last bit in my original answer is wrong, you cannot use DI in the render method.
So for use in render, use the mount method and for use in edit, bring it via the first parameter. If render complains about not having it after usage of edit, save to the protected property inside edit as well.
Final code, that should work
class Facilities extends Component
{
protected $FacilityRepository;
public function mount(FacilityRepositoryInterface $FacilityRepository)
{
$this->FacilityRepository = $FacilityRepository;
}
public function render()
{
// use $this->FacilityRepository->...
}
public function edit(FacilityRepositoryInterface $FacilityRepository, $id)
{
$this->FacilityRepository = $FacilityRepository;
// rest of the edit method from your code
}
}
add boot method
public function boot(FacilityRepositoryInterface $FacilityRepository)
{
$this->FacilityRepository = $FacilityRepository;
}
Related
I'm new to Livewire and I am stuck with this problem.
I've created a table.blade.php component with livewire, and another searchbar.blade.php component, which is not a child of the table component. Every time a search for a term, the table should rerender with the seached parameter.
All is right, and the search query gives the correct result (clients with pagination), but somehow the table does not rerender the html.
Any ideas what I'm doing wrong? Thanks
<div>
<input type="text" wire:model="query" autofocus>
</div>
class SearchBar extends Component
{
public $query;
public function updatedQuery()
{
$this->emit('searchForQuotes', $this->query);
}
public function render()
{
return view('livewire.clients.searchbar');
}
}
<div>
<table>
<tbody>
#foreach($clients as $client)
#livewire('clients.row', ['client' => $client], key($client->id))
#endforeach
</tbody>
</table>
</div>
class Table extends Component
{
use WithPagination;
public $query;
protected $listeners = [
'searchForQuotes' => 'render'
];
public function mount()
{
$this->resetData();
}
public function resetData()
{
$this->query = null;
}
public function render($query = null)
{
$q = Client::query();
if ($query) {
$q->whereRaw("CONCAT(surname, ' ', name) LIKE '%" . $query . "%'");
}
$clients = $q->latest()->paginate(20);
return view('livewire.clients.inc.table', [
'clients' => $clients, 'query' => $query
]);
}
}
You can make your child components reactive by making your key() unique every render of the parent:
#livewire('clients.row', ['client' => $client], key($client->id . "-" . Str::random()))
By adding a Str::random(), the key is different every time the parent updates, which forces the children to update as well. This also works with now(), but only as long as you have a prefix. It is important to note that this causes more requests and thus can make your table slower.
Try something like this :
class Table extends Component
{
use WithPagination;
public $query;
protected $listeners = ['searchForQuotes'];
public function mount()
{
$this->resetData();
}
public function searchForQuotes($query)
{
$this->query = $query;
// Do something
$this->render();
}
public function resetData()
{
$this->query = null;
}
public function render()
{
$q = Client::query();
if ($this->query) {
$q->whereRaw("CONCAT(surname, ' ', name) LIKE '%" . $query . "%'");
}
$clients = $q->latest()->paginate(20);
return view('livewire.clients.inc.table', [
'clients' => $clients, 'query' => $this->query
]);
}
}
I think I found the problem, but don't know how to solve it.
I the table.blade.php component I've got this code.
#foreach($clients as $client)
#livewire('clients.row', ['client' => $client], key($client->id))
#endforeach
It seems like the nested component are not rendering after firing the event.
My environment is Laravel 6.0 with PHP 7.3. I want to show the old search value in the text field. However, the old() method is not working. After searching, the old value of the search disappeared. Why isn't the old value displayed? I researched that in most cases, you can use redirect()->withInput() but I don't want to use redirect(). I would prefer to use the view(). method
Controller
class ClientController extends Controller
{
public function index()
{
$clients = Client::orderBy('id', 'asc')->paginate(Client::PAGINATE_NUMBER);
return view('auth.client.index', compact('clients'));
}
public function search()
{
$clientID = $request->input('clientID');
$status = $request->input('status');
$nameKana = $request->input('nameKana');
$registerStartDate = $request->input('registerStartDate');
$registerEndDate = $request->input('registerEndDate');
$query = Client::query();
if (isset($clientID)) {
$query->where('id', $clientID);
}
if ($status != "default") {
$query->where('status', (int) $status);
}
if (isset($nameKana)) {
$query->where('nameKana', 'LIKE', '%'.$nameKana.'%');
}
if (isset($registerStartDate)) {
$query->whereDate('registerDate', '>=', $registerStartDate);
}
if (isset($registerEndDate)) {
$query->whereDate('registerDate', '<=', $registerEndDate);
}
$clients = $query->paginate(Client::PAGINATE_NUMBER);
return view('auth.client.index', compact('clients'));
}
}
Routes
Route::get('/', 'ClientController#index')->name('client.index');
Route::get('/search', 'ClientController#search')->name('client.search');
You just need to pass the variables back to the view:
In Controller:
public function search(Request $request){
$clientID = $request->input('clientID');
$status = $request->input('status');
$nameKana = $request->input('nameKana');
$registerStartDate = $request->input('registerStartDate');
$registerEndDate = $request->input('registerEndDate');
...
return view('auth.client.index', compact('clients', 'clientID', 'status', 'nameKana', 'registerStartDate', 'registerEndDate'));
}
Then, in your index, just do an isset() check on the variables:
In index.blade.php:
<input name="clientID" value="{{ isset($clientID) ? $clientID : '' }}"/>
<input name="status" value="{{ isset($status) ? $status : '' }}"/>
<input name="nameKana" value="{{ isset($nameKana) ? $nameKana : '' }}"/>
...
Since you're returning the same view in both functions, but only passing the variables on one of them, you need to use isset() to ensure the variables exist before trying to use them as the value() attribute on your inputs.
Also, make sure you have Request $request in your method, public function search(Request $request){ ... } (see above) so that $request->input() is accessible.
Change the way you load your view and pass in the array as argument.
// Example:
// Create a newarray with new and old data
$dataSet = array (
'clients' => $query->paginate(Client::PAGINATE_NUMBER),
// OLD DATA
'clientID' => $clientID,
'status' => $status,
'nameKana' => $nameKana,
'registerStartDate' => $registerStartDate,
'registerEndDate' => $registerEndDate
);
// sent dataset
return view('auth.client.index', $dataSet);
Then you can access them in your view as variables $registerStartDate but better to check if it exists first using the isset() method.
example <input type='text' value='#if(isset($registerStartDate)) {{registerStartDate}} #endif />
I am trying to validate a multiple select field that have jquery chosen applied.
Validation is working perfectly but only problem is that validation message is not showing below the input field.
Here is my files.
profile_edit.ctp
<?php echo $this->Form->create($user,['action' => '', 'role'=>"form",'novalidate'=>true,'method'=>'post','id'=>'ProfileForm','templates'=>['label'=>false,'inputContainer'=>'{{content}}']]); ?>
<?php echo $this->Form->control('user_grades[].grade_id',['multiple','hiddenField'=>false, 'escape'=>false, 'type'=>'select', 'id'=>'sp_grade', 'options'=>App\Model\Table\GradesTable::getGrades('list'),'class'=>'form-control chosen-select']); ?>
<button type="submit" class="btn footer_btns float-left">Save</button>
<?php echo $this->Form->end(); ?>
MyAccountController.php
<?php
public function profileEdit(){
$user = $this->Users->get($this->Auth->user('id'), ['contain'=>['UserGrades']]);
if($this->request->is(['put','post'])){
$data = $this->request->getData();
if(isset($data['user_grades']) && !empty($data['user_grades'])) {
$this->UserGrades->deleteAll(['user_id' => $this->Auth->user('id')]);
}
if(null == $this->request->getData('user_grades')){
$this->request = $this->request->withData('user_grades.0.grade_id','');
}
$user = $this->Users->patchEntity($user, $this->request->getData(), [
'validate' => 'editProfileSection',
'associated' => [
'UserGrades' => ['validate'=> 'editProfileSection']
]
]);
if(empty($user->getErrors())){
if ($this->Users->save($user)) {
$this->Flash->success(__('Succesfully updated <strong>'.$user->full_name .'</strong> Information||Success'));
return $this->redirect(['action' => '']);
}
}
$this->Flash->error(__('Please check your inputs and try again.||Action Failed!'));
}
$this->set(compact('user'));
}
UserGradesTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\TableRegistry;
use Cake\Event\Event;
use Cake\ORM\RulesChecker;
use Cake\Validation\Validator;
class UserGradesTable extends Table {
public function initialize(array $config) {
$this->addBehavior('Timestamp');
$this->addBehavior('Trim');
}
public function validationEditProfileSection(Validator $validator) {
$validator
->notEmpty('grade_id',"Please select at least one grade.");
return $validator;
}
}
I have tried to get error message and got following:
Array
(
[user_grades] => Array
(
[0] => Array
(
[grade_id] => Array
(
[_empty] => Please select at least one grade.
)
)
)
)
But this error is not showing below the input field. Any help will be appreciated.
You are not using the correct naming scheme for the form control, you cannot use [], if you want the form helper magic to work, then you must supply the actual index, ie:
user_grades.0.grade_id
See also Cookbook > Views > Helpers > Form > Creating Inputs for Associated Data
I'm working on testing a shopping cart, checkout, payment process on Zend Framework with phpunit. I'm testing ShoppingCartController by adding products to cart, a ShoppingCart Model handles product additions by storing product id's in a Zend Session Namespace, and then in another test I want to test that the products were added. The same ShoppingCart Model retrieves a list of added products from the same Zend Session namespace variable.
The add product test looks like this and works well, and the var_dump($_SESSION) was added to debug and shows the products correctly:
public function testCanAddProductsToShoppingCart() {
$testProducts = array(
array(
"product_id" => "1",
"product_quantity" => "5"
),
array(
"product_id" => "1",
"product_quantity" => "3"
),
array(
"product_id" => "2",
"product_quantity" => "1"
)
);
Ecommerce_Model_Shoppingcart::clean();
foreach ($testProducts as $product) {
$this->request->setMethod('POST')
->setPost(array(
'product_id' => $product["product_id"],
'quantity' => $product["product_quantity"]
));
$this->dispatch($this->getRouteUrl("add_to_shopping_cart"));
$this->assertResponseCode('200');
}
$products = Ecommerce_Model_Shoppingcart::getData();
$this->assertTrue($products[2][0]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][0]["quantity"],
"8");
$this->assertTrue($products[2][1]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][1]["quantity"],
"1");
var_dump($_SESSION);
}
The second test attempts to retrieve the products by asking the model to do so, the var_dump($_SESSION) is null already at the beginning of the test. The session variables were reset, I want to find a way to preserve them, can anyone help?
public function testCanDisplayShoppingCartWidget() {
var_dump($_SESSION);
$this->dispatch($this->getRouteUrl("view_shopping_mini_cart"));
$this->assertResponseCode('200');
}
Sorry for pointing you in the wrong direction. Here is a way better way of achieving this, suggested by ashawley from #phpunit channel of irc.freenode.net:
<?php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
protected $backupGlobalsBlacklist = array( '_SESSION' );
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
?>
== END UPDATE
In function tearDown(): copy $_SESSION to a class attribute and
In function setUp(): copy the class attribute to $_SESSION
For example, this test fails when you remove the functions setUp() and tearDown() methods:
<?php
# Usage: save this to test.php and run phpunit test.php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
public static $shared_session = array( );
public function setUp() {
$_SESSION = FooTest::$shared_session;
}
public function tearDown() {
FooTest::$shared_session = $_SESSION;
}
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
Also there is a backupGlobals feature but it doesn't work for me. You should try it thought, maybe it works on stable PHPUnit.
that's a very ugly of doing that. The right way should be using dependency injection.
That implies changing your source code to use this class instead of sessions directly:
class Session
{
private $adapter;
public static function init(SessionAdapter $adapter)
{
self::$adapter = $adapter;
}
public static function get($var)
{
return self::$adapter->get($var);
}
public static function set($var, $value)
{
return self::$adapter->set($var, $value);
}
}
interface SessionAdapter
{
public function get($var);
public function set($var, $value);
}
Additional information:
http://community.sitepoint.com/t/phpunit-testing-cookies-and-sessions/36557/2
Using PHPUnit to test cookies and sessions, how?
You can also just create a random session id for your PHPUnit test, and then make sure you pass this session id in a cookie in every further call you make. With Curl, you would use the CURLOPT_COOKIE option and set it to 'PHPSESSID=thesessionidofyourunittest' as such:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=thesessionidofyourunittest');
I explained more in detail and with an example in this stackoverflow answer.
I am trying to make a single page of website but i cant passed argument article in view
my controller is:
public function single(Article $article)
{
$article->increment('viewCount');
$comments = $article->comments()->where('approved' , 1)->where('parent_id', 0)->latest()->with(['comments' => function($query) {$query->where('approved' , 1)->latest();}])->get();
return view('Home.articles.single' , compact('article' , 'comments'));
}
and my view is
<div class="subject_head">
<div class="subject_head--title"><h1 class="title">
{{$article->title}}
</h1>
</div>
</div>
but i cant passed article title. and my model is
protected $table='articles';
protected $casts= [
'images'=>'array'
];
protected $fillable= ['title','slug','description','body','images','tags'];
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
public function user()
{
return $this->belongsTo(User::class);
}
i passed this and while used
$article->all()
this passed all data
but when use
$article->title
this method is null
and when use
dd($article)
It has been changed.
Now you need to pass it as array ['name' => 'James']
in your case
public function single(Article $article)
{
$article->increment('viewCount');
$comments = $article->comments()->where('approved' , 1)->where('parent_id', 0)->latest()->with(['comments' => function($query) {$query->where('approved' , 1)->latest();}])->get();
return view('Home.articles.single' ,['article'=>$article,'comments'=$comments]));
}
Check it here : https://laravel.com/docs/7.x/views
i changed Route of
Route::get('/articles/{articleSlug}' , 'ArticleController#single');
Route::get('/series/{courseSlug}' , 'CourseController#single');
to
Route::get('/articles/{article:Slug}' , 'ArticleController#single');
Route::get('/series/{course:Slug}' , 'CourseController#single');
and solved problem.
thank you of all.