nepttune/ajaxselect

View on GitHub
src/Form/TAjaxSelect.php

Summary

Maintainability
A
45 mins
Test Coverage
<?php

/**
 * This file is part of Nepttune (https://www.peldax.com)
 *
 * Copyright (c) 2019 Václav Pelíšek (info@peldax.com)
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <https://www.peldax.com>.
 */

declare(strict_types = 1);

namespace Nepttune\Form;

use \Nette\Application\UI\Presenter;
use \Nette\ComponentModel\IComponent;

trait TAjaxSelect
{
    /** @var callable */
    private $callback;

    /** @var callable */
    private $onchange;

    /** @var \Nette\Caching\Cache */
    private $storage;

    private $optionsRaw;

    public function setCallback(callable $callback): self
    {
        $this->callback = $callback;
        return $this;
    }

    public function setOnchange(callable $onchange) : self
    {
        $this->onchange = $onchange;
        return $this;
    }

    public function setCacheStorage(\Nette\Caching\Cache $storage) : self
    {
        $this->storage = $storage;
        return $this;
    }

    public function getControl(): \Nette\Utils\Html
    {
        $this->initiateItems();

        $attrs = [];
        $control = parent::getControl();

        if ($this->callback) {
            $attrs['data-ajaxselect'] = $this->getForm()->getPresenter()->link(
                $this->lookupPath(Presenter::class) . IComponent::NAME_SEPARATOR . self::CALLBACK_SIGNAL_NAME . '!'
            );
        }

        if ($this->onchange) {
            $attrs['data-onchange'] = $this->getForm()->getPresenter()->link(
                $this->lookupPath(Presenter::class) . IComponent::NAME_SEPARATOR . self::ONCHANGE_SIGNAL_NAME . '!'
            );
        }

        $control->addAttributes($attrs);
        return $control;
    }

    public function setValue($value): void
    {
        $this->initiateItems($value);

        parent::setValue($value);
    }

    public function signalReceived(string $signal): void
    {
        $presenter = $this->lookup(Presenter::class);

        if (!$presenter->isAjax() || $this->isDisabled()) {
            return;
        }

        switch ($signal) {
            case self::CALLBACK_SIGNAL_NAME:
                $presenter->sendJson($this->getData($presenter->getParameter('q')));
                break;
            case self::ONCHANGE_SIGNAL_NAME:
                $this->fireOnchange($presenter->getParameter('s'));
                break;
        }
    }

    /**
     * @param string $query
     * @param array|int|null $default
     * @return array
     */
    private function getData(string $query = '', $default = null) : \Nepttune\Form\ResultObjectSet
    {
        if ($this->callback === null) {
            throw new \Nette\InvalidStateException('Callback for "' . $this->getHtmlId() . '" is not set.');
        }

        if ($this->storage instanceof \Nette\Caching\Cache) {
            $cacheKey = $this->getHtmlId() . '_' . $query . '_' . (\is_array($default) ? \implode(',', $default) : $default);
            $result = $this->storage->load($cacheKey);

            if (\is_array($result) && !empty($result)) {
                return $result;
            }
        }

        $data = \call_user_func($this->callback, $query, $default);

        if (\is_array($data)) {
            $data = \Nepttune\Form\ResultObjectSet::fromArray($data);
        }

        if (!$data instanceof \Nepttune\Form\ResultObjectSet) {
            throw new \Nette\InvalidStateException('Callback for "' . $this->getHtmlId() . '" must return array.');
        }

        if ($this->storage instanceof \Nette\Caching\Cache) {
            $this->storage->save($cacheKey, $data);
        }

        return $data;
    }

    private function initiateItems($value = null): void
    {
        if ($this instanceof \Nette\Forms\Controls\MultiChoiceControl) {
            $this->value ??= [];
        }

        $value = $value ?? $this->value;
        $data = [];

        if (\in_array($value, [null, '', []], true)) {
            if (\count($this->items) > 0) {
                return;
            }

            $data = $this->getData();
        } else {
            $data = $this->getData('', $value);
        }

        parent::setItems($data->getRawData());
        parent::setDisabled($data->getDisabled());
    }

    private function fireOnchange($selected = null) : void
    {
        if ($this->onchange === null) {
            throw new \Nette\InvalidStateException('Onchange for "' . $this->getHtmlId() . '" is not set.');
        }

        \call_user_func($this->onchange, $selected);
    }
}