File: D:/HostingSpaces/SBogers10/farmfun.komma.pro/app/Komma/Kms/Core/Attributes/MultiSelect.php
<?php
namespace App\Komma\Kms\Core\Attributes;
use App\Komma\Kms\Core\Attributes\Models\SelectOption;
use App\Komma\Kms\Core\Attributes\Models\SelectOptionInterface;
use App\Komma\Kms\Core\Attributes\Traits\ExplanationTrait;
use App\Komma\Kms\Core\Attributes\Traits\LabelTrait;
use App\Komma\Kms\Core\Attributes\Traits\PlaceholderTextTrait;
use App\Komma\Kms\Core\Attributes\Traits\ReadOnlyTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
class MultiSelect extends Attribute implements Attributable
{
use LabelTrait;
use PlaceholderTextTrait;
use ReadOnlyTrait;
use ExplanationTrait;
private $sortable = false;
/**
* @var string The option (value) that needs to excluded from the list. Usually because it represents something that you currently are in or are using
*/
private $exclude;
/** @var SelectOptionInterface[] */
private $items;
protected $defaultValue;
/** @var string */
protected $autoSaveUrl;
/** @var int */
protected $maxItemsToSelect;
/** @var array first item refers to a class and the second to
* a method on that class that returns SelectOptionInterface's to
* fill the autocomplete input with data when rendered
*/
protected $provider;
/** @var string Indicates that the model this autocomplete works for can be linked to another model */
protected $canBeLinkedWith;
public function __construct($value = null)
{
parent::__construct();
$this->maxItemsToSelect = 20;
$this->autoSaveUrl = false;
if ($value) {
$this->setValue($value);
}
$this->canBeLinkedWith = '';
$this->items = [];
}
/**
* Sets the items you can select
*
* @param SelectOptionInterface[] $items
* @return $this
*/
public function setItems(array $items)
{
$items = array_filter($items, function ($item) {
return is_a($item, SelectOptionInterface::class);
});
$this->items = $items;
return $this;
}
/**
* Set items through function calling
*/
public function setItemsThroughFunction(string $function)
{
}
/**
* Returns the items that you can select.
* Normally as an array unless you specify the parameter as true.
* In that latter case it will return a json string.
*
* @param bool $asJson
* @return SelectOptionInterface[]|string
*/
public function getItems(bool $asJson = false)
{
if (! $asJson) {
return $this->items;
}
$jsonPreparementArray = [];
/** @var SelectOptionInterface $item */
foreach ($this->items as $item) {
//Make sure that these keys match the ones in select.blade.php and ???
$jsonPreparementArray[] = $this->buildJsonRepresentation($item);
}
$jsonItems = implode(',', $jsonPreparementArray);
$json = '['.$jsonItems.']';
return $json;
}
/**
* Sets the id of the model that needs to be displayed
*
* @param string $value
* @return $this
*/
public function setValue(string $value)
{
$this->value = $value;
return $this;
}
/**
* Return the value as a string or as a json object for the javascript code.
*
* @param bool $asJson
* @return string
*/
public function getValue($asJson = false): string
{
if ($asJson == false) {
if (! empty($this->value)) {
return $this->value;
}
return '';
} else {
//Return an object
$jsonItemPreparementArray = [];
$selectedItemId = $this->value;
if ($selectedItemId) {
$jsonItemPreparementArray['id'] = $selectedItemId;
}
$json = json_encode($jsonItemPreparementArray);
return $json;
}
}
/**
* Builds a json representation of a SelectOptionsInterface implementation.
* You should use these representation in the javascript code.
* @param SelectOptionInterface $selectOption
* @return string
*/
private function buildJsonRepresentation(SelectOptionInterface $selectOption): string
{
$optionJsonPreparementData = [];
$optionJsonPreparementData['value'] = $selectOption->getValue();
$optionJsonPreparementData['content'] = $selectOption->getContent();
$optionJsonPreparementData['htmlContent'] = $selectOption->getHtmlContent();
$json = json_encode($optionJsonPreparementData);
return $json;
}
/**
* Returns a view that visually represents this attribute
*/
public function render()
{
//get id from model
$currentId = null;
foreach (request()->route()->parameters as $parameter) {
if (is_a($parameter, Model::class)) {
$currentId = $parameter->id;
}
}
$this->fillWithDataFromProvider();
return view('kms/attributes.multiSelect', [
'attribute' => $this,
'currentId' => $currentId,
'dataset' => $this->buildAutoCompleteDataJson(),
]);
}
private function buildAutoCompleteDataJson()
{
$dataSet = [];
$items = $this->getItems();
foreach ($items as $selectOption) {
/** @var SelectOptionInterface $selectOption */
$dataSet[] = [
'id' => $selectOption->getValue(),
'value' => str_replace(' ', '.', $selectOption->getHtmlContent()),
];
}
$dataSet = json_encode($dataSet);
return $dataSet;
}
/**
* Pre-fill the autocomplete input based on the provider. The provider is an array like this:
* [
* 'App\Komma\Users\Kms\Service',
* 'getOptionsForSelect'
* ]
*
* And can be given using the canBeLinkedWith method with a parameter like this: "App\Komma\Users\Kms\Service@getOptionsForSelect"
*/
private function fillWithDataFromProvider()
{
if ((isset($this->provider) && count($this->provider)) == 0 || (isset($this->items) && count($this->items) > 0)) {
return;
} //If the provider is not configured or we already have items, we return;
if (! class_exists($this->provider[0])) {
throw new \RuntimeException('MultiSelect: The provider class "'.$this->provider[0].'" does not exist. So the autocomplete input could not be filled automatically.');
}
//Validate that the class on the provider exists and that we can call it.
if (! method_exists($this->provider[0], $this->provider[1]) || ! is_callable([$this->provider[0], $this->provider[1]])) {
throw new \RuntimeException('MultiSelect: The provider class method "'.$this->provider[1].'" was not callable on provider class "'.$this->provider[0].'"');
}
/** @var Collection $selectOptions */
try {
$providerInstance = App::make($this->provider[0]); //Creates an instance of the provider class.
$selectOptions = call_user_func([$providerInstance, $this->provider[1]]);
} catch (\Exception $e) {
throw new \RuntimeException('MultiSelect: Failed using the provider class method "'.$this->provider[1].'" from provider class "'.$this->provider[0].'": '.$e->getMessage());
}
if (! is_a($selectOptions, Collection::class)) {
throw new \RuntimeException('MultiSelect: The provider class method "'.$this->provider[1].'" from provider class "'.$this->provider[0].'" did not return a collection ("'.Collection::class.'") while it was expected. It did return: '.typeOf($selectOptions));
}
$selectOptions->each(function ($selectOption) {
if (! is_a($selectOption, SelectOption::class)) {
throw new \RuntimeException('MultiSelect: On or more items from the collection from provider class "'.$this->provider[0].'", method "'.$this->provider[1].'" was not a "'.SelectOption::class.'"');
}
});
$this->items = $selectOptions;
}
/**
* @return mixed
*/
public function getDefaultValue()
{
return $this->defaultValue;
}
/**
* @param mixed $defaultValue
* @return MultiSelect
*/
public function setDefaultValue($defaultValue): self
{
$this->defaultValue = $defaultValue;
return $this;
}
/**
* @return string
*/
public function getAutoSaveUrl(): string
{
return $this->autoSaveUrl;
}
/**
* This url will receive a comma delimited list of item ids that need to be saved.
*
* @param string $autoSaveUrl
* @return MultiSelect
*/
public function setAutoSaveUrl(string $autoSaveUrl): self
{
$this->autoSaveUrl = $autoSaveUrl;
return $this;
}
/**
* @return int
*/
public function getMaxItemsToSelect(): int
{
return $this->maxItemsToSelect;
}
/**
* @param int $maxItemsToSelect
* @return MultiSelect
*/
public function setMaxItemsToSelect(int $maxItemsToSelect)
{
$this->maxItemsToSelect = $maxItemsToSelect;
return $this;
}
/**
* Indicates that the Autocomplete input will be pre-filled with data from a provider (for example "Userservice@getOptionsForSelect").
*
* @param string $provider
* @return MultiSelect
*/
public function fillWith(string $provider): self
{
$parts = explode('@', $provider);
if ($parts === false || count($parts) !== 2) {
throw new \InvalidArgumentException('"Attribute: The provider parameter must contain an @ that separates a class name and a method on that class');
}
$this->provider = $parts;
return $this;
}
/**
* Returns true if the autocomplete input will be filled by the given provider if it was specified. false if not.
*/
public function isFilledByProvider(): bool
{
return count($this->provider) == 2;
}
/**
* Define a model using this function as Attributable type.
* The AttributeableService used by the componentService then knows what kind of model you select
* using the multiSelect.
*
* @param string $model
*
* @return MultiSelect
*/
public function canBeLinkedWith(string $model): self
{
if (! is_a($model, Model::class, true)) {
throw new \InvalidArgumentException("Attribute: The attribute cannot be linked to a class that is no child of '".Model::class."'");
}
$this->canBeLinkedWith = $model;
return $this;
}
/**
* @return string Returns the name of a model that can be linked to the model the autocomplete works for
*/
public function getCanBeLinkedWith(): string
{
return $this->canBeLinkedWith;
}
/**
* @return bool
*/
public function isSortable(): bool
{
return $this->sortable;
}
/**
* @return TextArea
*/
public function enableSortable(): self
{
$this->sortable = true;
return $this;
}
}