-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
tkiehne
committed
Aug 21, 2024
1 parent
3631ac5
commit d2617fa
Showing
7 changed files
with
384 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Bootstrap Views Cards | ||
|
||
Adds a views plugin to render outputs as Bootstrap 5 cards | ||
|
||
## Requirements | ||
|
||
- Views (Drupal core) | ||
- Bootstrasp 5 based theme (technically optional - you can implement your own styling if you prefer) | ||
|
||
## Features | ||
|
||
This Views plugin supports most [Bootstrap 5 card](https://getbootstrap.com/docs/5.0/components/card) features: | ||
|
||
- Header | ||
- Footer | ||
- Image | ||
- Card body with: | ||
- Title | ||
- Subtitle | ||
- Text | ||
|
||
You may use template overrides and/or preprocess functions to customize further. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: Bootstrap Views Cards | ||
type: module | ||
description: Adds a views plugin to render outputs as Bootstrap 5 cards. | ||
package: Views | ||
core_version_requirement: ^10.2 || ^11 | ||
dependencies: | ||
- drupal:views |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php | ||
|
||
/** | ||
* @file | ||
* Custom functions for Bootstrap Views Cards. | ||
*/ | ||
|
||
use Drupal\Component\Utility\Html; | ||
use Drupal\Core\Template\Attribute; | ||
|
||
/** | ||
* Prepares variables for views cards templates. | ||
* | ||
* Default template: bootstrap-views-cards.html.twig. | ||
* | ||
* @param array $vars | ||
* An associative array containing: | ||
* - view: A ViewExecutable object. | ||
* - rows: The raw row data. | ||
*/ | ||
function template_preprocess_bootstrap_views_cards(array &$vars): void { | ||
$view = $vars['view']; | ||
$id = $view->storage->id() . '-' . $view->current_display; | ||
$vars['id'] = Html::getUniqueId('bootstrap-views-cards-' . $id); | ||
|
||
$vars['attributes'] = new Attribute(['class' => ['row', 'row-cols-1']]); | ||
$cols = $view->style_plugin->options['card_group_columns']; | ||
if ($cols > 1) { | ||
switch ($cols) { | ||
case 2: | ||
$vars['attributes']->addClass('row-cols-sm-2'); | ||
break; | ||
|
||
case 3: | ||
$vars['attributes']->addClass('row-cols-sm-2 row-cols-md-3'); | ||
break; | ||
|
||
case 4: | ||
$vars['attributes']->addClass('row-cols-sm-2 row-cols-md-4'); | ||
break; | ||
} | ||
} | ||
|
||
$options = $view->style_plugin->options; | ||
if ($options['card_group_class_custom']) { | ||
$option_classes = array_filter(explode(' ', $options['card_group_class_custom'])); | ||
$classes = array_map([Html::class, 'cleanCssIdentifier'], $option_classes); | ||
$vars['attributes']->addClass($classes); | ||
} | ||
|
||
$vars['image_option'] = $view->style_plugin->options['card_image_option_field']; | ||
$bodyClass = $vars['image_option'] == 'background' ? 'card-img-overlay' : 'card-body'; | ||
$vars['body_attributes'] = new Attribute(['class' => [$bodyClass]]); | ||
|
||
// Card rows. | ||
$header = $view->style_plugin->options['card_header_field']; | ||
$image = $view->style_plugin->options['card_image_field']; | ||
$title = $view->style_plugin->options['card_title_field']; | ||
$subtitle = $view->style_plugin->options['card_subtitle_field']; | ||
$content = $view->style_plugin->options['card_body_field']; | ||
$footer = $view->style_plugin->options['card_footer_field']; | ||
|
||
foreach ($vars['rows'] as $id => $row) { | ||
$vars['rows'][$id] = []; | ||
$vars['rows'][$id]['header'] = $view->style_plugin->getField($id, $header); | ||
$vars['rows'][$id]['image'] = $view->style_plugin->getField($id, $image); | ||
$vars['rows'][$id]['title'] = $view->style_plugin->getField($id, $title); | ||
$vars['rows'][$id]['subtitle'] = $view->style_plugin->getField($id, $subtitle); | ||
$vars['rows'][$id]['content'] = $view->style_plugin->getField($id, $content); | ||
$vars['rows'][$id]['footer'] = $view->style_plugin->getField($id, $footer); | ||
$vars['rows'][$id]['attributes'] = new Attribute(['class' => ['card', 'h-100']]); | ||
if ($row_class = $view->style_plugin->getRowClass($id)) { | ||
$vars['rows'][$id]['attributes']->addClass($row_class); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "drupal/bootstrap_views_cards", | ||
"description": "Adds a views plugin to render outputs as Bootstrap 5 cards.", | ||
"type": "drupal-module", | ||
"homepage": "https://github.com/deohs/bootstrap_views_cards", | ||
"authors": [ | ||
{ | ||
"name": "Thomas Kiehne (tkiehne)", | ||
"email": "[email protected]", | ||
"homepage": "https://www.drupal.org/u/tkiehne", | ||
"role": "Creator" | ||
} | ||
], | ||
"support": { | ||
"issues": "https://github.com/deohs/bootstrap_views_cards/issues", | ||
"source": "https://github.com/deohs/bootstrap_views_cards" | ||
}, | ||
"require": { | ||
"php": ">=8.1" | ||
}, | ||
"conflict": { | ||
"drush/drush": "<10.2" | ||
}, | ||
"license": "GPL-3.0-or-later", | ||
"minimum-stability": "dev" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
views.style.bootstrap_views_cards: | ||
type: views_style | ||
label: 'Bootstrap views cards' | ||
mapping: | ||
card_header_field: | ||
type: label | ||
label: "Card header field" | ||
card_title_field: | ||
type: label | ||
label: "Card title field" | ||
card_subtitle_field: | ||
type: label | ||
label: "Card subtitle field" | ||
card_body_field: | ||
type: label | ||
label: "Card body field" | ||
card_image_field: | ||
type: label | ||
label: "Card image field" | ||
card_image_option_field: | ||
type: label | ||
label: "Card image option field" | ||
card_footer_field: | ||
type: label | ||
label: "Card footer field" | ||
card_group_class_custom: | ||
type: label | ||
label: "Custom card group class" | ||
card_group_columns: | ||
type: label | ||
label: "Cards per row" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<?php | ||
|
||
namespace Drupal\bootstrap_views_cards\Plugin\views\style; | ||
|
||
use Drupal\Core\Form\FormStateInterface; | ||
use Drupal\views\Plugin\views\style\StylePluginBase; | ||
|
||
/** | ||
* Style plugin to render each item in a Bootstrap card. | ||
* | ||
* @ingroup views_style_plugins | ||
* | ||
* @ViewsStyle( | ||
* id = "bootstrap_views_cards", | ||
* title = @Translation("Bootstrap Cards"), | ||
* help = @Translation("Displays rows in a Bootstrap Card Group layout"), | ||
* theme = "bootstrap_views_cards", | ||
* display_types = {"normal"} | ||
* ) | ||
*/ | ||
class BootstrapViewsCards extends StylePluginBase { | ||
/** | ||
* Does the style plugin for itself support to add fields to it's output. | ||
* | ||
* @var bool | ||
*/ | ||
protected $usesFields = TRUE; | ||
|
||
/** | ||
* Does the style plugin allows to use style plugins. | ||
* | ||
* @var bool | ||
*/ | ||
protected $usesRowPlugin = TRUE; | ||
|
||
/** | ||
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesRowClass. | ||
* | ||
* @var bool | ||
*/ | ||
protected $usesRowClass = TRUE; | ||
|
||
/** | ||
* Definition. | ||
*/ | ||
protected function defineOptions() { | ||
$options = parent::defineOptions(); | ||
unset($options['grouping']); | ||
$options['card_header_field'] = ['default' => NULL]; | ||
$options['card_image_field'] = ['default' => NULL]; | ||
$options['card_image_option_field'] = ['default' => 'top']; | ||
$options['card_title_field'] = ['default' => NULL]; | ||
$options['card_subtitle_field'] = ['default' => NULL]; | ||
$options['card_body_field'] = ['default' => NULL]; | ||
$options['card_footer_field'] = ['default' => NULL]; | ||
$options['card_group_class_custom'] = ['default' => NULL]; | ||
$options['card_group_columns'] = ['default' => 3]; | ||
return $options; | ||
} | ||
|
||
/** | ||
* Render the given style. | ||
*/ | ||
public function buildOptionsForm(&$form, FormStateInterface $form_state) { | ||
parent::buildOptionsForm($form, $form_state); | ||
unset($form['grouping']); | ||
$form['help'] = [ | ||
'#markup' => $this->t('The Bootstrap cards displays content in a flexible container (<a href=":docs">see documentation</a>). Note that any fields not assigned below will not be displayed.', | ||
[':docs' => 'https://getbootstrap.com/docs/5.0/components/card/']), | ||
'#weight' => -99, | ||
]; | ||
$form['card_header_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card header field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_header_field'], | ||
'#description' => $this->t('Select the field that will be used for the card header. HTML tags will be stripped.'), | ||
'#weight' => 1, | ||
]; | ||
$form['card_image_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card image field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_image_field'], | ||
'#description' => $this->t('Select the field that will be used for the card image.'), | ||
'#weight' => 2, | ||
]; | ||
$form['card_image_option_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Image placement'), | ||
'#description' => $this->t('Where to place the image in the card.'), | ||
'#options' => [ | ||
'top' => $this->t('Top'), | ||
'bottom' => $this->t('Bottom'), | ||
'background' => $this->t('Background'), | ||
], | ||
'#default_value' => $this->options['card_image_option_field'], | ||
'#weight' => 3, | ||
]; | ||
$form['card_title_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card title field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_title_field'], | ||
'#description' => $this->t('Select the field that will be used for the card title. HTML tags will be stripped.'), | ||
'#weight' => 4, | ||
]; | ||
$form['card_subtitle_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card subtitle field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_subtitle_field'], | ||
'#description' => $this->t('Select the field that will be used for the card subtitle. HTML tags will be stripped.'), | ||
'#weight' => 5, | ||
]; | ||
$form['card_body_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card content field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => TRUE, | ||
'#default_value' => $this->options['card_body_field'], | ||
'#description' => $this->t('Select the field that will be used for the card content body.'), | ||
'#weight' => 6, | ||
]; | ||
$form['card_footer_field'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Card footer field'), | ||
'#options' => $this->displayHandler->getFieldLabels(TRUE), | ||
'#empty_option' => $this->t('- Select -'), | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_footer_field'], | ||
'#description' => $this->t('Select the field that will be used for the card footer.'), | ||
'#weight' => 7, | ||
]; | ||
$form['card_group_class_custom'] = [ | ||
'#title' => $this->t('Custom card group class'), | ||
'#description' => $this->t('Additional classes to provide on the card group, separated by a space.'), | ||
'#type' => 'textfield', | ||
'#required' => FALSE, | ||
'#default_value' => $this->options['card_group_class_custom'], | ||
'#weight' => 8, | ||
]; | ||
$form['row_class']['#title'] = $this->t('Custom card class(es), separated by a space.'); | ||
$form['row_class']['#weight'] = 9; | ||
$form['card_group_columns'] = [ | ||
'#type' => 'select', | ||
'#title' => $this->t('Cards per row'), | ||
'#description' => $this->t('The max number of cards to include in a row in the largest responsive viewport.'), | ||
'#options' => [ | ||
1 => 1, | ||
2 => 2, | ||
3 => 3, | ||
4 => 4, | ||
], | ||
'#required' => TRUE, | ||
'#default_value' => $this->options['card_group_columns'], | ||
'#weight' => 10, | ||
]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
{# | ||
/** | ||
* @file bootstrap-views-cards.html.twig | ||
* Default simple view template to display Bootstrap 5 Cards. | ||
* | ||
* | ||
* - rows: Contains a nested array of rows. Each row contains an array of | ||
* columns. | ||
* - row.attributes: Class attributes to apply to each card. | ||
* - attributes: Class attributes for the card group as a whole. | ||
* - image_option: The configured option for modifying image placement. | ||
* | ||
* @ingroup views_templates | ||
*/ | ||
#} | ||
|
||
<div {{ attributes }}> | ||
{% for row in rows %} | ||
<div class="col"> | ||
<div {{ row.attributes }}> | ||
{% if row.header %} | ||
<h5 class="card-header"> | ||
{{ row.header|striptags('<a><span><sup><sub>')|raw }} | ||
</h5> | ||
{% endif %} | ||
{% if row.image and image_option != 'bottom' %} | ||
<span class="{{ image_option == 'background' ? 'card-img' : 'card-img-top' }}">{{ row.image }}</span> | ||
{% endif %} | ||
<div {{ body_attributes }}> | ||
{% if row.title %} | ||
<h5 class="card-title">{{ row.title|striptags('<a><span><sup><sub>')|raw }}</h5> | ||
{% endif %} | ||
{% if row.subtitle %} | ||
<h6 class="card-subtitle">{{ row.subtitle|striptags('<a><span><sup><sub>')|raw }}</h6> | ||
{% endif %} | ||
{% if row.content %} | ||
{{- row.content -}} | ||
{% endif %} | ||
</div> | ||
{% if row.image and image_option == 'bottom' %} | ||
<span class="card-img-bottom">{{ row.image }}</span> | ||
{% endif %} | ||
{% if row.footer %} | ||
<div class="card-footer"> | ||
{{ row.footer }} | ||
</div> | ||
{% endif %} | ||
</div> | ||
</div> | ||
{%- endfor %} | ||
</div> |