Back

Third Party

Alert with Sweet Alert

Let me see the code 👀
routes/web.php
1<?php
2 
3Route::get('/third-party', function () {
4 return view('third-party');
5});
resources/views/third-party.blade.php
1<x-app-layout>
2 <x-slot name="title">Third Party - Larva Interactions</x-slot>
3 <a href="/" class="underline text-blue-500">Back</a>
4 <h1 class="text-2xl mb-4">Third Party</h1>
+ <livewire:third-party.alert />
6 <livewire:third-party.select/>
7 <livewire:third-party.text-editor/>
8 <livewire:third-party.nested/>
9</x-app-layout>
resources/views/livewire/third-party/alert.blade.php
1<?php
2 
3use Livewire\Attributes\On;
4use Livewire\Volt\Component;
5 
6new class extends Component {
7 public $visible = true;
8 
9 #[On('destroy')]
10 function destroy()
11 {
12 // Mock delete process in server with sleep function
13 sleep(3);
14 $this->dispatch('alert:ok', data: [
15 'message' => 'Item has been deleted!'
16 ]);
17 $this->visible = false;
18 }
19}; ?>
20 
21@push('scripts')
22<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js" crossorigin="anonymous"></script>
23<script>
24 function confirmDestroy(el) {
25 swal({
26 title: 'Warning!',
27 text: el.getAttribute('data-message'),
28 icon: 'warning',
29 buttons: true,
30 dangerMode: true,
31 })
32 .then((willDelete) => {
33 if (willDelete) {
34 swal('Data is being deleted. Please wait...', {
35 icon: 'info',
36 closeOnClickOutside: false,
37 button: false
38 });
39 window.Livewire.dispatch('destroy');
40 } else {
41 swal('Your data still saved!')
42 }
43 })
44 }
45 
46 window.addEventListener('alert:ok', function (e) {
47 swal({
48 icon: 'success',
49 text: e.detail.data.message
50 });
51 })
52</script>
53@endpush
54 
55<div>
56 <h2 class="text-xl mb-4">Alert with Sweet Alert</h2>
57 @if ($visible)
58 <div class="flex items-center gap-2 mb-4">
59 <p>Delete me and I will say good bye!</p>
60 <button onclick="confirmDestroy(this);" data-message="Are you sure to delete this item?" class="bg-red-500 p-1 rounded text-white">Delete</button>
61 </div>
62 @else
63 <p class="mb-4">Good bye! 👋🏼</p>
64 @endif
65</div>

Delete me and I will say good bye!

Select with Tom Select

Let me see the code 👀
routes/web.php
1<?php
2 
3Route::get('/third-party', function () {
4 return view('third-party');
5});
resources/views/third-party.blade.php
1<x-app-layout>
2 <x-slot name="title">Third Party - Larva Interactions</x-slot>
3 <a href="/" class="underline text-blue-500">Back</a>
4 <h1 class="text-2xl mb-4">Third Party</h1>
5 <livewire:third-party.alert />
+ <livewire:third-party.select/>
7 <livewire:third-party.text-editor/>
8 <livewire:third-party.nested/>
9</x-app-layout>
resources/views/livewire/third-party/nested.blade.php
1<?php
2 
3use Livewire\Volt\Component;
4 
5new class extends Component {
6 
7 public $selectedOption = '1';
8 public $selectedOptions = ['3', '4'];
9 
10 public function with(): array
11 {
12 return [
13 'md_content' => markdown_convert(resource_path('docs/third-party/select.md')),
14 'options' => [
15 [
16 'id' => '1',
17 'name' => 'Jujutsu Kaisen'
18 ],
19 [
20 'id' => '2',
21 'name' => 'One Piece'
22 ],
23 [
24 'id' => '3',
25 'name' => 'Elusive Samurai'
26 ],
27 [
28 'id' => '4',
29 'name' => 'Black Clover'
30 ]
31 ]
32 ];
33 }
34 
35 function clearSelectedOption()
36 {
37 $this->selectedOption = '';
38 }
39 
40 function clearSelectedOptions()
41 {
42 $this->selectedOptions = [];
43 }
44}; ?>
45 
46@push('styles')
47<link href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.css" rel="stylesheet">
48<style>
49 [x-cloak] {
50 display: none !important;
51 }
52</style>
53@endpush
54 
55@push('scripts')
56<script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
57@endpush
58 
59<div>
60 <h2 class="text-xl mb-4">Select with Tom Select</h2>
61 {!! $md_content !!}
62 <h3 class="text-lg">Single option</h3>
63 <div wire:ignore>
64 <select x-data="{
65 tomSelectInstance: null,
66 options: {{ collect($options) }},
67 items: $wire.entangle('selectedOption').live
68 }"
69 x-init="tomSelectInstance = new TomSelect($refs.select, {
70 valueField: 'id',
71 labelField: 'name',
72 searchField: 'name',
73 options: options,
74 items: items,
75 onItemAdd: function () {
76 this.setTextboxValue('');
77 }
78 }); $watch('items', (value, oldValue) => {
79 const result = JSON.parse(JSON.stringify(value));
80 if (result.length === 0) {
81 tomSelectInstance.clear();
82 }
83 });"
84 x-ref="select"
85 x-cloak
86 id="selectedOption"
87 wire:model.live="selectedOption"
88 autocomplete="off">
89 
90 </select>
91 </div>
92 <p class="mb-4">Selected option: {{ @json_encode($selectedOption) }}</p>
93 
94 <h3 class="text-lg">Multiple options</h3>
95 <div wire:ignore>
96 <select x-data="{
97 tomSelectInstance: null,
98 options: {{ collect($options) }},
99 items: $wire.entangle('selectedOptions').live
100 }"
101 x-init="tomSelectInstance = new TomSelect($refs.select, {
102 valueField: 'id',
103 labelField: 'name',
104 searchField: 'name',
105 options: options,
106 items: items,
107 maxItems: 2,
108 onItemAdd: function () {
109 this.setTextboxValue('');
110 this.refreshOptions();
111 }
112 }); $watch('items', (value, oldValue) => {
113 const result = JSON.parse(JSON.stringify(value));
114 if (result.length === 0) {
115 tomSelectInstance.clear();
116 }
117 })"
118 x-ref="select"
119 x-cloak
120 id="selectedOptions"
121 wire:model.live="selectedOptions"
122 multiple
123 autocomplete="off">
124 
125 </select>
126 </div>
127 <p class="mb-4">Selected options: {{ @json_encode($selectedOptions) }}</p>
128</div>

Single option

Selected option: "1"

Multiple options

Selected options: ["3","4"]

Text Area with Trix Editor

Let me see the code 👀
routes/web.php
1<?php
2 
3Route::get('/third-party', function () {
4 return view('third-party');
5});
resources/views/third-party.blade.php
1<x-app-layout>
2 <x-slot name="title">Third Party - Larva Interactions</x-slot>
3 <a href="/" class="underline text-blue-500">Back</a>
4 <h1 class="text-2xl mb-4">Third Party</h1>
5 <livewire:third-party.alert />
6 <livewire:third-party.select/>
+ <livewire:third-party.text-editor/>
8 <livewire:third-party.nested/>
9</x-app-layout>
resources/views/livewire/third-party/nested.blade.php
1<?php
2 
3use Livewire\Volt\Component;
4 
5new class extends Component {
6 public $content = '';
7 
8 public function with(): array
9 {
10 return [
11 'md_content' => markdown_convert(resource_path('docs/third-party/text-editor.md')),
12 ];
13 }
14 
15 public function submitEditor()
16 {
17 // May be add some validation here!
18 }
19 
20 public function clear()
21 {
22 $this->content = '';
23 }
24}; ?>
25 
26@push('styles')
27<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trix/dist/trix.min.css" crossorigin="anonymous">
28@endpush
29 
30@push('scripts')
31<script src="https://cdn.jsdelivr.net/npm/trix/dist/trix.umd.min.js" crossorigin="anonymous"></script>
32@endpush
33 
34<div
35 x-data="{
36 content: $wire.entangle('content'),
37 upload(event) {
38 const data = new FormData();
39 data.append('file', event.attachment.file);
40 
41 fetch('/upload-file', {
42 headers: {
43 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').getAttribute('content')
44 },
45 method: 'POST',
46 credentials: 'same-origin',
47 body: data
48 })
49 .then(response => response.json())
50 .then(data => {
51 event.attachment.setAttributes({
52 url: data.image_url
53 });
54 });
55 }
56 }">
57 <h2 class="text-xl mb-4">Text Area with Trix Editor</h2>
58 {!! $md_content !!}
59 <form class="mb-4" wire:submit="submitEditor">
60 <div wire:ignore
61 x-on:trix-change="content = $event.target.value"
62 x-on:trix-initialize="$refs.text.editor.loadHTML(content)">
63 
64 <input type="hidden" id="x" x-model="content">
65 
66 <trix-editor
67 x-on:trix-file-accept="event.preventDefault()"
68 x-on:trix-attachment-add="upload"
69 x-ref="text"
70 input="x" class="trix-content"></trix-editor>
71 </div>
72 <button class="bg-blue-500 text-white p-2 rounded my-4">Submit</button>
73 <button type="button" x-on:click="$wire.clear(); $refs.text.editor.loadHTML('');" class="bg-gray-200 p-2 rounded my-4">Clear</button>
74 {!! $content !!}
75 </form>
76</div>

Nested Sortable

Usually, I use nested sortable to display tree menu then drag and drop menu position. Imagine you build dynamic menus for your site.

Let me see the code 👀
routes/web.php
1<?php
2 
3Route::get('/third-party', function () {
4 return view('third-party');
5});
resources/views/third-party.blade.php
1<x-app-layout>
2 <x-slot name="title">Third Party - Larva Interactions</x-slot>
3 <a href="/" class="underline text-blue-500">Back</a>
4 <h1 class="text-2xl mb-4">Third Party</h1>
5 <livewire:third-party.alert />
6 <livewire:third-party.select/>
7 <livewire:third-party.text-editor/>
+ <livewire:third-party.nested/>
9</x-app-layout>
resources/views/livewire/third-party/nested.blade.php
1<?php
2 
3use Livewire\Attributes\On;
4use Livewire\Volt\Component;
5 
6new class extends Component {
7 public $sortableData;
8 public $sortableItem;
9 
10 #[On('set-data')]
11 function setSortableData($data)
12 {
13 $this->sortableData = $data;
14 }
15 
16 public function submitSortable()
17 {
18 if (!empty($this->sortableItem)) {
19 $this->dispatch('nested-sortable-store-item', data: [
20 'item' => $this->sortableItem
21 ]);
22 $this->sortableItem = '';
23 }
24 }
25}; ?>
26 
27@push('styles')
28<style>
29 /* Nested sortable style */
30 ul {
31 list-style-type: none;
32 margin: 0;
33 }
34 
35 .list-group-item {
36 border: 1px solid rgba(0, 0, 0, .125);
37 padding: .5rem;
38 cursor: move;
39 }
40 
41 .list-group-item:last-child {
42 border-bottom-left-radius: .25rem;
43 border-bottom-right-radius: .25rem;
44 }
45 
46 .list-group-item:hover {
47 z-index: 0;
48 }
49 
50 .nested-sortable,
51 .nested-1,
52 .nested-2,
53 .nested-3 {
54 margin-top: 5px;
55 }
56 
57 .nested-1 {
58 background-color: #e6e6e6;
59 }
60 
61 .nested-2 {
62 background-color: #cccccc;
63 }
64 
65 .nested-3 {
66 background-color: #b3b3b3;
67 }
68</style>
69@endpush
70 
71@push('scripts')
72<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
73<script>
74 let nestedSortables = Array.from(document.querySelectorAll('.nested-sortable'));
75 for (let i = 0; i < nestedSortables.length; i++) {
76 new Sortable(nestedSortables[i], {
77 group: 'nested',
78 animation: 150,
79 fallbackOnBody: true,
80 swapThreshold: 0.65,
81 onEnd: function (evt) {
82 let newData = JSON.stringify(serialize(root));
83 console.log(newData);
84 Livewire.dispatch('set-data', { data: newData });
85 }
86 });
87 }
88 
89 const nestedQuery = '.nested-sortable';
90 const identifier = 'id';
91 const root = document.getElementById('wrapper-nested-sortable');
92 function serialize(sortable) {
93 const serialized = [];
94 const children = [].slice.call(sortable.children);
95 for (const i in children) {
96 const nested = children[i].querySelector(nestedQuery);
97 const parentId = children[i].closest(nestedQuery).getAttribute('data-parent-id');
98 serialized.push({
99 id: children[i].dataset[identifier],
100 parent_id: parentId,
101 children: nested ? serialize(nested) : [],
102 order: (parseInt(i) + 1).toString(),
103 });
104 }
105 return serialized;
106 }
107 
108 window.addEventListener('nested-sortable-store-item', (e) => {
109 document.getElementById('wrapper-nested-sortable').appendChild(
110 document
111 .createRange()
112 .createContextualFragment(`<li data-id="${e.detail.data.item}" data-parent="0"
113 class="list-group-item nested-1">${e.detail.data.item}
114 </li>`));
115 });
116</script>
117@endpush
118 
119<div>
120<h2 class="text-xl mb-4">Nested Sortable</h2>
121 <p class="mb-4">Usually, I use nested sortable to display tree menu then drag and drop menu position. Imagine you build dynamic menus for your site.</p>
122 <form class="flex gap-2" wire:submit.prevent="submitSortable">
123 <input type="text" wire:model="sortableItem" placeholder="Insert an item" class="flex-1 rounded">
124 <button type="submit" class="bg-blue-500 p-2 rounded text-white">Submit</button>
125 </form>
126 <div wire:ignore>
127 {!! build_sortable_list(get_menus()) !!}
128 </div>
129 @if (!empty($sortableData))
130 <pre style="overflow-x: scroll;">
131 <code>
132 {{ @json_encode($sortableData) }}
133 </code>
134 </pre>
135 @else
136 <p>Please drag and drop the list above to see updated list.</p>
137 @endif
138</div>
  • Item 1.1
    • Item 2.1
    • Item 2.2
      • Item 3.1
      • Item 3.2
      • Item 3.3
      • Item 3.4
  • Item 1.2
  • Item 1.3
  • Item 1.4
    • Item 2.3
    • Item 2.4
    • Item 2.5
    • Item 2.6
  • Item 1.5

Please drag and drop the list above to see updated list.