From 860b7e4f8a520a6629e035cf81e67e8b267cc721 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 13:20:46 +0300 Subject: [PATCH 01/11] ease class extending, for consistency with other view classes, for example, form layout class --- src/Card.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Card.php b/src/Card.php index efb7399b67..351e2d860f 100644 --- a/src/Card.php +++ b/src/Card.php @@ -166,7 +166,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|null $fields @@ -178,7 +189,7 @@ 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()); From 35e00d4ad79822dbee8b402e09719a1f60e18806 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 13:44:41 +0300 Subject: [PATCH 02/11] use protected vs private to allow extending classes --- src/CardDeck.php | 8 ++++---- src/CardSection.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CardDeck.php b/src/CardDeck.php index cb1cb5db64..67194799b3 100644 --- a/src/CardDeck.php +++ b/src/CardDeck.php @@ -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 { @@ -262,7 +262,7 @@ protected function jsCreateNotifier(Model\UserAction $action, string $msg = null * * @return mixed */ - private function getReloadArgs() + protected function getReloadArgs() { $args = []; if ($this->paginator !== false) { @@ -278,7 +278,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); diff --git a/src/CardSection.php b/src/CardSection.php index 083667b815..cb8d3cf6df 100644 --- a/src/CardSection.php +++ b/src/CardSection.php @@ -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 { 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; } @@ -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); From db917969683f9a1ef9f03c39955d08c530dbc874 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 14:02:30 +0300 Subject: [PATCH 03/11] Add ability to not create field views always, but simply set values in (probably custom) template --- src/Card.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Card.php b/src/Card.php index 351e2d860f..798c533017 100644 --- a/src/Card.php +++ b/src/Card.php @@ -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 @@ -195,7 +198,12 @@ public function setModel(Model $entity, array $fields = null): void $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); + } } /** From f3ff230c75bb614a858c4cba943ec4d0e6f98c87 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 14:37:21 +0300 Subject: [PATCH 04/11] decouple methods to ease class extending --- src/CardDeck.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/CardDeck.php b/src/CardDeck.php index 67194799b3..e04276654c 100644 --- a/src/CardDeck.php +++ b/src/CardDeck.php @@ -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); @@ -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')); From ddb9a7720ea130478c833f0c3fdfbeb0a2944426 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 14:46:31 +0300 Subject: [PATCH 05/11] we require string there always, but sometimes value can be integer and then it fails --- src/HtmlTemplate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HtmlTemplate.php b/src/HtmlTemplate.php index 0a3a2480d4..62a6d92f8f 100644 --- a/src/HtmlTemplate.php +++ b/src/HtmlTemplate.php @@ -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); } return; From c61d65bdad290e37c7b830e7e5548b1317a0845d Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 16:32:18 +0300 Subject: [PATCH 06/11] Make sure we always set seed as array, BC --- src/Card.php | 1 - src/CardDeck.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Card.php b/src/Card.php index 798c533017..61fd54f245 100644 --- a/src/Card.php +++ b/src/Card.php @@ -198,7 +198,6 @@ public function setModel(Model $entity, array $fields = null): void $this->template->trySet('dataId', (string) $this->model->getId()); View::addTo($this->getSection(), [$entity->getTitle(), 'class.header' => true]); - if ($this->addFields) { $this->getSection()->addFields($entity, $fields, $this->useLabel, $this->useTable); } else { diff --git a/src/CardDeck.php b/src/CardDeck.php index e04276654c..f7236cecdf 100644 --- a/src/CardDeck.php +++ b/src/CardDeck.php @@ -22,7 +22,7 @@ class CardDeck extends View public $defaultTemplate = 'card-deck.html'; - /** @var class-string Card type inside this deck. */ + /** @var class-string|array Card type inside this deck. */ public $card = Card::class; /** @var bool Whether card should use table display or not. */ @@ -152,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])); $c->setModel($m, $fields); if ($extra) { $c->addExtraFields($m, $extra, $this->extraGlue); From 85ecc633ef8000e823b379800d7775bd2a669cf6 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 16:32:34 +0300 Subject: [PATCH 07/11] add demo --- demos/collection/card-deck.php | 43 +++++++++++++++++++++++++++- demos/collection/templates/card.html | 11 +++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 demos/collection/templates/card.html diff --git a/demos/collection/card-deck.php b/demos/collection/card-deck.php index e0b3bee9de..6a3f97c29e 100644 --- a/demos/collection/card-deck.php +++ b/demos/collection/card-deck.php @@ -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) { @@ -50,3 +52,42 @@ $deck = CardDeck::addTo($app, ['noRecordScopeActions' => ['request_info'], 'singleScopeActions' => ['book']]); $deck->setModel($countries, ['cost'], [$countries->fieldName()->iso, $countries->fieldName()->iso3]); + + + + + + + +Header::addTo($app, ['Card Deck - Editable', 'size' => 1, 'subHeader' => 'Cards can also use custom templates and have full editing support like Grid.']); + +class MyCard 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; + } +} + +$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' => [ + MyCard::class, + 'ui' => 'card atk-card blue', + 'addFields' => false, + 'defaultTemplate' => __DIR__ . '/templates/card.html', + ], +]); +$deck->setModel($countries); +//var_dump($countries->tryLoadAny()->get()); diff --git a/demos/collection/templates/card.html b/demos/collection/templates/card.html new file mode 100644 index 0000000000..a98cd9696b --- /dev/null +++ b/demos/collection/templates/card.html @@ -0,0 +1,11 @@ +
+
+ + + +
+ {$Image} + {$Content} + {$Section} + {$ExtraContent} +
From 4438de5da27490a1b0478e699b1d69d19355637a Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 16:38:23 +0300 Subject: [PATCH 08/11] cleanup --- demos/collection/card-deck.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/demos/collection/card-deck.php b/demos/collection/card-deck.php index 6a3f97c29e..45f1c4207a 100644 --- a/demos/collection/card-deck.php +++ b/demos/collection/card-deck.php @@ -53,19 +53,15 @@ $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.']); -class MyCard extends Card { +class MyCard extends Card +{ public function getButtonContainer() { if (!$this->btnContainer) { - //$this->btnContainer = $this->addExtraContent(new View(['ui' => 'buttons'])); + // $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) { @@ -78,7 +74,7 @@ public function getButtonContainer() } $countries = new Country($app->db); -$countries->addCalculatedField('iso_lower', ['expr' => function (Country $m){return strtolower($m->get($m->fieldName()->iso));}]); +$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, @@ -90,4 +86,3 @@ public function getButtonContainer() ], ]); $deck->setModel($countries); -//var_dump($countries->tryLoadAny()->get()); From 22b31fde35f4c3e29d3db964fccf3bb359f5ab82 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 16:45:58 +0300 Subject: [PATCH 09/11] fixer --- demos/collection/card-deck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/collection/card-deck.php b/demos/collection/card-deck.php index 45f1c4207a..86a07bb1dc 100644 --- a/demos/collection/card-deck.php +++ b/demos/collection/card-deck.php @@ -74,7 +74,7 @@ public function getButtonContainer() } $countries = new Country($app->db); -$countries->addCalculatedField('iso_lower', ['expr' => function (Country $m){return strtolower($m->get($m->fieldName()->iso)); }]); +$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, From 488d86fbc8b37464f834a8dc38c8791eddc4b2d9 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Wed, 29 Mar 2023 16:56:21 +0300 Subject: [PATCH 10/11] move class to separate file --- demos/_includes/CustomCard.php | 25 +++++++++++++++++++++++++ demos/collection/card-deck.php | 19 +------------------ 2 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 demos/_includes/CustomCard.php diff --git a/demos/_includes/CustomCard.php b/demos/_includes/CustomCard.php new file mode 100644 index 0000000000..2f53508c79 --- /dev/null +++ b/demos/_includes/CustomCard.php @@ -0,0 +1,25 @@ +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; + } +} diff --git a/demos/collection/card-deck.php b/demos/collection/card-deck.php index 86a07bb1dc..77c918b7e6 100644 --- a/demos/collection/card-deck.php +++ b/demos/collection/card-deck.php @@ -56,30 +56,13 @@ // 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.']); -class MyCard 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; - } -} - $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' => [ - MyCard::class, + CustomCard::class, 'ui' => 'card atk-card blue', 'addFields' => false, 'defaultTemplate' => __DIR__ . '/templates/card.html', From 40e4a452a3b43e61f700578176c619cfdefb0f8a Mon Sep 17 00:00:00 2001 From: DarkSide Date: Thu, 30 Mar 2023 15:21:38 +0300 Subject: [PATCH 11/11] add headerView property, useful for extending --- src/Card.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Card.php b/src/Card.php index 61fd54f245..8e29911c4c 100644 --- a/src/Card.php +++ b/src/Card.php @@ -76,6 +76,9 @@ class Card extends View /** @var string Default executor class */ public $executor = UserAction\ModalExecutor::class; + /** @var View|null Header view, useful to add additional views to it, icons for example */ + public $headerView; + protected function init(): void { parent::init(); @@ -197,7 +200,7 @@ public function setModel(Model $entity, array $fields = null): void $this->template->trySet('dataId', (string) $this->model->getId()); - View::addTo($this->getSection(), [$entity->getTitle(), 'class.header' => true]); + $this->headerView = View::addTo($this->getSection(), [$entity->getTitle(), 'class.header' => true]); if ($this->addFields) { $this->getSection()->addFields($entity, $fields, $this->useLabel, $this->useTable); } else {