src/Popup.php
<?php
declare(strict_types=1);
namespace Atk4\Ui;
use Atk4\Ui\Js\Jquery;
use Atk4\Ui\Js\JsExpression;
use Atk4\Ui\Js\JsExpressionable;
/**
* Implement popup view.
*
* Popup are views that will be display when triggered by another view.
*
* Popup can add content statically or dynamically via a callback.
*
* When adding a popup to the page, you need to specify it's trigger element
* and the event needed on the trigger element in order to display the popup.
*/
class Popup extends View
{
public $ui = 'popup';
/**
* The view activating the popup.
* Usually the view where popup is attached to,
* unless target is supply.
*
* @var View|string|null object view or a string id
*/
public $triggerBy;
/** @var string Js event that trigger the popup. */
public $triggerOn;
/** @var string Default position of the popup in relation to target element. */
public $position = 'top left';
/**
* When set to false, target is the triggerBy element.
* Otherwise, you can supply a View object where popup will be shown.
*
* @var View|false
*/
public $target = false;
/** @var array<string, mixed> Popup options as defined in Fomantic-UI popup module. */
public $popOptions = [];
/** @var Callback|null The callback use to generate dynamic content. */
public $cb;
/**
* The dynamic View to load inside the popup
* when dynamic content is use.
*
* @var View|array<mixed>
*/
public $dynamicContent = [View::class];
/**
* Whether or not dynamic content is cache.
* If cache is on, will retrieve content only the first time popup is required.
*
* @var bool
*/
public $useCache = false;
/** @var string Min width for a dynamic popup. */
public $minWidth;
/** @var string Min height for a dynamic popup. */
public $minHeight;
/**
* Whether or not the click event triggering popup
* should stop event propagation.
*
* Ex: when Popup is located inside a sortable grid header.
* Set this options to true in order to activate just the popup
* and stop sort action.
*
* @var bool
*/
public $stopClickEvent = false;
/**
* @param View|array<string, mixed> $triggerBy
*/
public function __construct($triggerBy = [])
{
if (is_object($triggerBy)) {
$triggerBy = ['triggerBy' => $triggerBy];
}
parent::__construct($triggerBy);
}
#[\Override]
protected function init(): void
{
parent::init();
if ($this->triggerOn === null) {
if ($this->triggerBy instanceof Menu
|| $this->triggerBy instanceof MenuItem
|| $this->triggerBy instanceof Dropdown
) {
$this->triggerOn = 'hover';
} elseif ($this->triggerBy instanceof Button) {
$this->triggerOn = 'click';
}
}
$this->popOptions = array_merge($this->popOptions, [
'popup' => $this,
'on' => $this->triggerOn,
'position' => $this->position,
'target' => $this->target,
]);
}
/**
* Set callback for loading content dynamically.
* Callback will receive a view attached to this popup
* for adding content to it.
*
* @param \Closure(View): void $fx
*/
#[\Override]
public function set($fx = null)
{
if (!$fx instanceof \Closure) {
throw new \TypeError('$fx must be of type Closure');
}
$this->cb = Callback::addTo($this);
if (!$this->minWidth) {
$this->minWidth = '80px';
}
if (!$this->minHeight) {
$this->minHeight = '45px';
}
$this->cb->set(function () use ($fx) {
// create content view to pass to callback
$content = $this->add($this->dynamicContent);
$fx($content);
// only render our content view, PopupService will replace content with this one
$this->cb->terminateJsonIfCanTerminate($content);
});
return $this;
}
/**
* @param View|string $trigger
*
* @return $this
*/
public function setTriggerBy($trigger)
{
$this->triggerBy = $trigger;
return $this;
}
/**
* Allow to pass a target selector by name, i.e. a CSS class name.
*
* @param string $name
*
* @return $this
*/
public function setTargetByName($name)
{
$this->popOptions['target'] = $name;
return $this;
}
/**
* Whether popup stay open when user hover on it or not.
*
* @return $this
*/
public function setHoverable(bool $isOverable = true)
{
$this->popOptions['hoverable'] = $isOverable;
return $this;
}
/**
* Set a popup options as defined in Fomantic-UI popup module.
*
* @param mixed $option
*
* @return $this
*/
public function setOption(string $name, $option)
{
$this->popOptions[$name] = $option;
return $this;
}
/**
* Return JS action need to display popup.
* When a grid is reloading, this method can be call
* in order to display the popup once again.
*
* @return Jquery
*/
public function jsPopup(): JsExpressionable
{
$selector = $this->triggerBy;
if ($this->triggerBy instanceof Form\Control) {
$selector = '#' . $this->triggerBy->name . '_input';
}
$chain = new Jquery($selector);
$chain->popup($this->popOptions);
if ($this->stopClickEvent) {
$chain->on('click', new JsExpression('function (e) { e.stopPropagation(); }'));
}
return $chain;
}
#[\Override]
protected function renderView(): void
{
if ($this->triggerBy) {
$this->js(true, $this->jsPopup());
}
if ($this->cb) {
$this->setAttr('data-url', $this->cb->getJsUrl());
$this->setAttr('data-cache', $this->useCache ? 'true' : 'false');
}
if ($this->minWidth) {
$this->setStyle('min-width', $this->minWidth);
}
if ($this->minHeight) {
$this->setStyle('min-height', $this->minHeight);
}
parent::renderView();
}
}