Livewire nestable is resetting order on drop - laravel

using the following library to add sorting and nesting https://www.hesamurai.com/nested-sort/latest, I'm also using the pre-rendered list option to populate the items (https://www.hesamurai.com/nested-sort/latest/pre-rendered).
Now my html structure looks like the following:
<div x-data class="pageContent__content__components mb10">
<span class="subTitle">Menu items</span>
<ol x-init="initSort($refs.draggable)" x-ref='draggable' id="draggable"
class='nested-sort nested-sort--enabled'>
#foreach($navigation->navigation_items as $item)
<li wire:key='{{ $item->id }}' draggable='true'
data-id="{{ $item->id }}"> {{ $item->navigatable->page_name ?? $item->navigatable->blog_title }}
<i wire:click="showAlert('{{ $item->id }}')"
class="fa-solid fa-trash-can"></i>
</li>
#if($item->children->count() > 0)
<ol data-id="{{ $item->id }}">
#foreach($item->children as $child)
<li draggable='true'
data-id="{{ $child->navigatable->id }}"> {{ $child->navigatable->page_name ?? $child->navigatable->blog_title }}
<i wire:click="showAlert('{{ $child->navigatable->id }}')"
class="fa-solid fa-trash-can"></i>
</li>
#endforeach
</ol>
#endif
#endforeach
</ol>
</div>
With the scripts being included at the bottom of the page
#push('scripts')
#once
<script src="https://cdn.jsdelivr.net/npm/nested-sort#5/dist/nested-sort.umd.min.js"></script>
<script type='text/javascript'>
function initSort(ref) {
return {
nested: new NestedSort({
el: ref,
actions: {
onDrop: function(data) {
#this.
call('saveMenuOrder', data);
},
},
}),
};
}
</script>
#endonce
#endpush
Now, adding and removing items to the list works as intended, but reordering or nesting any item causes livewire to reset the list to it's initial state.
If I where to add a wire:ignore to the <ol> that technically fixes the issue of livewire updating the DOM, but that also means that, when adding or removing items it no longer updates the list without manually refreshing the page.
My backend component looks like this:
// the model containing the items to be displayed (via a HasMany relation)
public NavigationModel $navigation;
// the method that is called everytime the onDrop action fires (the $data array contains the new order of elements)
public function saveMenuOrder(array $data): void
{
foreach ($data as $menuItem) {
$menuItemObject = $this->navigation->navigation_items->find(
$menuItem['id']
);
$menuItemObject->order = $menuItem['order'];
if (isset($menuItem['parent'])) {
$menuItemObject->parent_id = $menuItem['parent'];
} else {
$menuItemObject->parent_id = null;
}
$menuItemObject->save();
}
}
And that's basically it for the component, all I want is for livewire to update the list without messing up the DOM elements created by the library.
any ideas?
alpineJS is also installed, if that's a better solution that's also accepted.
thanks!
--- Edit
What I currenly have:
I converted the laravel foreach to an alpine x-for:
<div class="pageContent__content" style="grid-area: unset">
<div x-data='initSort($refs.draggable)'
wire:ignore
class="pageContent__content__components mb10">
<span class="subTitle">Menu items</span>
<ol class='nested-sort' x-ref='draggable' id="draggable">
<template x-for="item in items">
<li draggable="true" :data-id='item.id'>
<div class='nested-sort-item'>
<div class='nested-sort-item__text' x-text='item.text' />
<div class='nested-sort-item__actions'>
<button type='button' class='nested-sort-item__actions__button'
#click='$wire.showAlert(item.id)'
>
<i class='fas fa-trash-alt'></i>
</button>
</div>
</div>
</li>
</template>
</ol>
</div>
</div>
and rewrote the init function:
function initSort(ref) {
return {
navigation_items: #js($this->navigation_items),
get items() {
return this.navigation_items;
},
init() {
new NestedSort({
el: ref,
actions: {
onDrop: function(data) {
},
},
});
},
};
}
Now I can't seem to figure out how to access the navigation_items inside of my onDrop action, simply using this.navigation_items or this.items console.logs undefined.

Related

Laravel Vue Loading Side Bar Menu API

I am getting menu like below from api request with laravel vue.
{"data":[{"id":67,"slug":"link","name":"Dashboard","href":"\/","hasIcon":true,"icon":"mdi mdi-home-account","iconType":"coreui","sequence":1}]}
and my componant is below and displaying well but click event not woking , when i mannully put this json data --> menu then every thing working good. so i think mounted is not working as per expectation in this senario. i tried like passing it from another component as props , then putting it in data --> menu but not working as well any idea ?
<template>
<div class="vertical-menu">
<div data-simplebar="init" class="h-100 mm-show">
<div id="sidebar-menu">
<ul class="metismenu list-unstyled" id="side-menu">
<li v-for="(item, index) in menu" :key="index">
<router-link :to="item.href ? item.href : ''" :class="item.slug === 'dropdown' ? 'has-arrow' : '' ">
<i :class="item.icon"></i>
<span :data-key="item.datakey">{{item.name}}</span>
</router-link>
<ul class="sub-menu" aria-expanded="false">
<li v-for="subItem in item.elements" :key="subItem.name" >
<router-link :to="subItem.href ? subItem.href : ''"> {{subItem.name}} </router-link>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name:"DashboardLeftBar",
props:['app_base_url'],
data(){
return {
menu: null
}
},
mounted(){
this.menuSideBar();
},
methods:{
menuSideBar(){
let token= localStorage.getItem('jwt');
let crl=JSON.parse(localStorage.getItem('user')).crl;
axios.post(myconstant.APPICATION_API_URL+'/getmenu',{
token: token,
crl: crl,
}).then((response) => {
this.menu = response.data.data;
});
}
}
}
</script>

Laravel 2 submit buttons in the same form

I am building a CRUD with Laravel. Each category hasMany attachments and each attachment belongsTo a category.
In the category.edit view I want to give the user the possibility of deleting the attachments (singularly) from the Category. I tried this method but it did not work:
Registering route for the attachment:
Route::group(['middleware' => ['auth']], function () {
Route::delete('attachment/{id}', 'AttachmentController#delete')->name('attachment');
});
Handling the delete building the AttachmentController#delete method:
class AttachmentController extends Controller
{
public function delete($id) {
$toDelete = Attachment::findOrFail($id);
$toDelete->delete();
return redirect()->back();
}
}
In the CategoryController (edit method), I fetch the attachments linked to each category to be rendered inside the view:
public function edit($category)
{
$wildcard = $category;
$category = Category::findOrFail($wildcard);
$attachments = App\Category::findOrFail($wildcard)->attachments()->get()->toArray();
return view('category.edit', [
'category' => $category,
'attachments' => $attachments
]);
}
In the view, I render the attachment and the button to delete. I am fully aware of the error of having a form inside another form, nevertheless I do not know antoher approach to submit this delete request.
// update Category form
#foreach ($attachments as $attachment)
<div class="row">
<div class="col-4">
<img style="width: 100%;" src={{ $attachment['url'] }} alt="">
</div>
<div class="col-4">
<div>
<p class="general_par general_url">{{ $attachment['url'] }}</p>
</div>
</div>
<div class="col-4">
<form action="{{ route('attachment', $attachment['id']) }}" method="POST">
#csrf
#method('DELETE')
<button type="submit" class="btn btn-danger">Delete Image</button>
</form>
</div>
</div>
<hr>
#endforeach
// end of update Category form
Should I build a deleteAttachment method inside the CategoryController? If so, how can I still submit the Delete request? Also, if any other Model in the future will have attachments, should I build a deleteAttachment method inside each controller? That is cumbersome. Thanks in advance
if you don't like to use form, then use tag:
<a class="btn btn-danger" href="{{ route('attachment', $attachment['id']) }}">Delete Image</a>
And redefine the route to Route::get(...)
(or maybe use ajax for POST method if that is required)

Don't see a div into a vue component

I have a vue component with a div and a button and into another div I have two components
<script>
export default {
name: 'excursion-backend-component',
methods:{
doRedirection: function () {
window.location = APP_URL+"/excursiones/create";
}
},
mounted() {
console.log(APP_URL+"/excursiones/create");
console.log("aaaa");
}
}
</script>
<template>
<div class="container">
<h3>Adicionar excursión</h3>
<div style="text-align: right">
<a class="btn btn-primary" :href="doRedirection"><i class="fa fa-plus"></i> Adicionar</a>
</div>
<br>
<div>
<excursion-list-component></excursion-list-component>
<excursion-add-component></excursion-add-component>
</div>
</div>
</template>
But I don't see the button on the navigator.
What is wrong?
Here is how I see the page
:href="" expecting string with url to navigation, but you using method.
Use #click instead :href, if you want to use method.
<a #click="doRedirect">... </a>
Or, may be, move it to computed block
computed: {
excursionesUrl () {
return APP_URL+"/excursiones/create";
}
}

Passing the Div id to another vue component in laravel

I created a simple real-time chat application using vue js in laravel.
I am having a problem with the automatic scroll of the div when there is a new data.
What I want is the div to automatically scroll down to the bottom of the div when there is a new data.
Here is my code so far.
Chat.vue file
<template>
<div class="panel-block">
<div class="chat" v-if="chats.length != 0" style="height: 400px;" id="myDiv">
<div v-for="chat in chats" style="overflow: auto;" >
<div class="chat-right" v-if="chat.user_id == userid">
{{ chat.chat }}
</div>
<div class="chat-left" v-else>
{{ chat.chat}}
</div>
</div>
</div>
<div v-else class="no-message">
<br><br><br><br><br>
There are no messages
</div>
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid"></chat-composer>
</div>
</template>
<script>
export default {
props: ['chats','userid','adminid'],
}
</script>
ChatComposer.vue file
<template>
<div class="panel-block field">
<div class="input-group">
<input type="text" class="form-control" v-on:keyup.enter="sendChat" v-model="chat">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" v-on:click="sendChat">Send Chat</button>
</span>
</div>
</div>
</template>
<script>
export default{
props: ['chats','userid','adminid'],
data() {
return{
chat: ''
}
},
methods: {
sendChat: function(e) {
if(this.chat != ''){
var data = {
chat: this.chat,
admin_id: this.adminid,
user_id: this.userid
}
this.chat = '';
axios.post('/chat/sendChat', data).then((response) => {
this.chats.push(data)
})
this.scrollToEnd();
}
},
scrollToEnd: function() {
var container = this.$el.querySelector("#myDiv");
container.scrollTop = container.scrollHeight;
}
}
}
</script>
I am passing a div id from the Chat.vue file to the ChatComposer.vue file.
As you can see in the ChatComposer.vue file there is a function called scrollToEnd where in it gets the height of the div id from Chat.vue file.
When the sendchat function is triggered i called the scrollToEnd function.
I guess hes not getting the value from the div id because I am getting an error - Cannot read property 'scrollHeight' of null.
Any help would be appreciated.
Thanks in advance.
the scope of this.$el.querySelector will be limited to only ChatComposer.vue hence child component can not able to access div of parent component #myDiv .
You can trigger event as below in ChatComposer
this.$emit('scroll');
In parent component write ScollToEnd method and use $ref to assign new height
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid" #scroll="ScollToEnd"></chat-composer>
..

Laravel:Passing variable from controller to view

chatcontroller.php function returns variable to view:
public function getChat()
{
$message = chat_messages::all();
return View::make('home',compact($message));
}
this is my route:
Route::get('/getChat', array('as' => 'getChat','uses' => 'ChatController#getChat'));
this is my home.blade.php:
#extends('layouts.main')
#section('content')
<div class="container">
<h2>Welcome to Home Page!</h2>
<p> <a href="{{ URL::to('/logout') }}" > Logout </a></p>
<h1>Hello <span id="username">{{ Auth::user()->username }} </span>!</h1>
<div id="chat_window">
</div>
<input type="text" name="chat" class="typer" id="text" autofocus="true" onblur="notTyping()">
</div>
<ul>
#foreach($message as $msg)
<li>
{{ $msg['sender_username']," says: ",$msg['message'],"<br/>" }}
</li>
#endforeach
</ul>
<script src="{{ asset('js/jquery-2.1.3.min.js') }}"></script>
<script src="{{ asset('js/chat.js') }}"></script>
#stop
I am trying to send result returned by select query to view from controller.
when I do this from homecontroller.php then it works fine.
if I try to pass from controller which I have defined it gives error message as:Undefined variable.
I have used the extends \BaseController do i need to do anything else to access my controller variable from view.
please suggest some tutorial if possible for same.
Verify the route to be sure it uses the new controller:
Route::get('user/profile', array('uses' => 'MyDefinedController#showProfile'));
First of all check your routes, as Matei Mihai says.
There are two different ways to pass data into your view;
$items = Item::all();
// Option 1
return View::make('item.index', compact('items'));
// Option 2
return View::make('item.index')->with('items', $items); // same code as below
// Option 3
View::share('item.index', compact('items'));
return View::make('item.index);
You can also do this:
$this->data['items'] = Item::all();
return View::make('item.index', $this->data);

Resources