我們在建置網站時,有時候會需要製作客製化的嵌入式頁面,常見範例如要在 iframe 或光箱中顯示特定內容,在這些情境下,嵌入的網頁內容通常不需要(或不應該)包含網站的標準頁首、頁尾、側邊欄等元素,以避免多餘的檔案載入影響效能或重複元素干擾嵌入效果而影響使用經驗。今天就來跟大家分享如何在 Drupal 10 覆寫系統渲染機制以客製化嵌入頁面。
今天的介紹的範例是透過客製化嵌入頁面建立導覽地圖功能,導覽地圖擁有一個非常簡單的網頁架構與內容,並搭配特定的前端函式庫完成互動效果,最後會透過 iframe 的方式將其嵌入。
上圖為關渡自然公園的「參觀導覽地圖」功能之截圖
步驟一:製作客製化模組
首先,我們需要先建立一個客製化模組,基本架構如下
modules/custom/custom_map_guide/ |-- custom_map_guide.info.yml |-- custom_map_guide.routing.yml |-- src/ |-- Controller/ |-- MapGuideController.php
custom_map_guide.info.yml 透過 info.yml 定義模組的資訊
name: 'Custom Map Guide' type: module description: 'Provides a custom embed page rendering for map guides.' core_version_requirement: ^10 package: Custom
步驟二:定義路由
有別於 Drupal 7 的 hook_menu 機制,在 Drupal 10 我們需要透過 routing.yml 來定義路由,以下的範例為透過分類的方式建立地圖主題,因此會需要分類項目 ID作為參數
custom_map_guide.routing.yml
custom_map_guide.content: path: '/map_guide/{tid}' defaults: _controller: '\Drupal\custom_map_guide\Controller\MapGuideController::content' requirements: _permission: 'access content'
步驟三:建立控制器
定義好路由之後,接著我們要在模組的 src/Controller/ 目錄下,建立 MapGuideController.php,基本的控制器如下
namespace Drupal\custom_map_guide\Controller; use Drupal\Core\Controller\ControllerBase; class MapGuideController extends ControllerBase { public function content() { } }
接下來我們需要在控制器內處理以下項目:
- 匯入所需的類別
- 由於會從控制器中調用模組中定義的函式庫(Library),可以使用靜態 create 方法從服務器取得 ibrary.discovery 服務,以便稍後使用
- 在 content 方法中依照帶入的分類 ID 並透過自定義的 buildCustomHtml 方法組合成渲染所需的陣列,最後回傳一個包含生成 HTML 的 Response 物件
以下的範例用程式碼包含了較完整的程式邏輯,提供給大家參考
<?php // 定義命名空間 namespace Drupal\custom_map_guide\Controller; // 匯入所需的類別 use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Asset\LibraryDiscoveryInterface; use Drupal\taxonomy\Entity\Term; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Response; use Drupal\Core\Render\Markup; use Drupal\file\Entity\File; use Drupal\image\Entity\ImageStyle; // 定義控制器類別,繼承自 ControllerBase class MapGuideController extends ControllerBase { // 定義受保護屬性來保存 LibraryDiscoveryInterface 實例 protected $libraryDiscovery; // 靜態 create 方法,用於從服務容器中取得 library.discovery 服務並實例化控制器 public static function create(ContainerInterface $container) { return new static( $container->get('library.discovery') ); } // 建構函數,接受 LibraryDiscoveryInterface 實例 public function __construct(LibraryDiscoveryInterface $libraryDiscovery) { $this->libraryDiscovery = $libraryDiscovery; } // content 方法,根據給定的 tid 載入分類項目並生成對應的 HTML public function content($tid) { // 載入指定的分類項目 $term = Term::load($tid); // 如果分類項目存在,則生成自定義 HTML if ($term) { $render_array = $this->buildCustomHtml($term); // 渲染陣列並返回包含 HTML 的 Response 物件 $html = \Drupal::service('renderer')->renderRoot($render_array); return new Response($html); } // 如果分類項目不存在,返回 404 錯誤 return new Response('', Response::HTTP_NOT_FOUND); } // buildCustomHtml 方法,用於生成自定義 HTML private function buildCustomHtml($term) { // 取得網站名稱 $site_name = \Drupal::config('system.site')->get('name'); // 取得分類項目名稱 $term_name = $term->getName(); $term_map_guide_main_img_url = ''; // 檢查詞彙是否有 field_map_guide_main_img 欄位 if ($term->hasField('field_map_guide_main_img')) { $image_field = $term->get('field_map_guide_main_img'); // 如果圖片字段不為空,載入對應的圖片檔案 if (!$image_field->isEmpty()) { $image_file_id = $image_field->target_id; $file = File::load($image_file_id); // 如果圖片檔案存在,取得圖片 URI 和樣式 URL if ($file) { $img_uri = $file->getFileUri(); $img_style = ImageStyle::load('map_guide_main_img'); // 如果圖片樣式存在,生成圖片 URL if ($img_style) { $term_map_guide_main_img_url = $img_style->buildUrl($img_uri); } } } } // 取得所需的函式庫 $jquery = $this->libraryDiscovery->getLibraryByName('custom_map_guide', 'jquery'); $pageguide = $this->libraryDiscovery->getLibraryByName('custom_map_guide', 'pageguide'); $map_guide = $this->libraryDiscovery->getLibraryByName('custom_map_guide', 'map_guide'); $css_links = ''; $js_scripts = ''; // 生成 CSS foreach ($pageguide['css'] as $css) { $css_links .= '<link rel="stylesheet" type="text/css" href="/' . $css['data'] . '">'; } foreach ($map_guide['css'] as $css) { $css_links .= '<link rel="stylesheet" type="text/css" href="/' . $css['data'] . '">'; } // 生成 JS foreach ($jquery['js'] as $js) { $js_scripts .= '<script type="text/javascript" src="/' . $js['data'] . '"></script>'; } foreach ($pageguide['js'] as $js) { $js_scripts .= '<script type="text/javascript" src="/' . $js['data'] . '"></script>'; } foreach ($map_guide['js'] as $js) { $js_scripts .= '<script type="text/javascript" src="/' . $js['data'] . '"></script>'; } // 生成頁面主體的 HTML $body = '<div id="map-guide-container" data-map-title="' . $term_name . '" data-map-bg="' . $term_map_guide_main_img_url . '" data-site-name="' . $site_name . '" data-force-landscape="0"></div>'; // 建構並返回渲染陣列,包含完整 HTML 結構 $render_array = [ '#type' => 'html_tag', '#tag' => 'html', '#value' => Markup::create('<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>' . $term_name . ' | ' . $site_name . '</title>' . $css_links . '</head><body>' . $body . $js_scripts . '</body>'), ]; return $render_array; } }
* 本文首張圖片由 Ideogram 生成