Laravel Livewire wire:click() deleting component property? - components

Hi fellow stackoverflowers,
I am running repeatedly into following issue while digging myself into Laravel8 Livewire capabilities. In my Livewire component called Ticker I am reading a series of dates data from a calendar table into a component property $days.
In my view I am displaying a div element for each element of $days with a wire:click, which should wire to the component's tick() method.
The view renders and mounts fine, but each time an element is clicked, a "Trying to get property 'id' of non-object $day->id" error appears - as if the $days property had been deleted or emptied by the method call. Can anyone help me in understanding this behaviour?
Component Ticker.php:
class Counter extends Component
{
public $days;
public function mount()
{
$this->days = \DB::table("caldays")->WhereBetween("id", [320, 326])->get();
}
public function render()
{
return view('livewire.ticker');
}
public function tick($id)
{
/* do stuff with selected $id */
}
}
View ticker.blade.php:
...
#foreach($days as $day)
<div class="border border-light-blue-500 border-opacity-75 p-6 text-center"
wire:click="tick({{ $day->id }})"
>
#endforeach
...
Many thx in advance & happy year & stay healthy!

You have to notice that. After delete your respective day this will not update your collection anyway. So after deletion you have to re query the days.Or Simply remove the respective day from your collection.
Try This:
#forelse($days as $day)
<div class="border border-light-blue-500 border-opacity-75 p-6 text-center"
wire:click="tick({{ $day->id }})"
>
#empty
<p>No day found</p>
#endforelse
Another solution is:
#if(count($days>0)
#foreach($days as $day)
<div class="border border-light-blue-500 border-opacity-75 p-6 text-center"
wire:click="tick({{ $day->id }})"
>
#endforeach
#endif
In php file:
public function tick($dayId){
$day = \DB::table("caldays")->Where("id", $dayId)->get();
$day->delete();
if(in_array($dayId,$this->days)) {
$key = array_search($dayId, $this->days);
array_splice($this->days, $key, 1);
}
}

Related

Using livewire defer, if input value has text the value is sent when pagination link is clicked- how to stop this?

I'm using livewire with pagination. I set the input to defer so that the search is not carried out until the search button is clicked. However, I have also noticed that if I have any text in the search box that value is sent whenever a pagination button is clicked. I've tried setting the value to "" in jquery $(document).on("click", ".page-link", () => $( "#inlineFormInput" ).val("")); when a pagination buttton is click but that has not solved the problem. And actually clearing the value could cause other problems.
The desired result is that, if for some reason a user leaves text in the searchbox that value is not passed if the user changes their mind and just clicks a pagination link. The input value should only be passed when the search button is clicked. Any help would be greatly appreciated.
livewire component html:
<div id="searchBoxRow">
<input wire:model.defer="search" wire:keydown.enter="updateFaculty" id="inlineFormInput" class="form-control" val="" type="search" autocomplete="off" placeholder="Seach for Name or Country" aria-label="Search">
<button wire:click="updateFaculty" class="btn btn-primary" id="facultyCardsSearchButton" type="submit">
<i class="bi bi-search button-icon"></i>
<span class="button-text">Search</span>
</button>
</div>
livewire php file:
<?php
namespace App\Http\Livewire;
use App\Models\Tag;
use App\Models\Faculty;
use Livewire\Component;
use Livewire\WithPagination;
class FacultyData extends Component
{
use WithPagination;
protected $paginationTheme = 'bootstrap';
public $search = null;
public $tagId = null;
public function updateFaculty(){
$search = $this->search;
$tagId = $this->tagId;
$this->emit('closeAutocomplete');
}
public function updatingSearch()
{
$this->resetPage();
}
public function render()
{
$tags = Tag::all();
$allFaculty = Faculty::searchFilter([$this->search, $this->tagId])->with('country', 'tags')->paginate(10);
return view('livewire.faculty-data', [
'allFaculty' => $allFaculty,
'tags' => $tags
]);
}
}
Here is an idea. You can use the updatingPage() of the $page property hook. When performing the page switch clean the search property. But, somehow you need to check if this is a result of a searched data before or just the client has had his way...well, my idea is get a flagged property to tell livewire what to do. For example
public $searchFlag = false;
public function updateFaculty(){
if ($this->search) // only of search has any value
$this->searchFlag = true;
// do
}
public function updatingPage()
{
if (! $this->searchFlag) {
$this->reset(['search']);
}
if (! $this->search) {
$this->reset(['searchFlag']);
}
}

Laravel Request does not return the modified request on validation fail

I have recently updated my controllers to use Requests to validate data before saving, originally I used $request->validate() within the controller route, but I am now at a stage where I really need to seperate it out in to a request.
Issue
Before validation takes place, I need to alter some of the parameters in the request, I found out this can be done using the prepareForValidation() method, and this works great, during validation the values in the request have been altered. My issue comes if the validation fails. I need to be able to return the request I've altered back to the view, at the moment, after redirection it appears to be using the request as it was before I ran prepareForValidation(). (i.e. returns title as 'ABCDEFG' instead of 'Changed The Title').
After some reading on other SO posts and Laravel forum posts, it looks as though I need to overwrite the FormRequest::failedValidation() method (which I've done, see code below), however I'm still struggling to find a way to pass my altered request back. I've tried to edit the failedValidation() method, I've provided details further down.
Expectation vs Reality
Expectation
User enters 'ABCDEFG' as the title and presses save.
The title is altered in the request (using prepareForValidation()) to be 'Changed The Title'.
Validation fails and the user is redirected back to the create page.
The contents of the title field is now `Changed The Title'.
Reality
User enters 'ABCDEFG' as the title and presses save.
The title is altered in the request (using prepareForValidation()) to be 'Changed The Title'.
Validation fails and the user is redirected back to the create page.
The contents of the title field shows `ABCDEFG'.
What I've tried
Passing the request over to the ValidationException class.
After digging through the code, it looks as though ValidationException allows a response to be passed over as a parameter.
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator, $this))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
However this results in the error Call to undefined method Symfony\Component\HttpFoundation\HeaderBag::setCookie() in Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::addCookieToResponse.
Flashing the request to the session
My next attempt was to just flash my request to the session, this doesn't seem to work, instead of my modified request being in the session, it looks to be the request before I ran prepareForValidation().
protected function failedValidation(Validator $validator)
{
$this->flash();
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
Returning a response instead of an exception
My final attempt to get this to work was to return a response using withInput() instead of the exception.
protected function failedValidation(Validator $validator)
{
return redirect($this->getRedirectUrl())
->withErrors($validator)
->withInput();
}
However it looks as though the code continues in to the BlogPostController::store() method instead of redirecting back to the view.
At this point I'm out of ideas, I just can't seem to get the altered request back to the view if validation fails!
Other Notes
I am pretty much a Laravel newbie, I have experience with a custom framework loosely based on Laravel, but this is my first CMS project.
I fully understand I may well be going down the wrong route, perhaps there's a better way of altering a request and passing it back when validation fails?
What am I trying to achieve by doing this? The main thing is the active checkbox. By default it is checked (See the blade below), if the user unchecks it and presses save, active is not passed over in the HTTP request, therefore active does not exist in the Laravel request object and when the user is returned back, the active checkbox has been checked again when it shouldn't be.
Then why have you used title in your example? I am using title in my post because I think it's easier to see what I am trying to achieve.
Any help is apreciated as I've currently burnt quite a few hours trying to solve this. 😣
Related Code
BlogPostController.php
<?php
namespace App\Http\Controllers;
use App\BlogPost;
use Illuminate\Http\Request;
use App\Http\Requests\StoreBlogPost;
class BlogPostController extends Controller
{
/**
* Run the auth middleware to make sure the user is authorised.
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('admin.blog-posts.create');
}
/**
* Store a newly created resource in storage.
*
* #param StoreBlogPost $request
* #return \Illuminate\Http\Response
*/
public function store(StoreBlogPost $request)
{
$blogPost = BlogPost::create($request->all());
// Deal with the listing image upload if we have one.
foreach ($request->input('listing_image', []) as $file) {
$blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('listing_image');
}
// Deal with the main image upload if we have one.
foreach ($request->input('main_image', []) as $file) {
$blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('main_image');
}
return redirect()->route('blog-posts.edit', $blogPost->id)
->with('success', 'The blog post was successfully created.');
}
}
// Removed unrelated controller methods.
StoreBlogPost.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;
class StoreBlogPost extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required',
'url' => 'required',
'description' => 'required',
'content' => 'required',
];
}
/**
* Get the error messages for the defined validation rules.
*
* #return array
*/
public function messages()
{
return [
'title.required' => 'The Title is required',
'url.required' => 'The URL is required',
'description.required' => 'The Description is required',
'content.required' => 'The Content is required',
];
}
/**
* Prepare the data for validation.
*
* #return void
*/
protected function prepareForValidation()
{
$this->merge([
'active' => $this->active ?? 0,
'title' => 'Changed The Title',
]);
}
/**
* #see FormRequest
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
}
create.blade.php
#extends('admin.layouts.app')
#section('content')
<div class="edit">
<form action="{{ route('blog-posts.store') }}" method="POST" enctype="multipart/form-data">
#method('POST')
#csrf
<div class="container-fluid">
<div class="row menu-bar">
<div class="col">
<h1>Create a new Blog Post</h1>
</div>
<div class="col text-right">
<div class="btn-group" role="group" aria-label="Basic example">
<a href="{{ route('blog-posts.index') }}" class="btn btn-return">
<i class="fas fa-fw fa-chevron-left"></i>
Back
</a>
<button type="submit" class="btn btn-save">
<i class="fas fa-fw fa-save"></i>
Save
</button>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="form-group row">
<label for="content" class="col-12 col-xl-2 text-xl-right col-form-label">Active</label>
<div class="col-12 col-xl-10">
<div class="custom-control custom-switch active-switch">
<input type="checkbox" name="active" value="1" id="active" class="custom-control-input" {{ old('active', '1') ? 'checked' : '' }}>
<label class="custom-control-label" for="active"></label>
</div>
</div>
</div>
<div class="form-group row">
<label for="title" class="col-12 col-xl-2 text-xl-right col-form-label required">Title</label>
<div class="col-12 col-xl-10">
<input type="text" name="title" id="title" class="form-control" value="{{ old('title', '') }}">
</div>
</div>
</div>
</form>
</div>
#endsection
I had the same issue and here is the solution I found after digging through laravel code.
It seems that Laravel creates a different object for the FormRequest, so you can do something like this.
protected function failedValidation(Validator $validator)
{
// Merge the modified inputs to the global request.
request()->merge($this->input());
parent::failedValidation($validator);
}

Livewire and Flatpickr - fails after rerender

This may be a basic question, but I'm struggling. Essentially I have a livewire component that updates an array of flight information that a user enters. Whenever the components get rerendered, the flatpickr functionality stops working entirely. I presume this is because the javascript to initialize the component on that field is not running. What is the best practice to ensure these get rerendered with the appropriate javascript to enable the functionality.
Here's my blade snippet which renders fine on the initial load, but whenever a change to the data occurs, the page re-renders all the flights in the array, but the flatpickr functionality does not work anymore.
<form>
#foreach($flights as $i => $f)
<label
x-data
x-init="flatpickr($refs.input, {
dateFormat: 'Y-m-d H:i',
altInput: true,
altFormat: 'F j, Y h:i K',
enableTime: true,
})">
<div class="form-label">Arrival Time</div>
<div class="relative">
<input type="text"
wire:model="flights.{{ $i }}.ArrivalTime"
wire:key="fl{{ $i }}arrtime"
data-input
x-ref="input"
placeholder="Arrival Time"
value="{{ $f['ArrivalTime']}}"
name="flights[{{ $i }}][ArrivalTime]"
id="ArrivalTime{{$i}}"
/>
</div>
</label>
#endforeach
</form>
The livewire component is basically this:
class Itinerary extends Component
{
public $itin = null;
public $flights = [];
public function render()
{
return view('livewire.itinerary');
}
}
You need to wrap the input in a <div> like this:
<div wire:ignore>
<!-- Your input here -->
</div>
Source: https://laravel-livewire.com/docs/2.x/alpine-js#ignoring-dom-changes

Is it possible to add a message in the listing of items in Prestashop?

I've built my module and made an AdminController that list items from my table, with creation/update/delete/view.
In the listing page, I'd like to add a message after the breadcrumb, but before the table.
I saw there is a hook available : "displayAdminListBefore" and a block to extend "override_header", but I don't know how to make it work!
Can someone could point me in the right direction please?
You can simply add your module to the displayAdminListBefore hook.
First hook the module to this hook with the install function:
public function install()
{
if (!parent::install() || !$this->registerHook('displayAdminListBefore'))
return false;
return true;
}
Then create the hook function like that:
public function hookDisplayAdminListBefore($params)
{
return '
<div class="bootstrap">
<div class="alert alert-success">
<button data-dismiss="alert" class="close" type="button">×</button>
Add your text here
</div>
</div>
';
}
Or, you can also use a .tpl:
public function hookDisplayAdminListBefore($params)
{
$this->smarty->assign(array(
'first_var' => $first_var,
'second_var' => $second_var',
));
return $this->display(__FILE__, 'views/templates/admin/listbefore.tpl');
}
The best way for you will be to override list_header.tpl and use the override_header hook.
To do so, create a new file list_header.tpl in modules/your_module/views/templates/admin/your_module/helpers/list/list_header.tpl
In this file copy the following code:
{extends file="helpers/list/list_header.tpl"}
{block name="override_header"}
Your text
{$your_var}
{/block}
$your_var must be defined in your controller in the function renderList():
$this->context->smarty->assign(
array(
'your_var' => 'your_var_value'
)
);

Orchard CMS: Logon Page doesn't work with my custom layout

I am very new to Orchard.
I have created a new theme, based on the Minty theme. The only real change is the layout, where I have adapted the html from an existing asp.net masterpage to match the orchard style razor layout.cshtml. I have experience with MVC and razor, so no problem on that side... unless I have missed something vital.
The problem is the login page. Clicking the sign in link takes me to the correct url without errors, but not login form gets rendered. I have checked that this is the case by Inspecting Element in google chrome.
I am aware that setting up widgets, etc, I can make content appear. However, I can't find how the login form gets inserted when the login url gets requested. I presume it uses the Orchard.Users module, but not sure how. Does it need a specific zone? I can't see why, but see how else.
As a result, I can't solve my problem...
Any pointers?
Any books or other learning media?
The code for my layout.cshtml is:
#functions {
// To support the layout classifaction below. Implementing as a razor function because we can, could otherwise be a Func<string[], string, string> in the code block following.
string CalcuClassify(string[] zoneNames, string classNamePrefix) {
var zoneCounter = 0;
var zoneNumsFilled = string.Join("", zoneNames.Select(zoneName => { ++zoneCounter; return Model[zoneName] != null ? zoneCounter.ToString() : "";}).ToArray());
return HasText(zoneNumsFilled) ? classNamePrefix + zoneNumsFilled : "";
}
}
#{
/* Global includes for the theme
***************************************************************/
SetMeta("X-UA-Compatible", "IE=edge,chrome=1");
Style.Include("http://fonts.googleapis.com/css?family=Handlee");
Style.Include("http://html5shiv.googlecode.com/svn/trunk/html5.js");
Style.Include("site.css");
Script.Require("jQuery").AtHead();
Script.Require("jQueryUI_Core").AtHead();
Script.Require("jQueryUI_Tabs").AtHead();
Script.Include("http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.0.4/modernizr.min.js").AtHead();
Style.Include("TagDefaults.css");
Style.Include("LayoutStructure.css");
Style.Include("LayoutStyling.css");
Style.Include("TopMenu.css");
Style.Include("LeftBlock.css");
Style.Include("RightBlock.css");
Style.Include("MenuAdapter.css");
Style.Include("Content.css");
Style.Include("FloatedBoxes.css");
Style.Include("Helen.css");
/* Some useful shortcuts or settings
***************************************************************/
Func<dynamic, dynamic> Zone = x => Display(x); // Zone as an alias for Display to help make it obvious when we're displaying zones
/* Layout classification based on filled zones
***************************************************************/
//Add classes to the wrapper div to toggle aside widget zones on and off
var asideClass = CalcuClassify(new [] {"Sidebar"}, "aside-"); // for aside-1, aside-2 or aside-12 if any of the aside zones are filled
if (HasText(asideClass)) {
Model.Classes.Add(asideClass);
}
//Add classes to the wrapper div to toggle tripel widget zones on and off
var tripelClass = CalcuClassify(new [] {"TripelFirst", "TripelSecond", "TripelThird"}, "tripel-"); // for tripel-1, triple-2, etc. if any of the tripel zones are filled
if (HasText(tripelClass)) {
Model.Classes.Add(tripelClass);
}
//Add classes to the wrapper div to toggle quad widget zones on and off
var footerQuadClass = CalcuClassify(new [] {"FooterQuadFirst", "FooterQuadSecond", "FooterQuadThird", "FooterQuadFourth"}, "split-"); // for quad-1, quad-2, etc. if any of the quad zones are filled
if (HasText(footerQuadClass)) {
Model.Classes.Add(footerQuadClass);
}
var slideshowClass = CalcuClassify(new[] {"HomeSlideshow"}, "slideshow-");
if (HasText(slideshowClass)) {
Model.Classes.Add(slideshowClass);
}
/* Inserting some ad hoc shapes
***************************************************************/
//WorkContext.Layout.Header.Add(New.Branding(), "5"); // Site name and link to the home page
//WorkContext.Layout.Footer.Add(New.BadgeOfHonor(), "5"); // Powered by Orchard
WorkContext.Layout.Footer.Add(New.User(), "10"); // Login and dashboard links
/* Last bit of code to prep the layout wrapper
***************************************************************/
Model.Id = "layout-wrapper";
var tag = Tag(Model, "div"); // using Tag so the layout div gets the classes, id and other attributes added to the Model
}
#tag.StartElement
<a name="top"></a>
<div id="SiteHeader">
</div>
<div id="PageContainer">
<div style="position: absolute; Left:-80px; top:-88px;z-index:1000;">
<img id="bird" title="Pheasant" src="/Themes/TheFarmsBlogs/Styles/Images/PositionedImages/pheasant.gif" />
</div>
<div class="SiteMenu"><p>Hello Menu</p></div>
<div id="Specialized">
<div id="PageName">
<!--
PageName NOT in use!
-->
</div>
#if (Model.RightColumn != null) {
<div id="RightCol">
#Zone(Model.RightColumn)
</div>
}
<!-- Page divided into two main columns, of which the left column is subdivided as necessary -->
<div id="LeftCol">
<div id="PageBanner">
<div id="PageBannerLeft">
#if (Model.MainImage != null) {
<div id="PageBannerImage">
#Zone(Model.MainImage)
</div>
}
#if(Model.TheStrip != null) {
<div id="TheStrip">
#Zone(Model.TheStrip)
</div>
}
</div>
</div>
<div id="SpecializedContent">
#if(#Model.content != null)
{
#Zone(Model.content)
}
</div>
</div>
<div id="SpecializedFooter">
</div>
</div>
<div id="PageFooter">
#if (Model.FooterPage != null){
#Zone(Model.FooterPage)
}
</div>
</div>
<div id="SiteFooter">
#Display(Model.Footer)
The Farms Ltd - © 2007
</div>
#tag.EndElement
PS: the branding and badge of honour are commented out as I am only enabling bit by bit to eliminate the source of errors. It will be in the live site.
ADDENDUM:
See Bertrand Le Roy's answer below. The Orchard.Users module requires a Content zone with a Capital C. That instantly cured the problem.
I added this as Bertrand's response was tentative, and I wanted to reinforce that the problem was the name of the zone.
In Orchard.Users, look for Controllers/AccountController.cs. In there, there is a LogOn action. It creates a LogOn shape that it then puts in a shape result. This then gets resolved as the Views/LogOn.cshtml template (which you can override in your theme by just dropping a file with the same name in there, for example a copy of the original that you can tweak). The LogOn template will be rendered within the theme's layout, in the Content zone. Does this answer your question?
I think the mistake you made was to name your Content zone content (notice the casing).

Resources