laravel livewire refresh when updating variable - laravel

As you can see I am trying to make a list of categories, using ul/li on laravel livewire, the problem is that when I click on the "+" it transform to "-" as expected but retransform to "+", because I think DOM reloads.
controller:
<?php
namespace App\Http\Livewire\Product;
use App\Classes\Product\ShopifyCatList;
use Livewire\Component;
class ShopifyNewCategory extends Component
{
public $categories, $html;
private function getFirstCat()
{
$shopifyCatList = new ShopifyCatList();
$this->categories = $shopifyCatList->getFirstCat();
}
public function addChildren($rank)
{
$shopifyCatList = new ShopifyCatList();
$this->categories = $shopifyCatList->updateCategories($rank);
$this->html = $shopifyCatList->updateHtml($this->html, $this->categories, $rank);
}
public function mount()
{
$this->getFirstCat();
$this->html = '<li data-rank="' . $this->categories[0]['rank'] . '"><span class="sign" x-on:click="openList">+</span><span class="content">' . $this->categories[0]['name'] . '</span></li>';
}
public function render()
{
return view('livewire.product.shopify-new-category')->extends('layouts.app-livewire')->section('content');
}
}
view:
#section('header')
<div id="innerheader" style="display: flex;flex-direction: row;">
<div style="flex:auto">
<h1>Création nouvelle catégorie</h1>
</div>
</div>
#endsection
{{-- section content est mis dans le controller sinon form ne fonctionne pas correctement https://laracasts.com/discuss/channels/livewire/laravel-livewire-submitprevent-not-working-refreshing-page --}}
<form wire:submit.prevent="newCategory">
#csrf
<div class="form-group">
<label for="name">Nom de la catégorie</label>
<input type="text" class="form-control" id="name" name="name" placeholder="Nom de la catégorie"
wire:model="name">
#error('name')
<span class="text-danger">{{ $message }}</span>
#enderror
</div>
<div class="form-group">
<label for="parent" id="parent">Catégorie parent: <b></b></label>
<div x-data="categories" id="parent" class="border rounded bg-white overflow-auto" style="height:40vh;">
<ul style="padding-left:2%">
{!! $html !!}
</ul>
</div>
#error('parent')
<span class="text-danger">{{ $message }}</span>
#enderror
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-success mr-1" wire:click="updateValue('save')">Enregistrer</button>
<button type="submit" class="btn btn-primary" wire:click="updateValue('saveRecreate')">Enregistrer et
créer un autre</button>
</div>
</form>
#push('styles')
<style>
.content ul {
list-style: none;
}
span.sign {
margin-right: 6px;
}
.content li {
cursor: pointer;
}
.content li:hover {
background-color: #39F856;
}
</style>
#endpush
<script>
categories = () => {
return {
selected: '',
labelNoticeSelected: $('label#parent b'),
openList: (e) => {
if (e.target.innerText === '+') {
e.target.innerText = '-';
#this.addChildren(e.target.parentNode.getAttribute('data-rank'));
} else if (e.taget.innerText === '-') {
e.target.innerText = '+';
e.target.parentNode.parentNode.querySelector(
`li[data-rank='${e.target.parentNode.getAttribute('data-rank')}']+ul`).style
.display = 'none';
}
}
}
}
</script>
Here is all the code, I am also using alpinejs, when I click on the "+" I want to request db for children of the category I click on (that is ok) then I want it to transform "+" to "-" (that is not ok) so when I click on "-" it hides the children.
thanks for reading all of this and thanks to everyone trying to help.

Either use a shared state between Livewire and Alpine (see docs) or wrap your button in a wire:ignore. (Potentially a wire:ignore.self if the DOM inside of the ignored div does require Livewire updates)

Related

Laravel Ajax Add To Cart Not working 500 internal error

I have code I am working on. I will list the page, the route, and the controller code. When I inspect and click on the add to cart button I get 500 internal server error and I cannot figure out what I did. It should be giving a message checking to see if the item is already in the cart and if not send a message saying item is in the cart but I can't get past the 500 internal server error.
Page
#extends('layouts.frontend.frontend')
#section('title')
Distinctly Mine - {{$products->name}}
#endsection
#section('content')
<div class="py-3 mb-4 shadow-sm babyblue border-top">
<div class="container">
<h6 class="mb-0">Collections / {{$products->category->name}} / {{$products->name}}</h6>
</div>
</div>
<div class="container">
<div class="card-shadow shadow-sm product_data">
<div class="card-body">
<div class="row">
<div class="col-md-4 border-right">
<div class="img-hover-zoom img-hover-zoom--xyz card-img-top">
<img src="{{ asset('backend/uploads/products/'.$products->image) }}" class="w-100 h-100" alt="{{$products->name}}">
</div>
</div>
<div class="col-md-8">
<h2 class="mb-0">{{ $products->name}}</h2>
<hr>
<label for="" class="me-3">Price: ${{$products->original_price}}</label>
<p class="mt-3">{!! $products->small_description !!}</p>
<hr>
#if($products->qty > 0)
<label for="" class="badge bg-success text-dark fw-bold">In Stock</label>
#else
<label for="" class="badge bg-danger text dark fw-bold">Out of Stock</label>
#endif
<div class="row mt-2">
<div class="col-md-2">
<input type="hidden" value="{{$products->id}}" class="prod_id">
<label for="Quantity">Quantity</label>
<div class="input-group text-center mb-3" style="width:130px">
<button type="button" class="input-group-text decrement-btn">-</button>
<input type="text" name="quantity" value="1" class="form-control qty-input" />
<button type="button" class="input-group-text increment-btn">+</button>
</div>
</div>
<div class="col-md-10">
<br />
<button type="button" class="btn btn-warning text-dark fw-bold ms-3 float-start"><i class=" me-1 fa fa-heart text-danger me-1"></i>Add To Wishlist</button>
<button type="button" class="btn btn-success ms-3 float-start text-dark fw-bold addToCartBtn"><i class="me-1 fa fa-shopping-cart text-dark me-1"></i>Add to Cart</button>
</div>
<hr>
<h2>Description</h2>
{{$products->description}}
</div>
</div>
</div>
</div>
</div>
</div>
#endsection
#section('scripts')
<script>
$(document).ready(function (){
$('.addToCartBtn').click(function (e){
e.preventDefault();
var product_id = $(this).closest('.product_data').find('.prod_id').val();
var product_qty = $(this).closest('.product_data').find('.qty-input').val();
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
method: "POST",
url: "/add-to-cart",
data: {
'product_id': product_id,
'product_qty': product_qty,
},
dataType: "dataType",
success: function (response){
alert(response.status);
}
});
});
$('.increment-btn').click(function (e){
e.preventDefault();
var inc_value = $('.qty-input').val();
var value = parseInt(inc_value,10);
value = isNaN(value) ? 0 :value;
if(value < 10)
{
value++;
$('.qty-input').val(value);
}
});
$('.decrement-btn').click(function (e){
e.preventDefault();
var dec_value = $('.qty-input').val();
var value = parseInt(dec_value,10);
value = isNaN(value) ? 0 :value;
if(value > 1)
{
value--;
$('.qty-input').val(value);
}
});
});
</script>
#endsection
Route
Route::middleware(['auth'])->group(function (){
Route::post('/add-to-cart',[CartController::class,'addProduct']);
});
Controller
<?php
namespace App\Http\Controllers\Frontend;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
class CartController extends Controller
{
public function addProduct(Request $request)
{
$product_id = $request->input('product_id');
$product_qty = $request->input('product_qty');
if(Auth::check())
{
$prod_check = Product::where('id',$product_id)->first();
if($prod_check)
{
if(Cart::where('prod_id',$product_id)->where('user_id',Auth::id())->exists())
{
return response()->json(['status' => $prod_check->name." Already Added to cart"]);
}else{
$cartItem = new Cart();
$cartItem->prod_id = $product_id;
$cartItem->user_id = Auth::id();
$cartItem->prod_qty = $product_qty;
$cartItem->save();
return response()->json(['status'=>$prod_check->name." Added to cart"]);
}
}
}
else{
return response()->json(['status'=> "Login To Continue"]);
}
}
}

adding and editing events with fullcalendar 5, laravel 9, livewire, and a modal

I have a laravel 9 project with livewire, jetstream, fullcalendar 5, alpine, and tailwind (TALL stack) based off of this project:
https://github.com/LaravelDaily/Laravel-Jetstream-CRUD-Roles
and this tutorial: https://laravel.sillo.org/liveware-fullcalendar/
I have the drag and drop functionality as well as the select and eventClick actions working with livewire that submit event data to the database. I've also got the CRUD functions for events and timeline resources contributing data to the calendar view. I'm trying to employ a modal for both the select function and the eventClick function that adds an event and edits an event respectively.
My issues are currently threefold:
Curently, thanks to #ADyson, I managed to get the livewire calendar element to launch a livewire modal element and retrieve PART of the data. The event title, start, and end times. I'm trying to also include additional fields in the events CRUD for event acronym, city, venue, etc...I think the issue here is that I have not specifically declared these as EventObjects like ExtendedProps.
a)I'm interested specifically in where the livewire calendar class might be edited and imagine it's in this stanza of the view file where the events are constructed from JSON like this
events: JSON.parse(#this.events),
b) and I assume the select and eventClick statements in the same file would need to have the event info function altered to retrieve this information as well.
Below is the current fullcalendar 5 view file calendar.blade.php:
<style>
#calendar-container {
display: grid;
grid-template-columns: 200px 1fr;
padding: 20px;
}
#events {
grid-column: 1;
}
#calendar {
grid-column: 2;
height: 700px;
}
.dropEvent {
background-color: DodgerBlue;
color: white;
padding: 5px 16px;
margin-bottom: 10px;
text-align: center;
display: inline-block;
font-size: 16px;
border-radius: 4px;
cursor:pointer;
}
</style>
<div>
#include('livewire.eventmodal')
<div>
<!-- sidebar -->
<div id="calendar-container" wire:ignore>
<div id="events">
<div data-event='{"title":"Evénement A"}' class="dropEvent">Event One Drag</div>
<div data-event='{"title":"Evénement B"}' class="dropEvent">Event Two Draggable</div>
</div>
<div id="calendar"></div>
</div>
</div>
</div>
#push('scripts')
<script src='https://cdn.jsdelivr.net/npm/fullcalendar-scheduler#5.10.1/main.min.js'></script>
<script>
create_UUID = () => {
let dt = new Date().getTime();
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
let r = (dt + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c == 'x' ? r :(r&0x3|0x8)).toString(16);
});
return uuid;
}
document.addEventListener('livewire:load', function () {
const Calendar = FullCalendar.Calendar;
const calendarEl = document.getElementById('calendar');
const Draggable = FullCalendar.Draggable;
new Draggable(document.getElementById('events'), {
itemSelector: '.dropEvent'
});
const calendar = new Calendar(calendarEl, {
headerToolbar: {
left: 'promptResource prev,next today',
center: 'title',
right: 'resourceMonth,dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
views: {
resourceMonth: {
type: 'resourceTimelineMonth',
buttonText: 'personnel'
}
},
customButtons: {
promptResource: {
text: "+ personnel",
click: function() {
var title = prompt("Name");
if (title) {
calendar.addResource({
title: title
});
fetch("add_resources.php", {
method: "POST",
headers: {
Accept: "application/json"
},
body: encodeFormData({ title: title })
})
.then(response => console.log(response))
.catch(error => console.log(error));
}
}
}
},
initialView: 'resourceTimelineMonth',
locale: '{{ config('app.locale') }}',
events: JSON.parse(#this.events),
resourceAreaHeaderContent: 'Personnel',
resources: JSON.parse(#this.resources),
//'https://fullcalendar.io/api/demo-feeds/resources.json?with-nesting&with-colors',
editable: true,
eventResize: info => #this.eventChange(info.event),
eventDrop: info => #this.eventChange(info.event),
eventReceive: info => {
const id = create_UUID();
info.event.setProp('id', id);
#this.eventAdd(info.event);
},
selectable: true,
select: function(selectionInfo) {
$('#eventModal').modal('show');
},
eventClick: function(info) {
// Display the modal and set the values to the event values.
$('#updateEventModal').modal('show');
$('#updateEventModal').find('#title').val(info.event.title);
$('#updateEventModal').find('#acronym').val(info.event.acronym);
$('#updateEventModal').find('#city').val(info.event.city);
$('#updateEventModal').find('#start').val(info.event.start);
$('#updateEventModal').find('#end').val(info.event.end);
},
});
calendar.render();
});
</script>
<link href='https://cdn.jsdelivr.net/npm/fullcalendar-scheduler#5.11.3/main.min.css' rel='stylesheet' />
#endpush
I have further separated out the actual modal from being included in this file and it's a separate livewire element now called eventmodal.blade.php:
<!-- Insert Modal -->
<div wire:ignore.self class="modal fade" id="eventModal" tabindex="-1" aria-labelledby="eventModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="eventModalLabel">Create Event</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
wire:click="closeModal"></button>
</div>
<form wire:submit.prevent="saveEvent">
<div class="modal-body">
<div class="mb-3">
<label>Event Name</label>
<input type="text" id="title" wire:model="title" class="form-control">
#error('title') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Acronym</label>
<input type="text" id="acronym" wire:model="acronym" class="form-control">
#error('acronym') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event City</label>
<input type="text" id="city" wire:model="city" class="form-control">
#error('city') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Venue</label>
<input type="text" id="venue" wire:model="venue" class="form-control">
#error('venue') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Value</label>
<input type="text" id="value" wire:model="value" class="form-control">
#error('value') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Start</label>
<input type="text" id="start" wire:model="start" class="form-control">
#error('start') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event End</label>
<input type="text" id="end" wire:model="end" class="form-control">
#error('end') <span class="text-danger">{{ $message }}</span> #enderror
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" wire:click="closeModal"
data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Update Event Modal -->
<div wire:ignore.self class="modal fade" id="updateEventModal" tabindex="-1" aria-labelledby="updateEventModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="updateEventModalLabel">Edit Event</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" wire:click="closeModal"
aria-label="Close"></button>
</div>
<form wire:submit.prevent="updateEvent">
<div class="modal-body">
<div class="mb-3">
<label>Event Name</label>
<input type="text" id="title" wire:model="title" class="form-control">
#error('title') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Acronym</label>
<input type="text" id="acronym" wire:model="acronym" class="form-control">
#error('acronym') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event City</label>
<input type="text" id="city" wire:model="city" class="form-control">
#error('city') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Venue</label>
<input type="text" id="venue" wire:model="venue" class="form-control">
#error('venue') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Value</label>
<input type="text" id="value" wire:model="value" class="form-control">
#error('value') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event Start</label>
<input type="text" id="start" wire:model="start" class="form-control">
#error('start') <span class="text-danger">{{ $message }}</span> #enderror
</div>
<div class="mb-3">
<label>Event End</label>
<input type="text" id="end" wire:model="end" class="form-control">
#error('end') <span class="text-danger">{{ $message }}</span> #enderror
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" wire:click="closeModal"
data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Event Modal -->
<div wire:ignore.self class="modal fade" id="deleteEventModal" tabindex="-1" aria-labelledby="deleteEventModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteEventModalLabel">Delete Event</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" wire:click="closeModal"
aria-label="Close"></button>
</div>
<form wire:submit.prevent="destroyEvent">
<div class="modal-body">
<h4>Are you sure you want to delete this data ?</h4>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" wire:click="closeModal"
data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Yes! Delete</button>
</div>
</form>
</div>
</div>
</div>
Secondly, the current modals save functions are not working. I'm lost at where to start...do I need an additional route? Some additional code in the form of a controller?
Lastly, my select function...when you click on a date or a date range in the default fullcalendar view is not picking up the date ranges from fullcalendar as it does without the modal using the standard function like this:
select: arg => {
const title = prompt('Title :');
const id = create_UUID();
if (title) {
calendar.addEvent({
id: id,
title: title,
start: arg.start,
end: arg.end,
allDay: arg.allDay
});
#this.eventAdd(calendar.getEventById(id));
};
calendar.unselect();
Thanks to #ADyson, I've managed to retrieve the start and end date on the eventClick events within my livewire modal with the calendar.blade.php like so:
//...previously posted calendar view code above
select: function(selectionInfo) {
// Display the modal.
// You could fill in the start and end fields based on the parameters
$('#AddEventModal').modal('show');
},
eventClick: function(info) {
// Display the modal and set the values to the event values.
$('#AddEventModal').find('#title').val(info.event.title);
$('#AddEventModal').find('#acronym').val(info.event.acronym);
$('#AddEventModal').find('#start').val(info.event.start);
$('#AddEventModal').find('#end').val(info.event.end);
$('#AddEventModal').modal();
},
...
As you can see, I'm also trying to retrieve data from another field in the events table for 'acronym'. Perhaps the issue lies in either my livewire/calendar.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\Event;
use App\Models\Resource;
use Illuminate\Support\Arr;
class Calendar extends Component
{
public $events, $resources, $title, $acronym, $city, $venue, $value ;
public function eventChange ( $event )
{
$e=Event::find($event['id']) ;
$e->start=$event['start'] ;
$e->acronym=$event['acronym'] ;
if(Arr::exists($event,'end')) {
$e->end=$event['end'];
}
$e->save();
}
public function render()
{
$this->events=json_encode(Event::all());
$this->resources=json_encode(Resource::all());
return view('livewire.calendar');
}
public function eventAdd ( $event )
{
Event::create ( $event );
}
public function eventRemove ( $id )
{
Event::destroy ( $id );
}
public function resourceAdd ( $resources )
{
Resource::create ( $resources );
}
public function resourceRemove ( $id )
{
Resource::destroy ( $id );
}
}
I managed to get the first part of my issue resolved. FullCalendar 5, when retrieving the events via JSON automatically detects extendedProps that can be retrieved with the eventClick method like so:
eventClick: function(info) {
// Display the modal and set the values to the event values.
$('#updateEventModal').modal('show');
$('#updateEventModal').find('#title').val(info.event.title);
$('#updateEventModal').find('#acronym').val(info.event.extendedProps.acronym);
$('#updateEventModal').find('#city').val(info.event.extendedProps.city);
$('#updateEventModal').find('#venue').val(info.event.extendedProps.venue);
$('#updateEventModal').find('#value').val(info.event.extendedProps.value);
$('#updateEventModal').find('#start').val(info.event.start);
$('#updateEventModal').find('#end').val(info.event.end);
},

How to update more table rows at once using Laravel and Vuejs?

I have settings table with 3 columns (id, property, content).
I have seeded some data into settings table and I want to update this table. I am filling some form where each input is presenting one row of the table settings. After submitting that form, I want my table to be updated...
For example, some of my property-content pairs (some of my inputs in this form) are:
background_image - 'image.jpg'
header - 'this is the header'
I am confused because I don't really know what should I send to backend and also I am not sure what should endpoint be...
I hope some of you can help me. If you need more questions, be free to ask me. Thanks in advance, and here is my code:
SettingsController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Requests\UpdateSettings;
use App\Http\Resources\SettingResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Setting;
class SettingController extends Controller
{
public function index()
{
$settings = Setting::all();
return SettingResource::collection($settings);
}
public function store(Request $request)
{
//
}
public function show($id)
{
//
}
public function update(UpdateSettings $request, Setting $setting)
{
$setting->where('property', $request->property)
->update([ 'content' => $request->content ]);
return new SettingResource($setting);
}
public function destroy($id)
{
//
}
}
Settings.vue
<template>
<div class="ml-4 container">
<button #click="submit" id="saveBtn" class="btn btn-primary mt-2">Save Admin Settings</button>
<div class="custom-file mt-3">
<label for="backgroundImage" class="custom-file-label input">Add Background Image</label>
<input id="backgroundImage" #input="errors.clear('background_image')" #change="uploadImageName" type="file" class="custom-file-input btn btn-primary"
style="background: #1d68a7">
<span class="text-danger" v-if="errors.get('background_image')">{{ errors.get('background_image') }}</span>
</div>
<div class="form-group mt-3">
<label for="header">Header</label>
<input :class="{'is-invalid' : errors.get('header')}" #input="errors.clear('header')" v-model="form.header" type="text" class="form-control input" id="header">
<span class="text-danger" v-if="errors.get('header')">{{ errors.get('header') }}</span>
</div>
<div class="form-group mt-3">
<label for="videoOne">Video one</label>
<textarea :class="{'is-invalid' : errors.get('video')}" #input="errors.clear('video')" v-model="form.video" type="text" class="form-control videoBox input" id="videoOne"></textarea>
<span class="text-danger" v-if="errors.get('video')">{{ errors.get('video') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_video"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionOneText">Section One Text</label>
<editor
#input="errors.clear('section_one')"
v-model="form.section_one"
type="text"
class="form-control"
id="sectionOneText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_one')">{{ errors.get('section_one') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_one"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="editableBoxContainer">Editable Box Container</label>
<editor
#input="errors.clear('editable_box')"
v-model="form.editable_box"
type="text"
class="form-control"
id="editableBoxContainer"
>
</editor>
<span class="text-danger" v-if="errors.get('editable_box')">{{ errors.get('editable_box') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_editable_box"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionTwoText">Section Two Text</label>
<editor
#input="errors.clear('section_two')"
v-model="form.section_two"
type="text"
class="form-control"
id="sectionTwoText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_two')">{{ errors.get('section_two') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_two"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionThreeText">Section Three Text</label>
<editor
#input="errors.clear('section_three')"
v-model="form.section_three"
type="text"
class="form-control"
id="sectionThreeText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_three')">{{ errors.get('section_three') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_three"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="form-group mt-3">
<label for="videoTwo">Video two</label>
<textarea :class="{'is-invalid' : errors.get('video_two')}" #input="errors.clear('video_two')" v-model="form.video_two" type="text" class="form-control videoBox input" id="videoTwo"></textarea>
<span class="text-danger" v-if="errors.get('video_two')">{{ errors.get('video_two') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_video_two"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="form-group mt-3">
<label for="linkOne">Link One</label>
<input :class="{'is-invalid' : errors.get('link_one')}" #input="errors.clear('link_one')" v-model="form.link_one" type="text" class="form-control input" id="linkOne">
<span class="text-danger" v-if="errors.get('link_one')">{{ errors.get('link_one') }}</span>
</div>
<div class="form-group mt-3">
<label for="linkTwo">Link Two</label>
<input :class="{'is-invalid' : errors.get('link_two')}" #input="errors.clear('link_two')" v-model="form.link_two" type="text" class="form-control input" id="linkTwo">
<span class="text-danger" v-if="errors.get('link_two')">{{ errors.get('link_two') }}</span>
</div>
</div>
</template>
<script>
import Editor from '#tinymce/tinymce-vue'
import {ToggleButton} from 'vue-js-toggle-button'
import Errors from "../helpers/Errors";
Vue.component('ToggleButton', ToggleButton)
export default {
name: "Settings",
components: {Editor},
data() {
return {
errors: new Errors(),
form: {
background_image: '',
header: '',
video: '',
active_video: null,
section_one: '',
active_section_one: null,
editable_box: '',
active_editable_box: null,
section_two: '',
active_section_two: null,
section_three: '',
active_section_three: null,
video_two: '',
active_video_two: null,
link_one: '',
link_two: '',
},
}
},
mounted() {
},
methods: {
toggleBtn() {
if (this.sectionTwotext) {
this.sectionTwotext = false;
} else {
this.sectionTwotext = true;
}
},
async submit() {
try {
const form = Object.assign({}, this.form);
console.log(form);
let result = Object.keys(form).map(function (key) {
return [key, form[key]];
});
console.log(result[0])
result._method = 'PUT';
for(let i=0; i<=15; i++) {
let objectResult = Object.assign({}, result[i]);
console.log('objectResult')
console.log(objectResult)
objectResult._method = 'PUT';
const {data} = await axios.post(`/api/setting/${i}`, objectResult);
}
} catch (error) {
console.log(error.response.data.errors);
this.errors.record(error.response.data.errors);
}
},
uploadImageName() {
let image = document.getElementById("backgroundImage");
this.form.background_image = image.files[0].name;
console.log(image.files[0].name);
},
}
}
</script>
<style scoped>
#saveBtn {
border-radius: 0;
}
.input {
border-radius: 0;
}
.videoBox {
height: 300px;
}
.toggleButton {
margin-left: auto;
}
</style>
My async Submit function is not OK, I was just trying something...
Thanks in advance!
I would consider having a single endpoint on your api where you submit the entire form.
api.php
Create a route that we can submit our axios request to:
Route::put('/settings', 'Api\SettingController#update')->name('api.settings.update');
Note that I have namespaced the controller as you will likely have a web and api version of this controller. Mixing your api and web actions in a single controller is not advisable.
web.php
This is a standard web route which will display a blade view containing the vue component. We pass through the settings which will then be passed to the Settings.vue component as a prop.
Route::get('/settings', 'SettingController#edit')->name('settings.edit');
edit.blade.php
<settings :settings="{{ $settings }}"></settings>
Settings.vue
axios.put('/api/settings', {
header: this.form.header,
background_image: this.form.background_image
... other fields to be submitted
})
.then(success => {
console.log(success);
})
.catch(error => {
console.log(error);
});
SettingController.php
public function edit()
{
return view('settings.edit', ['settings' => Setting::all()]);
}
Api\SettingController.php
public function update(Request $request)
{
$validator = Validator::make(
$request->all(),
[
'header' => ['required', 'string'],
'background_image' => ['required', 'string']
... other fields to be validated
]
);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
foreach ($validator->validated() as $property => $content) {
Setting::where('property', $property)->update(['content' => $content]);
}
return response()->json(['success' => true], 200);
}
You might want to consider splitting settings into groups and submitting groups rather than the entire form, or submitting each setting individually (where it makes sense to do so).

Expand Collapse Icons with Vue Js and Bootstrap

I have a Bootstrap accordion with the expand collapse panels,which works fine.
But when expanded it should display minus icon which is not working well for me.
When I click to expand all the panel's icons are changed to minus instead of just changing the one which was expanded.
I have my code in the Vue JS template as below
Am calling the toggle function onclick to toggle the icons, but its doing it for all the other panels too.
How can I fix this? Thanks in advance.
Vue.component('accordion', {
data: function () {
return {
alerts: [],
sound: '',
collapsed:true
}
},
template: `
<div>
<div v-for="(alert, index ) in alerts" class="panel panel-default">
<div class="panel-heading" v-bind:style="'background-color:'+alert.color" role="tab" v-bind:id="'heading'+index" >
<a role="button" data-toggle="collapse" data-parent="#accordion"
v-bind:href="'#collapse'+index" aria-expanded="true" v-on:click="toggle">
<i id="collapseExpand" v-show="collapsed" class="more-less fa fa-plus"></i>
<i id="collapseExpand" v-show="!collapsed" class="more-less fa fa-minus"></i>
<h4 class="panel-title">#{{ alert.description }}</h4></a>
</div>
<div v-bind:id="'collapse'+index" class="panel-collapse collapse" role="tabpanel">
<div class="panel-body">
<div>#{{ alert.comment }}</div>
<div class="row">
<form v-bind:id="'form_'+index" v-bind:name="'form_'+index" v-bind:action="route" method="POST" style="display: inline;">
<input type="hidden" name="_token" :value="csrf">
<input type ="hidden" v-bind:id="'trigger_id_'+alert.triggerid" name = "trigger_id" v-bind:value="alert.triggerid">
<div class="col-lg-12">
<div class="input-group">
<input type="text" v-bind:id="'ack_msg_'+alert.triggerid" name="ack_msg" class="form-control"
placeholder="Acknowledge Message...">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Save</button>
</span>
</div>
</div>
</form>
</div>
</div>
<div class="panel-footer">#{{ alert.timestamp }}</div>
</div>
</div>
<input type="hidden" id="audioFile" name="audioFile" v-bind:value="sound">
</div>`,
mounted: function () {
this.loadData();
setInterval(function () {
this.loadData();
}.bind(this), 1000);
},
methods: {
loadData: function () {
$.get('{{ route('getAlertsPersistent') }}', function (response) {
this.alerts = response;
this.sound = this.alerts[0].sound
}.bind(this));
},
toggle(){
this.collapsed = !this.collapsed ;
}
},
});
new Vue({el: '#accordion'});
You want to separate your accordion item that you want to loop. In that way you can have isolated states in each component.
<div>
<accordion-item
v-for="(alert, index) in alerts"
:alert="alert"
:key="index">
</accordion-item>
</div>
inside your <accordion-item/> you should have collapsed inside your data
The other way around is storing the toggled items in array.
export default {
data: () => ({
toggled: []
}),
methods: {
isActive (item) {
return this.toggled.indexOf(item) >= 0
},
toggleItem (item) {
const index = this.toggled.indexOf(item)
if (index >= 0) {
this.toggled.splice(index, 1)
return
}
this.toggled.push(item)
}
}
}
So you can use it now as follows
<a
role="button"
data-toggle="collapse"
data-parent="#accordion"
v-bind:href="'#collapse'+index"
aria-expanded="true"
v-on:click="toggleItem(index)">
<i
:class="[isActive(index) ? 'fa-minus' : 'fa-plus']"
class="more-less fa"></i>
<h4 class="panel-title">#{{ alert.description }}</h4>
</a>
btw you're looping an id=collapseExpand which will cause you problem. instead try :id="'collapseExpand' + index"

Laravel 5.1 and DropzoneJS 4.2 can‘t upload photo

I am using Laravel 5.1 and DropzoneJS 4.2 to upload images,I copied the code of this example http://www.dropzonejs.com/bootstrap.html,and modified it.
click the start button,can not send http request.
This is my wiew:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css">
<style>
html, body {
height: 100%;
}
#actions {
margin: 2em 0;
}
/* Mimic table appearance */
div.table {
display: table;
}
div.table .file-row {
display: table-row;
}
div.table .file-row > div {
display: table-cell;
vertical-align: top;
border-top: 1px solid #ddd;
padding: 8px;
}
div.table .file-row:nth-child(odd) {
background: #f9f9f9;
}
/* The total progress gets shown by event listeners */
#total-progress {
opacity: 0;
transition: opacity 0.3s linear;
}
/* Hide the progress bar when finished */
#previews .file-row.dz-success .progress {
opacity: 0;
transition: opacity 0.3s linear;
}
/* Hide the delete button initially */
#previews .file-row .delete {
display: none;
}
/* Hide the start and cancel buttons and show the delete button */
#previews .file-row.dz-success .start,
#previews .file-row.dz-success .cancel {
display: none;
}
#previews .file-row.dz-success .delete {
display: block;
}
</style>
</head>
<body>
<div class="container" id="container">
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
upload photos
</button>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
<div class="modal-dialog dropzone" id="my-awesome-dropzone" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="myModalLabel">upload photos</h4>
</div>
<div class="modal-body">
<div id="actions" class="row">
<div class="col-lg-7">
<span class="btn btn-success fileinput-button dz-clickable">
<i class="glyphicon glyphicon-plus"></i>
<span>add photos</span>
</span>
</div>
</div>
<div class="table table-striped files" id="previews">
<div id="template" class="file-row">
<!-- This is used as the file preview template -->
<div><span class="preview"><img data-dz-thumbnail/></span></div>
<div>
<p class="name" data-dz-name></p>
<strong class="error text-danger" data-dz-errormessage></strong>
</div>
<div>
<p class="size" data-dz-size></p>
<div class="progress progress-striped active" role="progressbar"
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="progress-bar progress-bar-success" style="width:0%;"
data-dz-uploadprogress></div>
</div>
</div>
<div>
<button class="btn btn-primary start">
<i class="glyphicon glyphicon-upload"></i>
<span>Start</span>
</button>
<button data-dz-remove class="btn btn-warning cancel">
<i class="glyphicon glyphicon-ban-circle"></i>
<span>Cancel</span>
</button>
<button data-dz-remove class="btn btn-danger delete">
<i class="glyphicon glyphicon-trash"></i>
<span>Delete</span>
</button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<script src="http://ajax.useso.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/js/bootstrap.js"></script>
<script src="https://cdn.bootcss.com/dropzone/4.2.0/min/dropzone.min.js"></script>
</body>
</html>
This is the script:
<script>
Dropzone.autoDiscover = false;
</script>
<script>
// Get the template HTML and remove it from the doument
var previewNode = document.querySelector("#template");
previewNode.id = "";
var previewTemplate = previewNode.parentNode.innerHTML;
previewNode.parentNode.removeChild(previewNode);
var myDropzone = new Dropzone(document.body, { // Make the whole body a dropzone
url: "fileupload", // Set the url
thumbnailWidth: 80,
thumbnailHeight: 80,
parallelUploads: 20,
previewTemplate: previewTemplate,
autoQueue: false, // Make sure the files aren't queued until manually added
previewsContainer: "#previews", // Define the container to display the previews
clickable: ".fileinput-button" // Define the element that should be used as click trigger to select files.
});
myDropzone.on("addedfile", function (file) {
// Hookup the start button
file.previewElement.querySelector(".start").onclick = function () {
myDropzone.enqueueFile(file);
};
});
// Update the total progress bar
myDropzone.on("totaluploadprogress", function (progress) {
document.querySelector("#total-progress .progress-bar").style.width = progress + "%";
});
myDropzone.on("sending", function (file) {
// Show the total progress bar when upload starts
document.querySelector("#total-progress").style.opacity = "1";
// And disable the start button
file.previewElement.querySelector(".start").setAttribute("disabled", "disabled");
});
// Hide the total progress bar when nothing's uploading anymore
myDropzone.on("queuecomplete", function (progress) {
document.querySelector("#total-progress").style.opacity = "0";
});
// Setup the buttons for all transfers
// The "add files" button doesn't need to be setup because the config
// `clickable` has already been specified.
document.querySelector("#actions .start").onclick = function () {
myDropzone.enqueueFiles(myDropzone.getFilesWithStatus(Dropzone.ADDED));
};
document.querySelector("#actions .cancel").onclick = function () {
myDropzone.removeAllFiles(true);
};
</script>
This is the route:
Route::resource('fileupload', 'FileController');
These is the controller:
public function imageUpload(Requests\StorePhotoPostRequest $request)
{
$this->wrongTokenAjax();
$file = \Input::file('file');
$destinationPath = 'uploads/';
$extension = $file->getClientOriginalExtension();
$fileName = \Auth::user()->id . '_' . time() . '.' . $extension;
$upload_success = \Input::file('file')->move($destinationPath, $fileName);
if ($upload_success) {
return \Response::json('success', 200);
} else {
return \Response::json('error', 400);
}
}
public function wrongTokenAjax()
{
if (\Session::token() !== \Request::get('_token')) {
$response = [
'status' => false,
'errors' => 'Wrong Token',
];
return \Response::json($response);
}
}
This is the request:
public function rules()
{
return [
'file' => 'required|image(jpeg,jpg,png,bmp,gif)',
];
}
I think the problem is that you don't send the CSRF Token with the upload.
First of all add the CSRF Token in the <head>:
<meta name="csrf_token" content="{{ csrf_token() }}">
Try to replace this
myDropzone.on("sending", function (file) {
// Show the total progress bar when upload starts
document.querySelector("#total-progress").style.opacity = "1";
// And disable the start button
file.previewElement.querySelector(".start").setAttribute("disabled", "disabled");
});
With this:
myDropzone.on("sending", function (file, xhr, formData) {
formData.append("_token", $('[name="csrf_token"]').attr('content'));
});

Resources