【问题标题】:How link to and target/open a p:tab within an p:accordionPanel within a p:tab within a p:tabview如何在 p:tabview 中的 p:tab 中链接和定位/打开 p:tab 中的 p:accordionPanel
【发布时间】:2017-02-04 15:10:11
【问题描述】:

Primefaces 6.0(社区版);莫哈拉:2.2.8-18;玻璃鱼:4.1; Java版本:1.8.0_102; jQuery

[编辑:2016-10-05 使用纯 Primefaces+JSF 回答以下一般问题的答案现已得到自我回答(请参阅下面的详细答案):

Q1:如何在 p:tabview 中的 p:tab 中链接并定位/打开 p:accordionPanel 中的 p:tab?

下面的其余描述涉及通过使用 JavaScript 伪单击外部 p:tab(在 ap:tabView 内)和内部 p:tab(在 ap:accordionPanel 内)来实现相同目的的失败尝试.挑战(现在基本上是学术上的)是当 p:tabView 和 p:accordionPanel 都是动态且非缓存时如何同步这些。]


在 @ViewScoped 下,我在 dynamic 内有一个内部 p:tab,非缓存 p:accordionPanel 在 dynamic 内的外部 p:tab 内有一个非缓存 p:accordionPanel p:tabView。我必须能够离开那个内部标签并从其他页面返回。

重要提示:该问题仅发生在动态 p:tabView 和 p:accordionPanel 上,任何可接受的答案都必须适用于动态模式,因为真正的网络应用程序需要动态,因为它使用专家系统和显示的真实内容任何最里面的标签都可能受到来自其他地方的数据的影响

<h:body>

<h:outputScript name="js/changeTab.js" />                    

<h:form id="form" prependId="false">  

<p:tabView id='tabview' dynamic="true" cache="false" widgetVar="widgetTabView">

 <p:tab title="Outer Tab1" id="tabOuter1">Content of Tab1</p:tab>

 <p:tab title="Outer Tab2" id="tabOuter2">

  <p:accordionPanel 
    id="accordion"
    activeIndex="null" 
    dynamic="true" 
    cache="false" 
    widgetVar="widgetAccordion"
                    >
    <p:tab title="Inner Tab1" id="tabInner1">Content of inner Tab1</p:tab>

    <p:tab title="Inner Tab2" id="tabInner2">Content of inner Tab2</p:tab>

  </p:accordionPanel>                    
 </p:tab>

</p:tabView>

<ui:param name="tabViewId" value="#{param['tabViewId']}"/>
<ui:param name="tabId" value="#{param['tabId']}"/>
<ui:param name="accordionId" value="#{param['accordionId']}"/>
<ui:param name="accordionTabId" value="#{param['accordionTabId']}"/>

我需要以下内容来打开最里面的标签:

/faces/tabs_accordions.xhtml?tabViewId=tabview&tabId=tabOuter2&accordionId=accordion&accordionTabId=tabInner2

我的 JavaScript(通过 changeTab.js 包含),它模仿点击最外面的选项卡然后最里面的手风琴选项卡,没有捕捉到最里面的手风琴Panel+tab,最外面的 tabView+panel 在我之前还没有使目标可用尝试访问最里面的部分。

我首先在下面展示一种基于回调的方法/我在这里研究了许多关于如何强制连续同步函数的建议,包括使用.done()、Promises 和许多其他方法(参见下面的编辑示例),但所有它们似乎出于同样的原因而失败,这与 Primefaces 如何处理最外层 tabView/tab 上的第一次 click() 有关。

使用基本回调的示例:来自changeTab.js

function changeTabDynamicOuterCBwithParams(tabViewId, tabId, accordionId, accordionTabId, callback)
{

 // ... log params omitted ...

 $('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();
 callback(tabViewId,accordionId,accordionTabId);    
}

/**
 * Simulates clicking, and thus selection, of a p:tab within a p:accordionPanel.
 * 
 * IMPORTANT: For h:form with prepend=false !
 * 
 * NB: There is usually a p:tabView, p:tab, p:accordionPanel, p:tab.
 * but the first p:tab does not affect the ultimate client-side id.
 * 
 * @param {type} tabViewId Identifier of an outer ancestor p:tabView
 * @param {type} accordionId Identifier of an immediate parent p:accordion
 * @param {type} tabId Identifier of an immediate child p:tab
 * @returns {undefined}
 */
function changeTabViewAccordionTabDynamic(tabViewId,accordionId, accordionTabId)
 {
    var i = 'changeTabViewAccordionTabDynamic';

    console.log(i+": tabViewId("+tabViewId+")");
    console.log(i+": accordionId("+accordionId+")");
    console.log(i+": accordionTabId("+accordionTabId+")");

    var id = tabViewId + ":"+accordionId+":"+accordionTabId;
    console.log(i+": id("+id+")");
    var div = $("[id=\'" + id + "'\]");
    console.log(div.length);
    // The id is on the DIV following the H3 to click on.
    console.log(i+": div.length("+div.length+")");
    var h3 = div.prev();
    console.log(i+": h3.length("+h3.length+")");

    console.log(i+": clicking header of inner tab within accordion within outer tab within tabview");
    h3.click();
}

为测试而简化(不检查是否存在查询参数等)以下是在带有 tabView/tab/accordionPanel/tab 的页面中:

 changeTabDynamicOuterCBwithParams(
  '#{tabViewId}',
  '#{tabId}', 
  '#{accordionId}', 
  '#{accordionTabId}',
  changeTabViewAccordionTabDynamic
 );

JS 控制台给出:

changeTabDynamicOuterCB2: tabViewId(tabview)
changeTab.js:100 changeTabDynamicOuterCB2: tabId(tabOuter2)
changeTab.js:101 changeTabDynamicOuterCB2: accordionId(accordion)
changeTab.js:102 changeTabDynamicOuterCB2: accordionTabId(tabInner2)
changeTab.js:103 changeTabDynamicOuterCB2: clicking on outer tab within tabview THEN calling back on inner tab
changeTab.js:128 changeTabViewAccordionTabDynamic: tabViewId(tabview)
changeTab.js:129 changeTabViewAccordionTabDynamic: accordionId(accordion)
changeTab.js:130 changeTabViewAccordionTabDynamic: accordionTabId(tabInner2)
changeTab.js:133 changeTabViewAccordionTabDynamic: id(tabview:accordion:tabInner2)
changeTab.js:135 0
changeTab.js:137 changeTabViewAccordionTabDynamic: div.length(0)
changeTab.js:139 changeTabViewAccordionTabDynamic: h3.length(0)

请注意 DIV 和 H3 选择如何失败,因为它们尚不可用。

但是,如果我在浏览器的 JavaScript 控制台中执行此操作,则在第一个 click() 完成并且带有内部手风琴/tabview 的选项卡已正确加载后,此选择可以正常工作:

id='tabview:accordion:tabInner2'
var div = $("[id=\'" + id + "'\]");
var h3 = div.prev();

结果:

 h3;
<h3 class=​"ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" role=​"tab" aria-expanded=​"false" aria-selected=​"false" tabindex=​"0">​…​</h3>​

由于同样的原因,使用 PrimeFaces 小部件变量也不起作用:

function changeAccordionTabPF(accordionWidget,index) {
 PF(accordionWidget).select(index);     
}

如果我先将其用于外部,然后再用于内部,则手风琴Widget var 尚不可用。

Q2:如何通过同步来确保最内层标签上的click() 可用?

此问题的可接受答案必须针对此特定 Primefaces 选项卡情况进行回答;请不要只参考任何其他关于使用回调、延迟/完成/下一个或 Promises 执行异步函数的 stackoverflow 答案,我已经阅读并尝试了几十个。


[编辑:由于第二次单击模拟运行时手风琴最内层选项卡(在 tabView 内的选项卡内)的选择目标不可用,因此无法使用更多示例。部分改编自:https://stackoverflow.com/questions/39717433/how-pass-parameters-to-downstream-function-with-done-or-next]

function changeTabDynamicOuterDeferred(tabViewId, tabId)
{
  $('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();
  return $.Deferred().resolve();
}

function changeTabViewAccordionTabDynamic(tabViewId, accordionId, accordionTabId)
{
  var id = tabViewId + ":" + accordionId + ":" + accordionTabId;
  var div = $("[id=\'" + id + "'\]");
// The id is on the DIV following the H3 to click on.
  var h3 = div.prev();
  h3.click();
}

称为:

changeTabDynamicOuterDeferred(
 '#{tabViewId}',
 '#{tabId}'
).done(
   function() {
     changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')
   }
).fail(
  function(err) {
   console.log(err)
  }
);

一切似乎都正常工作(并且目标 id 的值通过)除了 changeTabViewAccordionTabDynamic 时要选择的元素尚不可用(显然尚未完全从 changeTabDynamicOuterDeferred 中的 click() 呈现),尽管使用了done(

使用带有then( 的promise 的这种变体也存在同样的问题:

function changeTabDynamicOuterDeferredPromise(tabViewId, tabId)
{
  $('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();

  return $.Deferred(
        function (dfd) {
            dfd.resolve()
        }
  ).promise();
}

changeTabViewAccordionTabDynamic 同上,调用如下:

changeTabDynamicOuterDeferredPromise(
 '#{tabViewId}',
 '#{tabId}'
).then(
  function () {
    changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')
  }
).fail(
  function(err) {
    console.log(err)
  }
);

编辑:我尝试在第二个函数中放置一个 while 循环,以模仿对手风琴面板内的内部选项卡的单击(在 tabView 的选项卡内),部分原因是我可以更好地看到什么时候可用。直到长循环完成后才能看到外部选项卡(顶级tabView内)的click()效果,因此选择总是失败:

function changeTabViewAccordionTabDynamic(tabViewId, accordionId, accordionTabId)
{
    var id = tabViewId + ":" + accordionId + ":" + accordionTabId;
    var timer = 0;
    var div = $("[id=\'" + id + "'\]");
    while (div.length < 1 && timer++ < 100000) {
      div = $("[id=\'" + id + "'\]"); // Proper code would make this DRY
      console.log(div.length);
    }
   // The id is on the DIV following the H3 to click on.
   var h3 = div.prev();
   h3.click();
}

无论我从上面使用哪种同步策略,都会发生这种情况。同样,内部手风琴和选项卡的 primefaces widgetVar 不可用。


编辑:更多失败的尝试。以下内容改编自How to execute JavaScript after page load?

jQuery(document).ready(function () {
  jQuery(document).ready(function () {
    changeTabDynamic('#{tabViewId}','#{tabId}');        
    jQuery(document).ready(function () {
      changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}');  
    });        
  });
});    

由于changeTabDynamic('#{tabViewId}','#{tabId}') 导致的更改在下一个函数changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}') 运行时仍然不可用,因此选择器失败。

我尝试了使用 p:remoteCommand 的变体,但遇到了同样的问题:

<p:remoteCommand oncomplete="changeTabDynamic('#{tabViewId}','#{tabId}')" autoRun="true" async="false"/>

<p:remoteCommand oncomplete="changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')" autoRun="true" async="false"/>

同样的问题。

【问题讨论】:

  • 离题:永远不要使用&lt;h:form id="form" prependId="false"&gt; : http://stackoverflow.com/questions/7415230/uiform-with-prependid-false-breaks-fajax-render (if I remember correctly, it breaks p:ajax`来`
  • 试过没有dynamic="true"
  • @Kukeltje 感谢您的建议(以及您在此论坛上的其他常见帮助)。回复prependId="false"。我意识到了这个弱点,但需要迁移大型 Web 应用程序;为未来计划。尝试了“dynamic=false”,是的,简单的回调链接方法可以完美运行,但这在任何情况下都不是我的现实世界网络应用程序的选择,它使用专家系统技术并且必须 100% 准确地更新以反映来自系统其他地方的其他依赖项。因此缓存也是顺便说一句。 (另请参阅问题编辑。)
  • 我会尝试从设置开始此操作时需要设置的选项卡的信号量和 ID 开始。我还将一些 ajax 标签添加到动态标签中,并在这些标签的 oncomplete 中,调用一个 javascript 函数来检查全局设置的值并调用嵌套选项卡的 widgetvars 或模拟点击它们。 'oncompletes' 是你可以用来检查某事是否完成的回调。
  • 对我的评论有意见吗?您可以多次编辑您的问题,但如果问题保持不变,我之前对我将尝试的内容的评论仍然有效。请对此发表评论。

标签: javascript jquery jsf primefaces


【解决方案1】:

现在已经找到了一个通过activeIndex 使用纯 Primefaces+JSF 的工作解决方案(没有任何 JavaScript 伪点击技巧),见下文。上面关于尝试同步 JavaScript 点击外部 p:tab 和内部 p:tab 的失败尝试的其余描述现在被认为是学术性的(现在对我来说是低优先级),但仍然欢迎任何关于他们失败原因的反馈。


以下在外部p:tabView 中使用activeIndex 然后内部p:accordionPanel 适用于动态非缓存。

我坚持(但现在放弃)尝试使用idwidgetVar 模仿对外部和内部选项卡的点击的原因之一是为了避免硬编码activeIndex 数字(所以该解决方案对于插入新选项卡是稳健的,因为每个目标 idwidgetVar 都是稳定的);事实证明,如果使用绑定到导航 bean 的 f:viewParam 参数,则不必对任何 activeIndex 值进行硬编码。

在带有 p:tabView/p:tab/p:accordionPanel/p:tab 嵌套的页面 tabs_accordions.xhtml 中:

<f:view>
  <f:metadata>                
   <f:viewParam name="tabViewActiveIndex" value="#{navBean.tabViewActiveIndex}" required="false"/>
   <f:viewParam name="accordionActiveIndex" value="#{navBean.accordionActiveIndex}" required="false"/>
...                                
 </f:metadata>
</f:view>

<p:tabView 
 id='tabview' 
 dynamic="true" 
 cache="false" 
 widgetVar="widgetTabView"
 activeIndex="#{navBean.tabViewActiveIndex}"
>

 <p:tab title="Outer Tab1" id="tabOuter1">
   Content of Tab1
 </p:tab>

 <p:tab title="Outer Tab2" id="tabOuter2" >

  <p:accordionPanel 
    id="accordion"
    dynamic="true" 
    cache="false" 
    widgetVar="widgetAccordion"
    activeIndex="#{navBean.accordionActiveIndex}" 
    >

     <p:tab title="Inner Tab1" id="tabInner1">
       <h:link 
         outcome="dummy_edit_viewParam" 
         value="Link1: Go to pretend edit page then return to this 1st inner tab">
          <f:param name="stem" value="tabs_accordions"/>
          <f:param name="tabViewActiveIndex" value="#{navBean.tabViewActiveIndex}"/>
          <f:param name="accordionActiveIndex" value="#{navBean.accordionActiveIndex}"/>
         </h:link>                            
      </p:tab>

     <p:tab title="Inner Tab2 " id="tabInner2">
         <h:link 
            outcome="dummy_edit_viewParam"
            value="Link2: Go to pretend edit page then return to this 2nd inner tab">
            <f:param name="stem" value="tabs_accordions"/>
            <f:param name="tabViewActiveIndex" value="#{navBean.tabViewActiveIndex}"/>
            <f:param name="accordionActiveIndex" value="#{navBean.accordionActiveIndex}"/>
          </h:link>                                                        
      </p:tab>

   </p:accordionPanel>
 </p:tab>
</p:tabView>

链接(来自任一内部选项卡)转到 dummy_edit_viewParam.xhtml,其中包含:

<f:view>
 <f:metadata>
   <f:viewParam name="stem" value="#{navBean.stem}" required="true"/>
   <f:viewParam name="tabViewActiveIndex" value="#{navBean.tabViewActiveIndex}" required="true"/>
   <f:viewParam name="accordionActiveIndex" value="#{navBean.accordionActiveIndex}" required="true"/>
  </f:metadata>
</f:view>

然后(例如)在一些编辑后保存,可以返回到任何内部选项卡。

有趣的是,在p:tabViewp:accordionPanel 中,这不仅会打开所需的选项卡,还会在单击选项卡时在导航 bean 中设置相应的值(即打开一个初始参数所针对的选项卡以外的选项卡):

activeIndex="#{navBean.tabViewActiveIndex}" 

activeIndex="#{navBean.accordionActiveIndex}" 

例如,如果您使用它进入视图循环,它会打开第二个外部选项卡的第一个内部选项卡:

/faces/tabs_accordions.xhtml?tabViewActiveIndex=1&accordionActiveIndex=0

如果您然后单击第二个外部选项卡的第二个内部选项卡它将设置#{navBean.accordionActiveIndex} 为与该选项卡对应的1

因此,当您点击此链接时(从第二个外部选项卡的第二个内部选项卡中),这将发送信息以定位正确的选项卡:

<p:tab title="Inner Tab2 " id="tabInner2">
  <h:link 
    outcome="dummy_edit_viewParam"
    value="Link2: Go to pretend edit page then return to this 2nd inner tab">
    <f:param name="stem" value="tabs_accordions"/>
    <f:param name="tabViewActiveIndex" value="#{navBean.tabViewActiveIndex}"/>
    <f:param name="accordionActiveIndex" value="#{navBean.accordionActiveIndex}"/>
  </h:link>                                                        
</p:tab>

当这些参数用于最终返回到原始 tabView/accordionPanel 时(例如在另一个页面中编辑值并返回保存后),它现在将自动定位到第二个内部选项卡:

/faces/tabs_accordions.xhtml?tabViewActiveIndex=1&accordionActiveIndex=1

例如,用于返回页面的保存按钮的操作可能是:

public String saveReturnToTabViewAccordionUseParams() {    
  return "tabs_accordions?faces-redirect=true&includeViewParams=true";
}    

然后所有这些都可以在没有对每个目标 activeIndex 进行任何硬编码的情况下工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多