Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/card improvements #2025

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
25 changes: 25 additions & 0 deletions demos/_includes/CustomCard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Atk4\Ui\Demos;

use Atk4\Ui\Card;
use Atk4\Ui\View;

class CustomCard extends Card
{
public function getButtonContainer()
{
if (!$this->btnContainer) {
// $this->btnContainer = $this->addExtraContent(new View(['ui' => 'buttons']));
$this->btnContainer = $this->add(new View(['ui' => 'buttons bottom attached'])); // attach buttons to bottom
$this->getButtonContainer()->addClass('wrapping');
if ($this->hasFluidButton) {
$this->getButtonContainer()->addClass('fluid');
}
}

return $this->btnContainer;
}
}
21 changes: 20 additions & 1 deletion demos/collection/card-deck.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@

use Atk4\Data\Model;
use Atk4\Ui\Button;
use Atk4\Ui\Card;
use Atk4\Ui\CardDeck;
use Atk4\Ui\Form;
use Atk4\Ui\Header;
use Atk4\Ui\UserAction\ExecutorFactory;
use Atk4\Ui\View;

/** @var \Atk4\Ui\App $app */
require_once __DIR__ . '/../init-app.php';

Header::addTo($app, ['Card Deck', 'size' => 1, 'subHeader' => 'Card can be display in a deck, also using model action.']);
Header::addTo($app, ['Card Deck - Booking', 'size' => 1, 'subHeader' => 'Card can be display in a deck, also using model action.']);

$countries = new Country($app->db);
$countries->addCalculatedField('cost', ['type' => 'atk4_money', 'expr' => function (Country $country) {
Expand Down Expand Up @@ -50,3 +52,20 @@
$deck = CardDeck::addTo($app, ['noRecordScopeActions' => ['request_info'], 'singleScopeActions' => ['book']]);

$deck->setModel($countries, ['cost'], [$countries->fieldName()->iso, $countries->fieldName()->iso3]);

// Another Card Deck example below
Header::addTo($app, ['Card Deck - Editable', 'size' => 1, 'subHeader' => 'Cards can also use custom templates and have full editing support like Grid.']);

$countries = new Country($app->db);
$countries->addCalculatedField('iso_lower', ['expr' => function (Country $m) {return strtolower($m->get($m->fieldName()->iso)); }]);
$deck = CardDeck::addTo($app, [
'cardHolder' => [View::class, 'ui' => 'cards six'],
'ipp' => 6 * 2,
'card' => [
CustomCard::class,
'ui' => 'card atk-card blue',
'addFields' => false,
'defaultTemplate' => __DIR__ . '/templates/card.html',
],
]);
$deck->setModel($countries);
11 changes: 11 additions & 0 deletions demos/collection/templates/card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div data-id="{$dataId}" {$attributes}>
<div class="content">
<i class="{$iso_lower} flag"></i>
<i class="right floated like icon"></i>
<i class="right floated star icon"></i>
</div>
{$Image}
{$Content}
{$Section}
{$ExtraContent}
</div>
28 changes: 23 additions & 5 deletions src/Card.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ class Card extends View
/** @var View|null The button Container for Button */
public $btnContainer;

/** @var bool Do we want to add fields in section or just set values in template */
public $addFields = true;

/** @var bool Display model field as table inside card holder content */
public $useTable = false;

/** @var bool Use Field label with value data. */
/** @var bool Use Field label with value data */
public $useLabel = false;

/** @var string Default executor class. */
/** @var string Default executor class */
public $executor = UserAction\ModalExecutor::class;

protected function init(): void
Expand Down Expand Up @@ -166,7 +169,18 @@ public function addContent(View $view)
}

/**
* If Fields are past with $model that field will be add
* Returns array of names of fields to automatically include them in view.
* This includes all editable or visible fields of the model.
*
* @return array
*/
protected function getModelFields(Model $model)
{
return array_keys($model->getFields(['editable', 'visible']));
}

/**
* If Fields are past with $model that field will be added
* to the main section of this card.
*
* @param array<int, string>|null $fields
Expand All @@ -178,13 +192,17 @@ public function setModel(Model $entity, array $fields = null): void
parent::setModel($entity);

if ($fields === null) {
$fields = array_keys($this->model->getFields(['editable', 'visible']));
$fields = $this->getModelFields($entity);
}

$this->template->trySet('dataId', (string) $this->model->getId());

View::addTo($this->getSection(), [$entity->getTitle(), 'class.header' => true]);
$this->getSection()->addFields($entity, $fields, $this->useLabel, $this->useTable);
if ($this->addFields) {
$this->getSection()->addFields($entity, $fields, $this->useLabel, $this->useTable);
} else {
$this->template->set($this->model);
}
}

/**
Expand Down
27 changes: 16 additions & 11 deletions src/CardDeck.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class CardDeck extends View

public $defaultTemplate = 'card-deck.html';

/** @var class-string<View> Card type inside this deck. */
/** @var class-string<View>|array Card type inside this deck. */
public $card = Card::class;

/** @var bool Whether card should use table display or not. */
Expand Down Expand Up @@ -85,10 +85,10 @@ class CardDeck extends View
];

/** @var array A collection of menu button added in Menu. */
private $menuActions = [];
protected $menuActions = [];

/** @var string|null The current search query string. */
private $query;
protected $query;

protected function init(): void
{
Expand All @@ -99,11 +99,7 @@ protected function init(): void
$this->container = $this->add($this->container);

if ($this->menu !== false && !is_object($this->menu)) {
$this->menu = $this->add(Factory::factory([Menu::class, 'activateOnClick' => false], $this->menu), 'Menu');

if ($this->search !== false) {
$this->addMenuBarSeach();
}
$this->addMenuBar();
}

$this->cardHolder = $this->container->add($this->cardHolder);
Expand All @@ -114,6 +110,15 @@ protected function init(): void
}
}

protected function addMenuBar(): void
{
$this->menu = $this->add(Factory::factory([Menu::class, 'activateOnClick' => false], $this->menu), 'Menu');

if ($this->search !== false) {
$this->addMenuBarSeach();
}
}

protected function addMenuBarSeach(): void
{
$view = View::addTo($this->menu->addMenuRight()->addItem()->setElement('div'));
Expand Down Expand Up @@ -147,7 +152,7 @@ public function setModel(Model $model, array $fields = null, array $extra = null
if ($count) {
foreach ($this->model as $m) {
/** @var Card */
$c = $this->cardHolder->add(Factory::factory([$this->card], ['useLabel' => $this->useLabel, 'useTable' => $this->useTable]));
$c = $this->cardHolder->add(Factory::factory((array) $this->card, ['useLabel' => $this->useLabel, 'useTable' => $this->useTable]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never use (array) cast, change the def. value to be an array seed instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will work on that a bit more probably tomorrow. Looks like there are more seed related things to fix in this (and Card) classes for consistency with other views and also to ease extending.

$c->setModel($m, $fields);
if ($extra) {
$c->addExtraFields($m, $extra, $this->extraGlue);
Expand Down Expand Up @@ -262,7 +267,7 @@ protected function jsCreateNotifier(Model\UserAction $action, string $msg = null
*
* @return mixed
*/
private function getReloadArgs()
protected function getReloadArgs()
{
$args = [];
if ($this->paginator !== false) {
Expand All @@ -278,7 +283,7 @@ private function getReloadArgs()
/**
* Return proper action need to setup menu or action column.
*/
private function getModelActions(string $appliesTo): array
protected function getModelActions(string $appliesTo): array
{
if ($appliesTo === Model\UserAction::APPLIES_TO_SINGLE_RECORD && $this->singleScopeActions !== []) {
$actions = array_map(fn ($v) => $this->model->getUserAction($v), $this->singleScopeActions);
Expand Down
6 changes: 3 additions & 3 deletions src/CardSection.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ public function addFields(Model $model, array $fields, bool $useLabel = false, b
/**
* Add fields label and value to section.
*/
private function addSectionFields(Model $model, array $fields, bool $useLabel = false): void
protected function addSectionFields(Model $model, array $fields, bool $useLabel = false): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private methods are often added as the API might not be well defined. If private, we do not have to care, but if protected, we need to support the API in a long term. Please confirm you understand this and also there are no simillar private methods in other components.

{
foreach ($fields as $field) {
if ($model->titleField === $field) {
continue;
}
$label = $model->getField($field)->getCaption();
$value = $this->getApp()->uiPersistence->typecastSaveField($model->getField($field), $model->get($field));
if ($useLabel) {
$label = $model->getField($field)->getCaption();
$value = $label . $this->glue . $value;
}

Expand All @@ -85,7 +85,7 @@ private function addSectionFields(Model $model, array $fields, bool $useLabel =
/**
* Add field into section using a CardTable View.
*/
private function addTableSection(Model $model, array $fields): void
protected function addTableSection(Model $model, array $fields): void
{
$cardTable = CardTable::addTo($this, ['class' => $this->tableClass]);
$cardTable->setModel($model, $fields);
Expand Down
2 changes: 1 addition & 1 deletion src/HtmlTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ protected function _setOrAppend($tag, string $value = null, bool $encodeHtml = t
// in this case we don't throw exception if tags don't exist
if (is_array($tag) && $value === null) {
foreach ($tag as $k => $v) {
$this->_setOrAppend($k, $v, $encodeHtml, $append, false);
$this->_setOrAppend($k, (string) $v, $encodeHtml, $append, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never cast to a string, string type is required

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if you use ->set($model), then it will try to set model id which is integer into template too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see l153/typecasting above, this is already fully handled when set thru model, we cannot do it with array, as php type itself is not enought to determine DBAL/typecasting type, since #1987 we format int using UI persistence config, so (string) will not produce desired output

}

return;
Expand Down