Error on multi select when using Laravel Livewire - php

I'm using choices.js to have multiple select options in my Laravel project. However, I'm currently having an error Attempt to read property "id" on string.
PostController
public function new()
{
$tags = TagQuery::all();
return view('threads.new', compact('tags'));
}
new.blade.php
<livewire:editor :title="null" :tags="$tags" :body="null" />
livewire/editor.blade.php
#if($errors->has('tags'))<span class="text-pink-600 text-sm">{{ $errors->first('tags') }}</span>#endif
<select id="tags" multiple #if($errors->has('tags')) class="border-pink-600" #endif wire:model="tags">
#foreach($tags as $tag)
<option x-cloak value="{{ $tag->id }}">{{ $tag->name }}</option>
#endforeach
</select>
Editor.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class Editor extends Component
{
public $title;
public $tags;
public $body;
protected $rules = [
'title' => ['required', 'min:5', 'max:70'],
'body' => ['required', 'min:10', 'max:50000'],
'tags' => ['required', 'array', 'min:1', 'max:3'],
'tags.*' => ['exists:tags,id']
];
public function render()
{
return view('livewire.editor');
}
public function submit()
{
$this->validate();
return dd($this->title, $this->body, $this->tags);
}
}
How do I fix this issue? The code is working if submitted as normal form (without livewire). I need to use Livewire since I'm also using tiptap headless editor and I cannot pass the value of the editor from livewire to regular laravel request.

Related

Why are some of my request parameters sent as `null` to my controller?

I'm developing a LiveWire/Laravel project and in my product.blade.php, the category_id is sent as null to the store function. Apparently there is no problem in my input and my model and category_id is defineded in my component.
I used some artisan comands related to clearing the cache and config and even I created another component with different name but I met same bug
dd:
array:8 [▼
"title" => "تراکت"
"en_title" => "tracket"
"category_id" => null
"description" => "nothing"
"status" => 1
"img" => null
"price" => "48000"
"keywords" => "tracket"
]
Livewire Product Blade:
<div class="form-group">
<label class="form-label">دسته بندی محصول</label>
<select wire:model="category_id" id="select-countries" class="form-control custom-select">
#foreach($categories as $category)
<option value="{{$category->id}}">{{$category->title}}</option>
#if(count($category->childrenRecursive) >0)
#include('Admin.category.partials',['categories'=>$category->childrenRecursive,'level'=>1,'create'=>1])
#endif
#endforeach
</select>
</div>
Livewire Component :
<?php
class Index extends Component
{
public $title,$en_title,$category_id,$description,$status=1,$img=null,$price,$keywords;
public function store(){
$keywords=serialize(explode(",",$this->keywords));
Product::create([
'title'=>$this->title,
'en_title'=>$this->en_title,
'category_id'=>$this->category_id,
'description'=>$this->description,
'status'=>$this->status,
'price'=>$this->price,
'image'=>$this->img,
'keywords'=>$keywords,
]);
$this->reset(['title','en_title','category_id','description','price']);
session()->flash('add_product','محصول شما با موفقیت اضافه شد');
}
public function render()
{
$categories=Category::with('childrenRecursive')->where('parent_id',null)->get();
$products=Product::latest()->paginate(8);
return view('livewire.admin.product.index',compact('categories','products'))->extends('layouts.admin')->section('content');
}
}
Your syntax appears correct and I suspect you're sometimes seeing null because you're not setting an initial value for $category_id. Therefore, if you don't select an option from your select element and submit your form, the value of $category_id will be null.
You can perform some component initialization in the mount method where I suggest you set an initial value for your public properties:
public $categories, $products;
public $category_id;
public function mount()
{
$this->categories = Category::with('childrenRecursive')
->where('parent_id', null)
->get();
$this->products = Product::latest()->paginate(8);
$this->category_id = $this->categories->first()->id;
}
public function render()
{
return view(
'livewire.admin.product.index',
compact('categories','products')
)
->extends('layouts.admin')
->section('content');
}

Laravel Livewire accessing json attribute fails with type error

I have a model with some typical columns and one json column. The Json is casted as array:
Model:
protected $casts = [
'client' => 'array'
];
In my Livewire component, I created the following validation rule
Livewire component:
protected $rules = [
'editing.name' => 'required',
...
'editing.client' => 'present|array',
'editing.client.*.tenant' => 'required',
];
I call the 'editModal' method where I type-hint the model and set a public property with it's attributes. Already filtered to the specific item.
Livewire component:
public function editModal(TokenCacheProvider $provider)
{
$this->editing = $provider;
$this->dispatchBrowserEvent('open-modal', ['modal' => 'edit']);
}
My blade is a simple boiler blade component:
Blade:
<div>
<x-input.group inline borderless for="name" label="Name" :error="$errors->first('editing.name')"/>
<x-input.text name="name" class="w-full" wire:model="editing.name" />
</div>
<div>
<x-input.group inline borderless for="name" label="Tenant" :error="$errors->first('editing.client.tenant')"/>
<x-input.text name="tenant" class="w-full" wire:model="editing.client.tenant" />
</div>
Once I load the page I get the following type exception
foreach() argument must be of type array|object, string given
This is because the client attribute is still a string as in the database. It should be an array as I casted it:
So, I don't understand why the client attribute is still a string and not an array as casted.
Thank you
Well it's more a work-around than a solution but Daantje found an Livewire issue on Github which might explain this behavior.
I've changed the architecture from one to two public properties. One for the actual model and a second for the json column.
Livewire component (truncated)
public MyModel $editing; // the model
public array $client; // for the json attribute
protected $rules = [
'editing.name' => 'required',
...
'client.foo' => 'required',
'client.bar' => 'required',
'client.baz' => 'required',
...
];
public function editModal(MyModel $model)
{
$this->editing = $model;
$this->client = json_decode($model->client,true);
$this->dispatchBrowserEvent('open-modal', ['modal' => 'edit']);
}
public function save()
{
$this->validate();
$this->editing->client = json_encode($this->client);
$this->editing->save();
$this->dispatchBrowserEvent('close-modal', ['modal' => 'edit']);
$this->event('Saved', 'success');
}
Two blade input field examples:
<!-- ORM field(s) -->
<div>
<x-input.group inline borderless for="name" label="Name" :error="$errors->first('editing.name')"/>
<x-input.text name="name" wire:model="editing.name" />
</div>
<!-- Json field(s) -->
<div>
<x-input.group inline borderless for="foo" label="Foo" :error="$errors->first('client.foo')"/>
<x-input.text name="foo" wire:model="client.foo" />
</div>
Well, this works but as mentioned it's more a workaround

How do I show a dynamic navigation bar on a dynamic web page in Laravel?

I have a dynamic navigation bar, created however it won't show on the dynamic web page.
The current output is:
ErrorException
Undefined variable: navContent (View: C:\Users\Computer
Angel\Documents\blog\resources\views\page\dynamic.blade.php)
The desired output is my dynamic.blade.php where the pageContent is the dynamic page content the user inputted through a form and the dynamic navigation bar in the tags.
This is my dynamic.blade.php:
<nav>
#foreach($navContent as $nav)
{!!nav-navName!!}
#endforeach
</nav>
<body>
{!!$pageContent->pageContent!!}
</body>
This is my NavController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Nav;
use DB;
use Illuminate\Database\MySqlConnection;
class NavController extends Controller
{
public function index()
{
$navs = Nav::all();
return view('navManagement', compact('navs'));
}
public function create()
{
return view('createNav');
}
public function store(Request $request)
{
$data = request()->validate([
'navName' => 'required',
'navLink' => 'required',
]);
$nav = new Nav([
'navName' => $request->get('navName'),
'navLink' => $request->get('navLink'),
]);
$nav->save();
return redirect('/n');
}
public function show($navName)
{
$navContent = DB::table('navs')->where('navName',$navName)->first();
return view('page.dynamic', ['navContent' => $navContent]);
}
public function edit($navName)
{
$navContent = DB::table('navs')->where('navName',$navName)->first();
return view('editNav', ['navContent' => $navContent]);
}
public function update(Request $request)
{
$data = $request->validate([
'navName' => 'required|exists:navs,navName',
'navLink' => 'required'
]);
$obj = \App\Nav::where('navName', $request->navName)
->update([
'navLink' => $request->navLink
]);
return redirect('/n');
}
public function destroy(Request $request)
{
$obj = \App\Nav::where('navName', $request->navName)
->delete();
return redirect('/n');
}
}
This is my PageController.php:
<?php
namespace App\Http\Controllers;
use App\Page;
use Illuminate\Http\Request;
use DB;
use Illuminate\Database\MySqlConnection;
class PageController extends Controller
{
public function index()
{
$pages = Page::all();
return view('pageManagement', compact('pages'));
}
public function create()
{
//This will load create.blade.php
return view('createPage');
}
public function store(Request $request)
{
$data = request()->validate([
'title' => 'required',
'URI' => 'required|min:5|max:10|',
'pageContent' => 'required',
]);
$page = new Page([
'title' => $request->get('title'),
'URI' => $request->get('URI'),
'pageContent' => $request->get('pageContent'),
]);
$page->save();
return redirect('/p');
}
public function show($URI)
{
$pageContent = DB::table('pages')->where('URI',$URI)->first();
return view('page.dynamic', ['pageContent' => $pageContent]);
}
public function edit($URI)
{
$pageContent = DB::table('pages')->where('URI',$URI)->first();
return view('editPage', ['pageContent' => $pageContent]);
}
public function update(Request $request)
{
$data = $request->validate([
'title' => 'required',
'URI' => 'required|min:5|max:10|exists:pages,URI',
'pageContent' => 'required'
]);
$obj = \App\Page::where('URI', $request->URI)
->update([
'title' => $request->title,
'pageContent' => $request->pageContent
]);
return redirect('/p');
}
public function destroy(Request $request)
{
$obj = \App\Page::where('URI', $request->URI)
->delete();
return redirect('/p');
}
}
This is my Nav.php:
class Nav extends Model
{
protected $fillable = ['navName', 'navLink'];
}
This is my Page.php:
class Page extends Model
{
protected $fillable = ['title', 'URI', 'pageContent'];
}
This is my migration for pages:
public function up()
{
Schema::create('pages', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('URI');
$table->text('pageContent');
$table->timestamps();
});
}
This is my migration for nav:
public function up()
{
Schema::create('navs', function (Blueprint $table) {
$table->id();
$table->string('navName');
$table->string('navLink');
$table->timestamps();
});
}
This is my createNav.blade.php:
<form action="/storeNav" method="post">
#csrf
<label for="navName">Navigation Bar Option Name:</label><br>
<input type="text" id="navName" name="navName" autocomplete="off" value="{{ old('navName') }}">
<br>
#error('navName') <p style="color: red">{{ $message }}</p> #enderror
<label for="navLink">Navigation Bar Option Link:</label><br>
<input type="text" id="navLink" name="navLink" autocomplete="off" value="{{ old('navLink') }}">
<br>
#error('navLink') <p style="color: red">{{ $message }}</p> #enderror
<input type="submit" value="Submit">
</form>
This is my createPage.blade.php:
<form action="/storePage" method="post">
#csrf
<label for="title">Title:</label><br>
<input type="text" id="title" name="title" autocomplete="off" value="{{ old('title') }}"><br>
#error('title') <p style="color: red">{{ $message }}</p> #enderror
<label for="URI">URI:</label><br>
<input type="text" id="URI" name="URI" autocomplete="off" value="{{ old('URI') }}"><br>
#error('URI') <p style="color: red">{{ $message }}</p> #enderror
<label for="pageContent">Page Content:</label><br>
<textarea id="pageContent" name="pageContent" value="{{ old('pageContent') }}"></textarea>
#error('pageContent') <p style="color: red">{{ $message }}</p> #enderror
<input type="submit" value="Submit">
</form>
This is my web.php:
Route::get('/page/{URI}', 'PageController#show');
Route::get('/page/{URI}/edit', 'PageController#edit');
Route::get('/p', 'PageController#index');
Route::get('/createPage', 'PageController#create');
Route::post('/storePage', 'PageController#store');
Route::patch('/page/{URI}', 'PageController#update');
Route::delete('/page/{URI}', 'PageController#destroy');
Route::get('/nav/{navName}/edit', 'NavController#edit');
Route::get('/n', 'NavController#index');
Route::get('/createNav', 'NavController#create');
Route::post('/storeNav', 'NavController#store');
Route::patch('/nav/{navName}', 'NavController#update');
Route::delete('/nav/{navName}', 'NavController#destroy');
Below is my github repository link, if you want to take a look at my full code, or you want to try run the code in your Integrated Development Environment.
https://github.com/xiaoheixi/blog
Thanks for reading! :D
If is complaining that it can't find a variable called $navContent.
I can't see you passing it to either of the views you are calling from your index() functions.
return view('navManagement', compact('navs'));
return view('pageManagement', compact('pages'));
You would need to set that varaible and pass it to the view
// Get the nav content however you want, this is just a crude example
$navContent = $this->getMyNavContent();
return view('navManagement', [
'navs' => $navs,
'navContent' => $navContent
]);
I'm guessing you want to find a way to embed the dynamic nav without having to add the navContent every time you render a view, as that's what the other answer is suggesting. You could achieve this using a middleware, say dynamicNav and registering your routes under that middleware group.
In that middleware you can do all the logic of fetching the nav content and then use something like merge, as shown here:
$request->merge(['dynamicNav' => $navContent]);
This way your middleware will add the data to every request which goes through it, although I wouldn't recommend this solution.
What I would do is cache the nav content and retrieve in the view using the cache() helper, then override the save() method of the model to also update the cache when the DB is updated (to avoid duplicate code, you could create a trait for the fetching of the nav). Example:
// in your model
public function save(array $options = [])
{
Cache::put('nav-content', getNavContent());
parent::save();
}

Laravel validation blade syntax

I have a dropdown like this
How could i sent the option selected on dropdown into database? I need blade syntax to send them.
Here is my piece of code for validation:
FrontController
public function insert(Request $request)
{
$request->validate([
'jenis' => 'required|string',
'kategori' => 'required|string',
'nopol' => 'required|string',
]);
DB::table('park')->insert([
'jenis' => $request->input('jenis'),
'kategori' => $request->input('kategori'),
'nopol' => $request->input('nopol'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'status' => true;
]);
}
home.blade.php
<div class="control-group">
<label class="control-label" for="typeahead">Jenis Kendaraan</label>
<div class="controls">
<select id="opt" name="jenis">
<option value="1">Motor</option>
<option value="2">Mobil</option>
</select>
</div>
</div>
Should i use #foreach or directly use #if #else #endif ?
I think you want to save jenis with value either 'Motor' or 'Mobile' because you are validating as a string. Just put as
<select id="opt" name="jenis">
<option value="Motor">Motor</option>
<option value="Mobil">Mobil</option>
</select>
Whatever you write in value will go in the request object.
<option value="1">Motor</option>
this what you wrote in the value , so on the form submission in request()->input('jenis') you will get 1.. you should give numeric validation or between(range) type of validation, please check the laravel docs.
In case anyone stumbled upon this via Googling how to validate actual Blade syntax, check this out.
Create a custom validation rule:
<?php
namespace App\Rules;
use Exception;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\File;
class BladeSyntax implements Rule
{
private $message;
public function passes($attribute, $value)
{
File::put(resource_path('views/validation.blade.php'), $value);
try {
view('validation')->render();
return true;
}
catch (Exception $exception) {
$this->message = $exception->getMessage();
return false;
}
}
public function message()
{
return explode('(', $this->message)[0];
}
}
Then use new BladeSyntax in your validation rules.

why do I get preg_replace() error when submitting a post form in Laravel?

I have built a simple application laravel 4. I have scaffolding setup for adding posts which seems to be working fine. I have setup Stapler and image uploading package. When I setup to use single image uploads its pretty good and it works a charm. I recently looked at the docs here
It states that you can do multiple uploads so I went about doing it as explained in the docs. Here are my coded pages:
Post.php model:
<?php
class Post extends Eloquent {
use Codesleeve\Stapler\Stapler;
protected $guarded = array();
// A user has many profile pictures.
public function galleryImages(){
return $this->hasMany('GalleryImage');
}
public static $rules = array(
'title' => 'required',
'body' => 'required'
);
public function __construct(array $attributes = array()) {
$this->hasAttachedFile('picture', [
'styles' => [
'thumbnail' => '100x100',
'large' => '300x300'
],
// 'url' => '/system/:attachment/:id_partition/:style/:filename',
'default_url' => '/:attachment/:style/missing.jpg'
]);
parent::__construct($attributes);
}
}
PostsController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$input = Input::all();
$validation = Validator::make($input, Post::$rules);
if ($validation->passes())
{
$this->post->create($input);
return Redirect::route('posts.index');
}
$post = Post::create(['picture' => Input::file('picture')]);
foreach(Input::file('photos') as $photo)
{
$galleryImage = new GalleryImage();
$galleryImage->photo = $photo;
$user->galleryImages()->save($galleryImage);
}
return Redirect::route('posts.create')
->withInput()
->withErrors($validation)
->with('message', 'There were validation errors.');
}
This has save functions and other functions inside it too.
GalleryImage.php gallery image model to use in the post controller
<?php
class GalleryImage extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function __construct(array $attributes = array()) {
$this->hasAttachedFile('photo', [
'styles' => [
'thumbnail' => '300x300#'
]
]);
parent::__construct($attributes);
}
// A gallery image belongs to a post.
public function post(){
return $this->belongsTo('Post');
}
}
My create.blade.php template to post the post itself
#extends('layouts.scaffold')
#section('main')
<h1>Create Post</h1>
{{ Form::open(array('route' => 'posts.store', 'files' => true)) }}
<ul>
<li>
{{ Form::label('title', 'Title:') }}
{{ Form::text('title') }}
</li>
<li>
{{ Form::label('body', 'Body:') }}
{{ Form::textarea('body') }}
</li>
<li>
{{ Form::file('picture') }}
</li>
<li>
{{ Form::file( 'photo[]', ['multiple' => true] ) }}
</li>
<li>
{{ Form::submit('Submit', array('class' => 'btn btn-info')) }}
</ul>
{{ Form::close() }}
#if ($errors->any())
<ul>
{{ implode('', $errors->all('<li class="error">:message</li>')) }}
</ul>
#endif
#stop
When I post the form with a single images attached its fine and saves to the db and it works a treat but when I save it with multiple image uploads I get this error:
ErrorException
preg_replace(): Parameter mismatch, pattern is a string while replacement is an array
The full stack trace is here in my gist of the files
Can anyone point out to me why this error happens. From my research its creating a multidimensional array that needs flattening I think but I am unsure if this is true.
I have been banging my head against a brick wall with this for ages.
Problem is when your submitting multiple images it becomes an array of pictures instead of a single string. So its trying to save an array to the database instead of a string which its expecting. If you make it so your photo variable is a json_encoded array of pictures then you should be able to save them.
Hope this helps.

Categories