Ajax调用后如何更新Twig中的Symfony上下文?
电商式商品筛选功能:Ajax同步筛选器上下文与保留选中状态问题
我正在实现类似电商的商品筛选功能,流程如下:
- 页面加载时,控制器渲染包含
<div id="filter">(筛选器区域)和<div id="filtered_content">(结果内容区域)的temp.html.twig模板,同时传入筛选器所需的上下文数据。 - 用户选择筛选条件后,通过Ajax调用搜索接口,接口返回带更新上下文的
content.html.twig模板内容,替换filtered_content区域的操作正常。但现在不知道如何把接口返回的更新后筛选器上下文同步到页面的筛选器中。 - 尝试过单独创建筛选器模板并替换
filter区域,但这样会清除用户已选中的复选框状态。
相关代码
控制器代码
public function search(allRepositoryNeeded, Request request){ // 获取用户选择的筛选条件 $selectedCountries = $request->get("countries"); $selectedBiologicalStatuses = $request->get("biologicalStatuses"); $selectedMLSStatuses = $request->get('mlsStatuses'); $selectedTaxonomies = $request->get('taxonomies'); if ($request->get('ajax')) { // 获取筛选器相关数据 $accessionsByCountry = $countryRepo->getAccessionsByCountry($selectedBiologicalStatuses); $accessionsByBiologicalStatus = $biologicalStatusRepo->getAccessionsByBiologicalStatus(); // 组装响应上下文 $context = [ 'title' => 'Accession Filtered List', 'accessions' => $filteredAccession, 'accessionsByCountry' => $accessionsByCountry, 'accessionsByBiologicalStatus' => $accessionsByBiologicalStatus, ]; return new JsonResponse([ 'content' => $this->renderView('search/content_accession.html.twig', $context) ]); } $accessions = $accessionRepo->findAll(); $context = [ 'title' => 'Accession List', 'accessions' => $accessions, // 筛选器初始数据 'accessionsByCountry' => $accessionsByCountry, 'accessionsByBiologicalStatus' => $accessionsByBiologicalStatus, ]; return $this->render('search/index_accession.html.twig', $context); }
JS代码
$(document).ready(function(){ // 获取筛选表单 const filtersForm = document.querySelector("#filters"); // 获取表单所有输入控件 const inputs = document.querySelectorAll("#filters input"); // 为每个输入控件绑定change事件 inputs.forEach(input => { input.addEventListener("change", function() { // 获取表单数据 const form = new FormData(filtersForm); // 组装URL参数 const params = new URLSearchParams(); form.forEach((value, key) => { params.append(key, value); }); // 当前页面URL const currentUrl = new URL(window.location.href); // 发起Ajax请求,添加ajax=1标识 fetch(currentUrl.pathname + "?" + params.toString() + "&ajax=1", { headers: { "X-Requested-With": "XMLHttpRequest" } }).then(response => response.json() ).then(data => { // 更新内容区域 const filteredContent = document.querySelector("#filtered_content"); filteredContent.innerHTML = data.content; }).catch(e => alert(e)); }); }); })
解决方案
方案1:返回筛选器HTML并恢复选中状态
适合筛选器结构需要频繁变动的场景,核心是替换前记录用户已选选项,替换后重新恢复选中状态。
修改控制器代码
在Ajax响应中同时返回筛选器的渲染内容:
if ($request->get('ajax')) { // ... 原有逻辑 ... // 新增:渲染筛选器模板 $filterHtml = $this->renderView('search/filter_accession.html.twig', $context); return new JsonResponse([ 'content' => $this->renderView('search/content_accession.html.twig', $context), 'filter' => $filterHtml // 新增筛选器HTML ]); }
修改JS代码
保存当前选中值,更新筛选器后重新设置选中状态:
$(document).ready(function(){ const filtersForm = document.querySelector("#filters"); const inputs = document.querySelectorAll("#filters input"); inputs.forEach(input => { input.addEventListener("change", function() { // 保存当前选中的所有复选框值(按name分组) const selectedValues = {}; document.querySelectorAll("#filters input[type='checkbox']").forEach(checkbox => { const name = checkbox.name; if (!selectedValues[name]) selectedValues[name] = []; if (checkbox.checked) selectedValues[name].push(checkbox.value); }); const form = new FormData(filtersForm); const params = new URLSearchParams(); form.forEach((value, key) => params.append(key, value)); const currentUrl = new URL(window.location.href); fetch(currentUrl.pathname + "?" + params.toString() + "&ajax=1", { headers: { "X-Requested-With": "XMLHttpRequest" } }).then(response => response.json()) .then(data => { // 更新内容区域 const filteredContent = document.querySelector("#filtered_content"); filteredContent.innerHTML = data.content; // 更新筛选器区域 const filterArea = document.querySelector("#filter"); filterArea.innerHTML = data.filter; // 恢复选中状态 Object.keys(selectedValues).forEach(name => { selectedValues[name].forEach(value => { const checkbox = document.querySelector(`#filters input[name="${name}"][value="${value}"]`); if (checkbox) checkbox.checked = true; }); }); }).catch(e => alert(e)); }); }); })
方案2:返回筛选器结构化数据,局部更新DOM
性能更优,适合仅需更新筛选选项计数、可用性的场景,完全保留原有DOM的选中状态。
修改控制器代码
返回筛选器的结构化数据而非HTML:
if ($request->get('ajax')) { // ... 原有逻辑 ... // 组装筛选器结构化数据 $filterData = [ 'countries' => array_map(function($country) { return [ 'name' => $country['name'], 'value' => $country['id'], 'count' => $country['count'], 'isAvailable' => $country['count'] > 0 // 是否有结果可选 ]; }, $accessionsByCountry), 'biologicalStatuses' => array_map(function($status) { return [ 'name' => $status['name'], 'value' => $status['id'], 'count' => $status['count'] ]; }, $accessionsByBiologicalStatus) ]; return new JsonResponse([ 'content' => $this->renderView('search/content_accession.html.twig', $context), 'filterData' => $filterData // 新增筛选器数据 ]); }
修改JS代码
根据返回数据局部更新现有筛选器的内容:
$(document).ready(function(){ const filtersForm = document.querySelector("#filters"); const inputs = document.querySelectorAll("#filters input"); inputs.forEach(input => { input.addEventListener("change", function() { const form = new FormData(filtersForm); const params = new URLSearchParams(); form.forEach((value, key) => params.append(key, value)); const currentUrl = new URL(window.location.href); fetch(currentUrl.pathname + "?" + params.toString() + "&ajax=1", { headers: { "X-Requested-With": "XMLHttpRequest" } }).then(response => response.json()) .then(data => { // 更新内容区域 const filteredContent = document.querySelector("#filtered_content"); filteredContent.innerHTML = data.content; // 更新国家筛选的计数与可用性 data.filterData.countries.forEach(country => { const checkbox = document.querySelector(`#filters input[name="countries"][value="${country.value}"]`); if (checkbox) { // 更新计数(假设复选框旁有class为filter-count的span) const countSpan = checkbox.nextElementSibling; if (countSpan?.classList.contains('filter-count')) { countSpan.textContent = `(${country.count})`; } // 隐藏无结果的选项 checkbox.parentElement.style.display = country.isAvailable ? 'block' : 'none'; } }); // 更新生物状态筛选的计数 data.filterData.biologicalStatuses.forEach(status => { const checkbox = document.querySelector(`#filters input[name="biologicalStatuses"][value="${status.value}"]`); if (checkbox) { const countSpan = checkbox.nextElementSibling; if (countSpan?.classList.contains('filter-count')) { countSpan.textContent = `(${status.count})`; } } }); }).catch(e => alert(e)); }); }); })
注意事项
- 方案1需确保筛选器模板的复选框
name和value与原有模板完全一致,否则无法恢复选中状态。 - 方案2需要提前在HTML中为计数元素添加统一类名(如
filter-count),方便JS定位更新。
内容的提问来源于stack exchange,提问作者EE2017
相关产品推荐
相关产品推荐

