DEV.RULES
Digital agency
290
Livewire

Карточки заметок с поиском и бесконечной прокруткой с помощью Livewire.

От Святослав Торн, публикация от 2 July, 2020

Верстка новой карточки:

Честно говоря начали бесить разноразмерные карточки представляющие статьи. Решил что надо прийти к более дружелюбному для глаз стилю. Сейчас 100% экрана занимают карточки, отведем под них 2/3 ширины, на оставшейся трети позже придумаем еще какой нибудь виджет.

Слегка переработал дизайн карточки, она стала простой и без картинки. Вот код:

<div class="mb-4">
    <div class="w-full px-10 py-6 bg-white rounded-lg shadow-md">
        <div class="flex justify-between items-center">
            <span class="font-light text-gray-600">Laravel</span>
            <span class="font-light text-gray-600">4 дня назад</span>
        </div>
        <div class="mt-2">
            <a class="text-2xl text-gray-700 font-bold hover:text-gray-600" href="https://sk3.devrules.ru/article/1">Лалала Ларавел</a>
            <p class="mt-2 text-base text-gray-600">Текст с кратким описанием заметки</p>
        </div>
        <div class="flex justify-between items-center mt-4">
            <a class="text-blue-600 hover:underline" href="https://sk3.devrules.ru/article/1">Читать дальше...</a>
            <div>
                <a class="flex items-center" href="https://vk.com/torns" title="Связь со мной">
                    <img class="mx-4 w-10 h-10 object-cover rounded-full hidden sm:block" src="{{ asset('img/profile-img.jpeg') }}" alt="avatar">
                    <h1 class="text-gray-700 font-bold">Святослав Торн</h1>
                </a>
            </div>
         </div>
     </div>
</div>

Создаем livewire component

php artisan make:livewire NotesCardComponent

Соответсвенно имеем два файла - вью и класс. Переносим верстку во вью компонента и подключаем в шаблоне, обновляем страницу, все ок ничего не изменилось.

<div class="w-full sm:w-1/3">

</div>
<div class="w-full sm:w-2/3 px-1">
    @livewire('notes-card-component')
</div>

Начнем с самого начала, покажем список карточек из БД:

public $defaultLimit = 5;
public $currentLimit = 5;
public $notesList;

public function getNotesList()
{
    $this->notesList = Notes::where(['status' => 1])
                ->orderBy('id', 'desc')
                ->limit($this->currentLimit)
                ->with('user')
                ->get();
}

public function render()
{
    $this->getNotesList();
    return view('livewire.notes-card-component');
}

Итоговый вариант вохможно выглядит не самым лучшим образом, но он работает. Бесит что при нажатии показать еще, обновляется весь компонент и вьюпорт кидает вверх. Пробовал сделать с оффсетами и мерджить массивы старый и новый. Результат тот же. Вот итоговый код.

<?php

namespace App\Http\Livewire;

use App\Notes;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Validator;
use Livewire\Component;

class NotesCardComponent extends Component
{
    // Задаем количество карточек
    public $defaultLimit = 5;
    public $currentLimit = 0;
    public $notesList;
    public $search, $loadFlag;

    // Установка флага говорит о том что сейчас идет не первичная загрузка, а подгрузка
    public function load(): void
    {
        $this->loadFlag = 1;
    }

    public function getNotesList(): void
    {
            $this->changeLimit();
            $this->notesList = Notes::where(['status' => 1])
                    ->orderBy('id', 'desc')
                    ->limit($this->currentLimit)
                    ->with('user')
                    ->get();
    }

    public function getNotesListSearch() :void
    {
        $this->notesList = Notes::where(['status' => 1])
            ->orderBy('id', 'desc')
            ->where('content', 'like', '%'.$this->search.'%')
            ->orWhere('title', 'like', '%'.$this->search.'%')
            ->with('user')
            ->get();
    }

    public function changeLimit(): void
    {
        $this->currentLimit += $this->defaultLimit;
    }

    public function validateSearch(): bool
    {
        $v = Validator::make(['search' => $this->search], [
            'search' => 'string',
        ]);
        return $v->fails();
    }

    public function render()
    {
        if((strlen($this->search) > 3) && !$this->validateSearch()){
            $this->loadFlag = 0;
            $this->getNotesListSearch();
        }else if (!$this->loadFlag){
            $this->currentLimit = 0;
            $this->getNotesList();
        }else{
            $this->getNotesList();
        }
        return view('livewire.notes-card-component');
    }
}
<div>
        <div class="mb-4">
            <div class="w-full  rounded shadow flex relative">
                <div class="absolute mt-4 ml-5">
                    <i class="fas fa-search text-gray-400 text-2xl"></i>
                </div>
                <input wire:model.debounce.500ms="search" class="w-full pl-16 pr-10 py-4 bg-white text-gray-800 rounded" placeholder="Строка поиска">
            </div>
        </div>
    @foreach($notesList as $note)
        <div class="mb-4">
            <div class="w-full px-10 py-6 bg-white rounded-lg shadow-md">
                <div class="flex justify-between items-center">
                    <span class="font-light text-gray-600">{{ $note->main_tag }}</span>
                    <span class="font-light text-gray-600">{{ $note->created_at->diffForHumans() }}</span>
                </div>
                <div class="mt-2">
                    <a class="text-2xl text-gray-700 font-bold hover:text-gray-600" href="{{ route('note.page', $note->slug) }}">{{ $note->title }}</a>
                    <p class="mt-2 text-base text-gray-600">{{ $note->preview }}</p>
                </div>
                <div class="flex justify-between items-center mt-4">
                    <a class="text-blue-600 hover:underline" href="{{ route('note.page', $note->slug) }}">Читать дальше...</a>
                    <div>
                        <a class="flex items-center" href="https://vk.com/torns" title="Связь со мной">
                            <img class="mx-4 w-10 h-10 object-cover rounded-full hidden sm:block" src="{{ asset('img/profile-img.jpeg') }}" alt="avatar">
                            <h1 class="text-gray-700 font-bold">{{ $note->user->name }}</h1>
                        </a>
                    </div>
                </div>
            </div>
        </div>
    @endforeach
    @if((count($notesList) >= $currentLimit) && (strlen($search) <= 3))
        <div class="mb-4">
            <div class="w-full  rounded shadow flex justify-center">
                <a wire:click="load" href="#" class="w-full px-10 py-4 bg-blue-500 text-white text-center rounded hover:bg-blue-600 transition duration-200">Показать еще</a>
            </div>
        </div>
    @endif
</div>

Как то так)