Laravel livewire wire:model with array - laravel

From blade component I can give valule product_id
<input wire:model="qty.{{$row->product_id}}" value="{{$row->qty}}" max="{{$row->stock}}">
but how I can display qty from database and when increase qty then wire model will work and updated, what I can should do???
public $qty;
public function render()
{
$this->userId=Auth::id();
if ($this->qty!=null){
foreach($this->qty as $key => $qty)
{
$cart=Cart::where('user_id',$this->userId)->where('product_id',$key)->first();
if ($cart){
$cart->update([
'qty' => $qty,
]);
$this->emit('refreshCart');
}
}
}
}

in your blade
<input wire:model="inputs.{{ $loop->index }}.qty" value="{{$row->qty}}" max="{{$row->stock}}">
<a wire:click.prevent="increase({{$loop->index}})"></a>
<a wire:click.prevent="reduce({{$loop->index}})"></a>
in your livewire component
public $inputs = [];
public function mount()
{
foreach (Cart::where('user_id',$this->userId)->get() as $item) {
array_push($this->inputs, [
"id" => $item->id,
"qty" => $item->quantity,
]);
}
}
public function reduce($index)
{
$product_id = $this->inputs[$index]['id'];
$this->inputs[$index]['qty'] -= 1;
$this->updateCart($product_id, $this->inputs[$index]['qty']);
}
public function increase($index)
{
$product_id = $this->inputs[$index]['id'];
$this->inputs[$index]['qty'] += 1;
$this->updateCart($product_id, $this->inputs[$index]['qty']);
}
public function updateCart($product_id, $productQty)
{
//update yourcart here with productID and qty
}
This code works for me.

Related

How to re-render a table with Livewire after an event emitted from another component

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.

Livewire choicesjs component with AplineJS returns Cannot redefine property: $nextTick

I try to get working choicesjs with livewire but I get on the way a lot problems. For now what I try
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class ChoicesLivewire extends Component
{
/**
* #var array
*/
public array $options;
/**
* #var array
*/
public array $selectedOptions;
protected $listeners = [
'reloaded'
];
public function mount()
{
$collection = [
1 => 'Architektur',
2 => 'Bauwirtschaft',
3 => 'Straßenbau',
4 => 'Tiefbau',
];
$this->options = $collection;
$this->selectedOptions = [2];
}
public function updatedOptions()
{
//dump($this->selectedOptions);
}
public function render()
{
$this->dispatchBrowserEvent('reloaded', ['currentSelects' => $this->selectedOptions]);
return view('livewire.choices-livewire');
}
}
than the blade
<div>
<select wire:model="options" x-ref="multiple" x-data="livewareChoices" multiple>
#foreach($options as $key => $option)
<option value="{{$key}}" {{ in_array($key, $selectedOptions) ? 'selected' : '' }}>{{$option}}</option>
#endforeach
</select>
#dump($selectedOptions)
</div>
and my custom js file
import Choices from "choices.js";
window.choices = Choices;
const livewareChoices = {
init() {
if (!typeof Choices === 'function' || !this.$refs.multiple) return;
const self = this
//console.log(this.$wire.livewareChoices)
choices = new Choices(this.$refs.multiple, {
itemSelectText: '',
removeItems: true,
//allowHTML: true,
removeItemButton: true,
});
/*choices.passedElement.element.addEventListener(
'change',
function (event) {
console.log(self.$refs.$attributes)
//self.$wire.set('livewareChoices', event.target.value)
})*/
}
}
window.addEventListener('livewire:load', function () {
window.livewareChoices = livewareChoices;
})
on request choices is getting rendered correctly as I try to select some values choicesjs will break with the following error Uncaught TypeError: Cannot redefine property: $nextTick. What is wrong in this case?

I am trying to update data without page reload using live wire

Hi I am making a Private chat Application In Livewire but the Thing is When i Insert a message don't show Unless I reload the Page please help me how can i resolved that ? thank u.
I am using Livewire Full-Page Components here is the Code
Note :- I am using this render function inside viewMessage function but data is not updated without page relaod.
this will load the all the conservation message
$this->render();
app\Http\Livewire\Messaging.php
class Messaging extends Component
{
public $body;
public $searchTerm;
public $selectedUser;
public function mount(){
$this->selectedUser =User::where('id','!=',Auth::user()->id)
->first();
}
public function render()
{
$searchTerm = '%'.$this->searchTerm.'%';
if($searchTerm){
$user= User::where('id', '!=', Auth::user()->id)
->where('user_type',1)
->where('email', 'like', $searchTerm)
->with('messages')
->get();
}
$conservation = Message::query()
->where('sender_id', Auth::user()->id)
->where('receiver_id', $this->selectedUser->id)
->orWhere('receiver_id', Auth::user()->id)
->where('sender_id', $this->selectedUser->id)
->with('sender')
->with('receiver')
->get();
return view('livewire.messaging',[
'users' => $user,
'conservation' =>$conservation
]);
}
public function viewMessages($userId){
$this->selectedUser = User::findorFail($userId);
$senderExist = Message::where('sender_id',$this->selectedUser->id)->exists();
if($senderExist){
$message = Message::where('sender_id',$this->selectedUser->id)->orderBy('receiver_id','Desc')->get();
foreach($message as $value){
$value->notification = "0";
$value->save();
}
}
}
public function sendMessages(){
Message::create([
'receiver_id' => $this->selectedUser->id,
'sender_id' => Auth::user()->id,
'body' => $this->body,
]);
$this->reset('body');
$this->viewMessages($this->selectedUser->id);
$this->render(); //this will load the all the conservation message
}
}
resources\views\livewire\messaging.blade.php
<form wire:submit.prevent="sendMessages" action="#" >
<div class="position-absolute bottom-0 col-md-12 pe-3">
<div class="input-group comment-box p-3">
<input wire:model.defer="body" type="text" class="form-control"
placeholder="Type Message ..." aria-label="Aa" aria-describedby="button-addon2" required>
<button class="btn btn-outline-secondary py-0 px-3" type="submit" id="button-addon2"><i class='bx bx-send fs-4'></i></button>
</div>
</div>
</form>
I personally would not suggest loading in data on every single render. Livewire is able to hydrate previously fetched models and collections, which takes away the strain of reloading it from your database each render. I personally would write your component as such:
use Illuminate\Support\Collection;
class Messaging extends Component
{
public $body = '';
public $searchTerm = '';
public $selectedUser;
// Assuming you're using PHP 7.4 or above, else remove typehint
public Collection $users;
public Collection $conversation;
// Ensure we validate the data passed
protected $rules = [
'body' => ['required', 'string'],
];
public function mount()
{
$this->selectedUser = User::where('id','!=',Auth::user()->id)->first();
$this->getUsers();
$this->getConversation();
}
public function render()
{
return view('livewire.messaging');
}
public function updated($field)
{
// Only update the users if the search term has changed
if ($field === 'searchTerm') {
$this->getUsers():
}
}
public function viewMessages($userId)
{
$this->selectedUser = User::findorFail($userId);
$senderExist = Message::where('sender_id',$this->selectedUser->id)->exists();
if($senderExist) {
$messages = Message::where('sender_id',$this->selectedUser->id)->orderBy('receiver_id','desc')->get();
foreach($messages as $message){
$message->notification = "0";
$message->save();
}
// Are you saving the messages one by one to trigger model events? If not, use below query to instantly update all of them at once in the database:
// Message::where('sender_id',$this->selectedUser->id)->orderBy('receiver_id','desc')->update([
// 'notification' => 0,
// ]);
}
}
public function sendMessages()
{
// Validate the data as per given rules
$this->validate();
Message::create([
'receiver_id' => $this->selectedUser->id,
'sender_id' => Auth::user()->id,
'body' => $this->body,
]);
$this->reset('body');
$this->viewMessages($this->selectedUser->id);
// Refresh conversation
$this->getConversation();
}
public function getConversation()
{
$this->converstaion = Message::query()
->where('sender_id', Auth::user()->id)
->where('receiver_id', $this->selectedUser->id)
->orWhere('receiver_id', Auth::user()->id)
->where('sender_id', $this->selectedUser->id)
->with('sender')
->with('receiver')
->get();
}
public function getUsers()
{
$query = User::where('id', '!=', Auth::user()->id)
->where('user_type', 1)
->with('messages');
if (! empty($this->searchTerm)) {
$searchTerm = '%'.$this->searchTerm.'%';
$query->where('email', 'like', $searchTerm);
}
$this->users = $query->get();
}
}
The render method gets called by Livewire itself each time something happens, such as a value change of a variable, or after a function was called. You should not call it yourself.
P.S. For Livewire to fully keep track of what is what, I suggest adding wire:key to data rows. For example, on a div that is holding the data to your conversation messages, add wire:key="message-{{$message->id}}". This way, if a data change happens, Livewire knows exactly what date should be changed. Read more about it here
You should use the livewire emite event
https://laravel-livewire.com/docs/2.x/events

Yii2 Validate multiple models

I have two models in Yii2 (masterTransaction and splitTransaction), where each masterTransactions can have multiple splitTransactions. Each splitTransaction has an attribute 'amount'. My problem is I need to validate if the sum over all 'amount' attributes is 0.
My first solution was to make another model called Transaction, in which I had an attribute where I saved an instance of the masterTransaction model and another attribute with an array of splitTransaction instances. I did the validation with a custom inline validatior, which work perfectly.
Transaction model
class Transaction extends Model
{
public $masterTransaction;
public $splitTransactions;
public function init()
{
$this->masterTransaction = new MasterTransaction();
$this->splitTransactions[] = new SplitTransaction();
}
public function rules()
{
return [
['splitTransactions', 'validateSplitTransactions'],
];
}
public function validateSplitTransactions($attribute, $params)
{
$sum = 0;
foreach ($this->$attribute as $transaction) {
$sum = bcadd($sum, $transaction->amount, 3);
}
if ($sum != 0) {
$this->addError($attribute, 'The sum of the entries has to be 0');
}
}
public function save()
{
$this->masterTransaction->save();
foreach ($this->splitTransactions as $splitTransaction) {
$splitTransaction->master_transaction_id = $this->masterTransaction->id;
$splitTransaction->save();
}
}
}
Controller function to create the model
public function actionCreate()
{
$transaction = new Transaction();
$count = count(Yii::$app->request->post('SplitTransaction', []));
for ($i = 1; $i < $count; $i++) {
$transaction->splitTransactions[] = new SplitTransaction();
}
if ($transaction->masterTransaction->load(Yii::$app->request->post()) && Model::loadMultiple($transaction->splitTransactions, Yii::$app->request->post())) {
$transaction->masterTransaction->user_id = Yii::$app->user->id;
foreach ($transaction->splitTransactions as $splitTransaction) {
$splitTransaction->user_id = Yii::$app->user->id;
}
if ($transaction->validate()) {
$transaction->save();
}
}
return $this->render('create', [
'transaction' => $transaction,
]);
}
But when I tried building a form to input the data, I ran into a problem with the Ajax validation. The validation would work, but Yii didn't know where to put the error message, so it just deleted it.
I suspect that this is just not the preferred way in Yii2 model my data, but I don't really have another idea. Maybe someone has some ideas for me.
Option 1.
It depends on your view file codes. Does your form contains "splitTransactions" variable? If not, you can put it like this
<?= $form->field($model, 'splitTransactions')->hiddenInput(['maxlength' => true])->label(false); ?>
The variable will be hidden, but still show errors. In some case validation will not be fired because of empty value of "splitTransactions" variable.
"splitTransactions" should contain some value to fire validation. You can put some value to if before pasting the form like this
$model->splitTransactions=1;
Option 2.
You can add error to other variable (which form contains) like this
public function validateSplitTransactions($attribute, $params)
{
$sum = 0;
foreach ($this->$attribute as $transaction) {
$sum = bcadd($sum, $transaction->amount, 3);
}
if ($sum != 0) {
$this->addError('transaction_number', 'The sum of the entries has to be 0');
}
}
Look, form should contain "transaction_number" variable. Error will be added to "transaction_number" input.
Option 3. In my experience.
It is better to separate ajax validation from form action url a.g. create another controller action for ajax validation and use it.
Example
Create model FeedbackForm
class FeedbackForm extends Model
{
public $name;
public $email;
public $text;
/**
* #inheritdoc
*/
public function rules()
{
return [
[['name', 'email', 'text'], 'required'],
[['name', 'email'], 'string', 'max' => 128],
[['email'], 'email'],
[['text'], 'string', 'max' => 512],
];
}
public function attributeLabels()
{
return [
'name' => \Yii::t('front', 'Name'),
'email' => \Yii::t('front', 'Email'),
'text' => \Yii::t('front', 'Message text'),
];
}
}
put actions to SiteSontroller
public function actionFeedback()
{
$model= new \frontend\models\FeedbackForm;
$model->load(Yii::$app->request->post());
if($model->validate()) {
$newFeed=new \frontend\models\Feedback;
$newFeed->create_time=new \yii\db\Expression('NOW()');
$newFeed->name=$model->name;
$newFeed->email=$model->email;
$newFeed->is_new=1;
$newFeed->text=$model->text;
if($newFeed->save()) {
\Yii::$app->session->setFlash('success', \Yii::t('front', 'Your message has accepted'));
} else {
\Yii::$app->session->setFlash('error', \Yii::t('front', 'Error on save'));
}
} else {
\Yii::$app->session->setFlash('error', \Yii::t('front', 'Data error'));
}
return $this->redirect(['/site/index']);
}
public function actionFeedbackvalidate()
{
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model= new \frontend\models\FeedbackForm;
$model->load(Yii::$app->request->post());
return ActiveForm::validate($model);
}
And create form inside view
<?php $model=new \frontend\models\FeedbackForm; ?>
<?php $form = ActiveForm::begin([
'enableClientValidation' => true,
'enableAjaxValidation' => true,
'validationUrl'=>['/site/feedbackvalidate'],
'validateOnSubmit' => true,
'id' => 'form-feedback',
'action'=>['/site/feedback'],
'options'=>['class'=>'some class', 'autocomplete'=>'off']
]); ?>
<?= $form->field($model, 'name')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('name'), 'autocomplete'=>'off'])->label(false); ?>
<?= $form->field($model, 'email')->textInput(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('email'), 'autocomplete'=>'off'])->label(false); ?>
<?= $form->field($model, 'text')->textarea(['maxlength' => true, 'placeholder'=>$model->getAttributeLabel('text'), 'autocomplete'=>'off'])->label(false); ?>
<div class="form-group">
<input type="submit" class="btn btn-default" value="<?php echo Yii::t('front', 'Send') ?>">
</div>
<?php ActiveForm::end(); ?>
That is it

Codeigniter - Trying to count articles view

Im working with codeigniter and have a controller and 3 model functions to read, insert and update a column of table.
i want to get count of view from db and update it per each view!
but after the update an extra row added to table! with zero value for content_id.
please check once!
this is my controller:
public function daily_report($id="")
{
$counter=$this->home_model->counter($id);
if($counter)
{
$view=$this->home_model->counter($id)->row()->view;
$views=$view+1;
$this->home_model->update_counter($views,$id);
}
else{
$views=1;
$this->home_model->set_counter($views,$id);
}
}
This is the model functions:
public function counter($id)
{
$code=$id;
$lang=$this->session->userdata('lang');
$data = $this->db
->select('view')
->from('tbl_views')
->where("content_id",$code)
->where("language",$lang)
->get();
if ($data->num_rows() > 0) {
return $data;
}else{
return false;
}
}
public function set_counter($views,$id)
{
$data = array(
'content_id' => $id ,
'view' => $views,
'language'=>$this->session->userdata('lang')
);
$this->db->insert('tbl_views', $data);
}
public function update_counter($views,$id)
{
$data = array(
'view' => $views,
);
$this->db->where('content_id', $id);
$this->db->update('tbl_views', $data);
}

Resources