AJAX与头尾更新最佳实践:动态菜单代码复用方案咨询
Great question—you’re already on the right track by centralizing your menu logic instead of duplicating it across PHP and JS, which is a common pitfall with AJAX-driven sites. Let’s break down how to refine your approach for cleaner, more maintainable code:
1. 替换嵌入<script>标签的JSON传递方式
Your current method of outputting a script tag to inject JSON into the global scope works, but it’s not the cleanest approach. Here are two better alternatives:
选项A:AJAX响应直接返回结构化数据
Instead of returning HTML with an embedded script, have your ProcessWire backend return a JSON object containing both the menu data and page content when an AJAX request is detected. This keeps data and markup separate:
// In your page template or init file if ($config->ajax) { // Build your $menuArray as before echo json_encode([ 'menu' => $menuArray, 'content' => $page->render() // Render your main page content here ]); exit; }
Then in your JS, handle the response directly:
fetch(yourUrl, { method: 'GET' }) .then(res => res.json()) .then(data => { // Update menu with the fresh data this.updateMenu(data.menu); // Update main content document.querySelector('.main-content').innerHTML = data.content; });
选项B:用隐藏的data属性传递初始菜单数据
For the initial page load (non-AJAX), you can inject the menu JSON into a hidden element’s data-* attribute instead of a script tag. This keeps your markup cleaner and avoids polluting the global scope unnecessarily:
// In head.inc echo '<div id="menu-data" style="display:none" data-menu="' . htmlspecialchars($menuJSON) . '"></div>';
Then in your JS, fetch it on initialization:
const menuDataEl = document.getElementById('menu-data'); const menuJSON = JSON.parse(menuDataEl.dataset.menu);
2. 优化PHP菜单数组的构建逻辑
Your current menu building code can be simplified using ProcessWire’s built-in API methods to reduce repetition and improve readability:
$homePage = $pages->get(1); // Build left menu $leftMenuItems = $pages->find("template=work|news, sort=sort")->map(function($item) { return [ 'id' => $item->id, 'name' => $item->name, 'url' => $item->url, 'title' => $item->title, 'x' => '100%' ]; }); // Add "Close" button if current page is in left menu templates if (in_array($page->template->name, ['work', 'news'])) { $leftMenuItems[] = [ 'name' => 'close', 'url' => $homePage->url, 'title' => 'Close', 'x' => '100%' ]; } // Build right menu $rightMenuItems = $pages->find("template=clients|about, sort=sort")->map(function($item) { return [ 'id' => $item->id, 'name' => $item->name, 'url' => $item->url, 'title' => $item->title, 'x' => '100%' ]; }); // Add "Close" button if current page is in right menu templates if (in_array($page->template->name, ['clients', 'about'])) { array_unshift($rightMenuItems, [ 'name' => 'close', 'url' => $homePage->url, 'title' => 'Close', 'x' => '100%' ]); } $menuArray = [ 'left' => $leftMenuItems, 'right' => $rightMenuItems ]; $menuJSON = json_encode($menuArray);
Key improvements here:
- Using
map()to generate menu items in a more concise way - Separating the "Close" button logic into clear, condition-based steps
- Avoiding unnecessary loop repetition
3. 完全复用菜单渲染逻辑(可选)
If you want to eliminate HTML template duplication between PHP and JS, consider rendering the menu HTML server-side both for initial loads and AJAX requests:
First, create a reusable PHP function to render the menu:
function renderMenu($menuArray) { $html = ''; foreach ($menuArray as $position => $items) { $html .= '<ul class="menu --' . $position . '">'; foreach ($items as $item) { $html .= sprintf( '<li data-template-name="%s"><a data-ajax data-x="%s" href="%s">%s</a></li>', htmlspecialchars($item['name']), htmlspecialchars($item['x']), htmlspecialchars($item['url']), htmlspecialchars($item['title']) ); } $html .= '</ul>'; } return $html; }
Then use it for initial loads:
// In head.inc echo renderMenu($menuArray);
And for AJAX requests, return the pre-rendered HTML:
if ($config->ajax) { echo json_encode([ 'menuHtml' => renderMenu($menuArray), 'content' => $page->render() ]); exit; }
Now your JS can skip string concatenation and just replace the header HTML directly:
updateMenu: function(menuHtml) { document.querySelector('header.main').innerHTML = menuHtml; }
总结
Your core approach—centralizing menu logic in PHP and sharing it with JS—is absolutely the right direction. By refining how you pass data (avoiding script tags) and simplifying your code structure, you’ll end up with a more maintainable, scalable solution that avoids duplication entirely.
内容的提问来源于stack exchange,提问作者John the Painter

