XPages execMode partial 在渲染时移除 DOM 元素
Posted
技术标签:
【中文标题】XPages execMode partial 在渲染时移除 DOM 元素【英文标题】:XPages execMode partial removes DOM elements when it's put in rendered 【发布时间】:2019-04-10 17:15:37 【问题描述】:我的一个 XPage 上有一个 fileUploader。我遇到的问题是当我尝试将它与 xp.this.rendered 属性一起使用时,它实际上删除了它应该更新的 DOM 元素。如果没有该属性,它可以很好地工作,但有时我必须仅在某些条件为真时才显示 fileUploader:
这就是我的意思:
我在这里做了什么:
-
使用步骤 №1(默认)打开了一个页面。它只是主要
div_main
元素中的一个表(input_step
为 1)
在那里上传了一个文件
点了“下一步”按钮
它刷新了div_main
元素并将input_step
组件值设置为2
显示第二张表(渲染条件为rendered="#javascript:getComponent('input_step').getValue()=='2'"
)
我尝试将文件上传到第二个表
然后是触发刷新按钮的代码(我会在下面发布)
它确实将文件上传到服务器,但删除了它应该刷新和执行的 DOM 元素
然后只是为了实验,我单击“上一步”刷新整个div_main
但是!而不是刷新div_main
它什么都不做!它只会刷新自己,但不会进入将 input_step
设置回 1 的代码。
当我再次单击“上一步”时,它会返回到它应该正常工作的第一步
我完全不知道为什么会发生这种情况。真的很烦人 我这辈子从来没有遇到过这么诡异的问题。
这是div_main
<xp:div styleClass="doc_list" id="div_main">
<xp:table style="width:100.0%">
<xp:tr>
<xp:td style="width:25.0%" align="center" valign="top"
styleClass="background">
</xp:td>
<xp:td id="content" styleClass="background_field">
<xp:table id="table_nav" style="width:100.0%">
<xp:tr>
<xp:td style="width:100.0%" align="center"
styleClass="background_field">
<xp:label id="label152"
styleClass="doc_header_step_title">
<xp:this.value><![CDATA[#javascript:var step=getComponent('input_step').getValue()
switch (step)
case "1":
return('Step1')
break;
case "2":
return('Step2')
break;
case "3":
return('Step3')
break;
case "4":
return('Step4')
break;
case "":
return('Step1')
break;
]]></xp:this.value>
</xp:label>
</xp:td>
</xp:tr>
</xp:table>
<xc:User_request_new_step_1></xc:User_request_new_step_1>
<xc:User_request_new_step_2></xc:User_request_new_step_2>
<!-- <xc:User_request_step_3></xc:User_request_step_3> -->
<xp:table id="table_step4" style="width:100.0%"
rendered="#javascript:getComponent('input_step').getValue()=='4'">
<xp:tr>
<xp:td style="width:50.0%" align="center">
<xc:Doc2_Tree_Structure
syselem="gecho_directory">
</xc:Doc2_Tree_Structure>
</xp:td>
</xp:tr>
</xp:table>
<xp:table id="table_step5" style="width:100.0%"
rendered="false">
<xp:tr id="tr_sign">
<xp:td id="td_sign">
<xc:Event2_cryptopro></xc:Event2_cryptopro>
</xp:td>
</xp:tr>
<xp:tr id="tr_sign_button">
<xp:td id="td_sign_button" align="center">
<xp:inputHidden id="gechoSign"
value="#doc_source.gechoSign">
</xp:inputHidden>
<xp:table style="width:1.0%">
<xp:tr>
<xp:td style="width:1.0%"
styleClass="doc_field_select" id="td10">
<xp:text
id="cf_create_button"
value="#javascript:return('Sign')" escape="false">
</xp:text>
<xp:eventHandler
event="onclick" submit="true" refreshMode="partial"
refreshId="table_step5">
<xp:this.action><![CDATA[#javascript:getComponent('inputBase64').setValue(generateSignData(doc_source.getDocument()));
var tprint=getComponent('inputThumbprint').getValue()
var base64id=getComponent('inputBase64').getClientId(facesContext)
var resid=getComponent('gechoSign').getClientId(facesContext)
var csjs="signCryptoPro('"+tprint+"','"+base64id+"','"+resid+"')"
//print(csjs)
view.postScript(csjs)]]></xp:this.action>
</xp:eventHandler>
</xp:td>
</xp:tr>
</xp:table>
</xp:td>
</xp:tr>
</xp:table>
<xp:table id="table_nav_bottom"
style="width:100.0%">
<xp:tr>
<xp:td style="width:25.0%" align="right">
<xp:table>
<xp:tr>
<xp:td
styleClass="doc_field_select" id="td1">
<xp:this.rendered><![CDATA[#javascript:var step=getComponent('input_step').getValue()
if (step=="" | step=="1") return(false)
return(true)]]></xp:this.rendered>
<xp:text escape="false"
id="computedField1"
value="#javascript:return(texticon('arrow-31-left',20,20,'Previous step',false))">
</xp:text>
<xp:eventHandler
event="onclick" submit="true" refreshMode="partial"
refreshId="div_main" disableValidators="true">
<xp:this.action><![CDATA[#javascript:var step=getComponent('input_step').getValue()
print("step value before is " + getComponent('input_step').getValue());
switch (step)
case "1":
getComponent('input_step').setValue('1')
break;
case "2":
getComponent('input_step').setValue('1')
break;
case "3":
getComponent('input_step').setValue('2')
break;
case "4":
getComponent('input_step').setValue('3')
break;
case "":
getComponent('input_step').setValue('1')
break;
print("step value after is " + getComponent('input_step').getValue());
]]></xp:this.action>
</xp:eventHandler>
</xp:td>
</xp:tr>
</xp:table>
</xp:td>
<xp:td style="width:50.0%">
</xp:td>
<xp:td style="width:25.0%" align="left">
<xp:table>
<xp:this.rendered><![CDATA[#javascript:var step=getComponent('input_step').getValue()
if (step=="" | step=="4") return(false)
return(true)
]]></xp:this.rendered>
<xp:tr>
<xp:td
styleClass="doc_field_select" id="NextStep">
<xp:text escape="false"
id="computedField2">
<xp:this.value><![CDATA[#javascript:var step=getComponent('input_step').getValue()
if (step=="" | step=="4") return(texticon('check-mark-5-icon',20,20,'Save',false))
return(texticon('arrow-31',20,20,'Next step',false))
]]></xp:this.value>
</xp:text>
<xp:eventHandler
event="onclick" submit="true" refreshMode="partial"
refreshId="div_main">
<xp:this.action><![CDATA[#javascript:var step=getComponent('input_step').getValue()
switch (step)
case "1":
getComponent('input_step').setValue('2')
break;
case "2":
getComponent('input_step').setValue('3')
break;
case "3":
getComponent('input_step').setValue('4')
break;
case "4":
getComponent('input_step').setValue('4')
break;
case "":
getComponent('input_step').setValue('4')
break;
]]></xp:this.action>
<xp:this.script><![CDATA[var files='#id:Files_from_pers_files_repeat'
var hidden='#id:inputText64'
if (!!document.getElementById(files))
var fileshtml=document.getElementById(files).innerHTML
if( filesHtml !=="" && filesHtml!=="\n" )
document.getElementById(hidden).value="file"
else
document.getElementById(hidden).value=""
]]></xp:this.script>
</xp:eventHandler>
</xp:td>
</xp:tr>
</xp:table>
</xp:td>
</xp:tr>
</xp:table>
</xp:td>
<xp:td style="width:15.0%" styleClass="background">
</xp:td>
</xp:tr>
</xp:table>
<xp:div styleClass="navigation_step">
<xp:table style="width:100.0%">
<xp:tr>
<xp:td style="width:15.0%;white-space:nowrap;"
styleClass="background">
<xp:inputHidden id="input_step" defaultValue="1"
value="#doc_source.InputStep">
</xp:inputHidden>
<xc:Field2_select_nav_readonly resultid="input_step"
refreshid="div_main" multiselect="false" icon="checkbox-12-icon"
icon_deselected="checkbox-19-icon">
<xc:this.valueslist><![CDATA[#javascript:var arr = new Array();
arr.push('Purpose|1')
arr.push('Client|2')
arr.push('Conditions|3')
arr.push('Documentation|4')
//arr.push('Signing|5')
return(arr)]]></xc:this.valueslist>
</xc:Field2_select_nav_readonly>
</xp:td>
<xp:td style="width:85.0%"></xp:td>
</xp:tr>
</xp:table>
</xp:div>
</xp:div>
这是 fileUploader 本身:
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script src="/fileUploader.js" clientSide="true">
</xp:script>
</xp:this.resources>
<xp:div id="$javascript:compositeData.ID+'refresh'">
<xp:messages id="messages1"></xp:messages>
<!-- <xp: message id="message1" for="$javascript:compositeData.ID+'refresh'"></xp:message> -->
<xp:table>
<xp:this.rendered><![CDATA[#javascript:currentDocument.isEditable() && (!context.getUserAgent().isIE(6,9))
]]></xp:this.rendered>
<xp:tr>
<xp:td id="td1" styleClass="doc_field_select">
<xp:text escape="false" id="cf_add">
<xp:this.value><![CDATA[#javascript:return(texticon('plus-5-icon',25,25,'Add',false))
]]></xp:this.value>
</xp:text>
<xp:eventHandler event="onclick" submit="false"
disableValidators="true">
<xp:this.script><![CDATA[document.getElementById("#javascript:compositeData.ID+'_files_input'").click();]]></xp:this.script>
</xp:eventHandler>
</xp:td>
<xp:td styleClass="doc_field_select" id="td2">
<xp:text escape="false" id="cf_deleteall">
<xp:this.value><![CDATA[#javascript:return(texticon('x-mark-4-icon',25,25,'Delete all',false))
]]></xp:this.value>
</xp:text>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete" disableValidators="true">
<xp:this.script><![CDATA[var id='#javascript:
getClientId(compositeData.ID+"_files_upload")';
var tst=document.getElementById(id);
tst.value='';]]></xp:this.script>
<xp:this.action>
<xp:actionGroup>
<xp:executeScript>
<xp:this.script><![CDATA[#javascript:
var doc:NotesDocument=doc_source.getDocument(true);
if (doc==null)
return(null);
if (!doc.hasItem(compositeData.FieldName))
return(null);
var rit1:NotesRichTextItem=doc.getFirstItem(compositeData.FieldName);
if (rit1==null)
return(null);
try
var arr=rit1.getEmbeddedObjects();
catch(e)
return(null);
for(var i = 0; i < arr.length; i++)
doc_source.removeAttachment(compositeData.FieldName, arr[i].getName());
return;
var doc:NotesDocument=doc_source.getDocument(true);
var rit:NotesRichTextItem=doc.getFirstItem(compositeData.FieldName);
if (rit==null)
return('');
var arr=rit.getEmbeddedObjects()
if (arr==null)
return('');
res=[]
for (var i = 0; i < arr.length; i++)
var att:NotesEmbeddedObject=arr[i];
//res.push(att.getName())
arr[i].remove();
//doc.save()
doc=doc_source.getDocument(true);
print('has RichText');
print(doc.hasItem(compositeData.FieldName));
return;]]></xp:this.script>
</xp:executeScript>
<!-- <xp:saveDocument></xp:saveDocument> -->
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
</xp:td>
</xp:tr>
</xp:table>
<div style="height:0px;overflow:hidden">
<input type="file"
id="$javascript:compositeData.ID+'_files_input'"
onchange="#javascript:
var currentCustomID = compositeData.ID;
var filesInput = '\'' + currentCustomID + '_files_input' + '\'';
var filesUpload = '\'' + currentCustomID + '_files_upload' + '\'';
var filesButton = '\'' + currentCustomID + '_files_button' + '\'';
var filesProgress = '\'' + currentCustomID + '_files_progress' + '\'';
return 'files_onchange(' + filesInput + ',' + filesUpload + ',' + filesButton + ',' + filesProgress + ')';
"
multiple="true" uploadOnSelect="true" name="uploadedfile"/>
<xp:fileUpload
id="$javascript:compositeData.ID+'_files_upload'"
useUploadname="true">
<xp:this.value><![CDATA[#doc_source[compositeData.FieldName]]]></xp:this.value>
</xp:fileUpload>
<xp:button value="Refresh"
id="$javascript:compositeData.ID+'_files_button'">
<xp:eventHandler event="onclick"
disableValidators="true"
refreshMode="partial"
refreshId="#javascript:compositeData.ID+'refresh'" execMode="partial"
execId="#javascript:compositeData.ID+'refresh'" submit="true">
<xp:this.action>
<xp:actionGroup>
<xp:actionGroup>
<xp:saveDocument></xp:saveDocument>
<xp:executeScript>
<xp:this.script><![CDATA[#javascript:
if (compositeData.postUpload!=null)
compositeData.postUpload.getScript().invoke(facesContext, null)
]]></xp:this.script>
</xp:executeScript>
</xp:actionGroup>
</xp:actionGroup>
</xp:this.action>
<xp:this.script>
<xp:executeClientScript
script="console.log('The refresh button has just been clicked')">
</xp:executeClientScript>
</xp:this.script>
</xp:eventHandler>
</xp:button>
</div>
<xp:repeat id="$javascript:compositeData.ID+'_files_repeat'"
rows="30" var="rowData" indexVar="rowIndex">
<xp:this.value><![CDATA[#javascript:
try
var doc:NotesDocument=doc_source.getDocument(true);
catch(e)
print(e);
var oss=new OsnovaSession();
oss.CreateError("Загрузка файлов", "Некорректное имя файла");
if (doc==null)
return(null);
if (!doc.hasItem(compositeData.FieldName))
return(null);
var rit1:NotesRichTextItem=doc.getFirstItem(compositeData.FieldName);
if (rit1==null)
return(null);
try
var arr=rit1.getEmbeddedObjects()
catch(e)
return(null);
return(arr)
]]></xp:this.value>
<xp:table>
<xp:tr>
<xp:td styleClass="doc_field_select" id="td4">
<xp:text escape="false" id="cf_file">
<xp:this.value><![CDATA[#javascript:
if (rowData==null)
return('');
var siz=(rowData.getFileSize()/1024).toFixed(1);
siz=siz.replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1 ');
return(texticon('download-9-icon',compositeData.IconSize,compositeData.IconSize,rowData.getName()+' ('+siz+' KB) ',false));
]]></xp:this.value>
<xp:this.style><![CDATA[#javascript:
if(compositeData.IconColor==null)
return('')
else
return('fill:'+compositeData.IconColor+';')
]]></xp:this.style>
</xp:text>
<xp:link escape="true" text="Link"
id="link_test" target="_blank" style="display:none">
<xp:this.value><![CDATA[#javascript:var oss=new OsnovaSession()
try
var db:NotesDatabase=session.getDatabase(null,null);
db.openByReplicaID(session.getCurrentDatabase().getServer(),compositeData.ReplicaID);
var doc=db.getDocumentByUNID(compositeData.DocumentUNID);
catch(e)
var doc=null;
if(doc==null)
var doc:NotesDocument=doc_source.getDocument();
var db=database;
if (doc==null)
return(null);
//http(s)://[yourserver]/[application.nsf]/[viewname|0]/[UNID| ViewKey]/$File/[AttachmentName]?Open
var res=oss.ServerURL()+'/';
res+=db.getFilePath().replace(/\\/g,'/');
res+='/0/'+doc.getUniversalID()+'/$File/'+rowData.getName()+'?Open';
return(res);
//Old version
var res=oss.ServerURL()+'/'
res+=db.getFilePath().replace(/\\/g,'/')
res+='/xsp/.ibmmodres/domino/OpenAttachment/'
res+=db.getFilePath().replace(/\\/g,'/')+'/'
res+=doc.getUniversalID()+'/$File/'+rowData.getName()+'?Open'
return(res)
]]></xp:this.value>
</xp:link>
<xp:eventHandler event="onclick" submit="true"
refreshMode="norefresh" disableValidators="true">
<!-- <xp:this.action><![CDATA[#javascript:/*
var res=oss.ServerURL()+'/'
res+=database.getFilePath().replace(/\\/g,'/')
res+='/xsp/.ibmmodres/domino/OpenAttachment/'
res+=database.getFilePath().replace(/\\/g,'/')+'/'
res+=doc.getUniversalID()+'/$File/'+rowData.getName()+'?Open'
facesContext.getExternalContext().redirect(res)
//view.postScript("window.open('" + res + "'),'_blank'")
*/]]></xp:this.action> -->
<xp:this.script><![CDATA[
var linkID = '#javascript:getClientId("link_test")';
document.getElementById(linkID).click();
/*var refreshButton = '#javascript:compositeData.ID+'_files_button'';
console.log('а хули, увы ' + refreshButton);
document.querySelector('[id$=' + refreshButton + ']').click(); */
]]>
</xp:this.script>
</xp:eventHandler>
</xp:td>
<xp:td styleClass="doc_field_select" id="td3">
<xp:text escape="false" id="cf_del">
<xp:this.value><![CDATA[#javascript:
return(texticon('minus-5-icon',compositeData.IconSize,compositeData.IconSize,'Delete',false));
]]>
</xp:this.value>
<xp:this.style><![CDATA[#javascript:
if(compositeData.IconColor==null)
return('');
else
return('fill:'+compositeData.IconColor+';');
]]></xp:this.style>
</xp:text>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete" disableValidators="true">
<xp:this.action>
<xp:actionGroup>
<xp:executeScript>
<xp:this.script><![CDATA[#javascript:
doc_source.removeAttachment(compositeData.FieldName, rowData.getName())
]]>
</xp:this.script>
</xp:executeScript>
<xp:save></xp:save>
<xp:executeScript>
<xp:this.script><![CDATA[#javascript:
if (compositeData.postDelete!=null)
compositeData.postDelete.getScript().invoke(facesContext, null);
]]></xp:this.script>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action>
<xp:this.script><![CDATA[XSP.allowSubmit();]]></xp:this.script>
</xp:eventHandler>
</xp:td>
</xp:tr>
</xp:table>
</xp:repeat>
<span id="$javascript:compositeData.ID+'_files_progress'">
</span>
</xp:div>
</xp:view>
看看 Refresh 按钮和隐藏的 div 除了这些元素应该没什么问题
上传的代码在这里:
function files_onchange(filesInput, filesUpload, filesButton, filesProgress)
var urfiles = document.getElementById(filesInput).files;
files_upload(filesInput, filesUpload, urfiles, 0, filesButton, filesProgress);
function files_upload(filesInput, uploadID, files, counter, refreshID, filesProgress)
var url = window.location.href;
var formData = new FormData();
var file = null;
var form = XSP.findForm(filesInput);
if (!files) return;
var numberOfFiles = files.length;
file = files[counter];
var max = files.length;
if (counter >= max) return;
formData.append(document.querySelector('[id$=' + uploadID + ']').id, file);
formData.append("$$viewid", dojo.query("input[name='$$viewid']")[0].value);
formData.append("$$xspsubmitid", dojo.query("input[name='$$xspsubmitid']")[0].value);
formData.append("$$xspsubmitvalue", dojo.query("input[name='$$xspsubmitvalue']")[0].value);
formData.append("$$xspsubmitscroll", dojo.query("input[name='$$xspsubmitscroll']")[0].value);
formData.append(form.id, form.id);
console.log(form.id);
var xhr = new XMLHttpRequest();
/* event listners */
xhr.upload.addEventListener("progress", function(e)
if (e.lengthComputable)
var percentComplete = Math.round(e.loaded * 100 / e.total);
document.getElementById(filesProgress).innerHTML = percentComplete.toString()+'%, ( '+(counter+1).toString()+' / '+numberOfFiles.toString()+' )';
else
document.getElementById(filesProgress).innerHTML = '...';
, false);
xhr.addEventListener("load", function()
counter++;
if (counter >= max)
document.getElementById(filesInput).value = "";
if (refreshID)
document.querySelector('[id$=' + refreshID + ']').click(); // Here's where the refresh button is triggered. It DOES work. Always
else
files_upload(filesInput, uploadID, files, counter, refreshID, filesProgress)
, false);
xhr.addEventListener("error", function(e)
document.getElementById(filesProgress).innerHTML = "Error: "+e;
, false);
xhr.addEventListener("abort", function()
document.getElementById(filesProgress).innerHTML = "Upload cancelled.";
, false);
xhr.open("POST", url, true);
xhr.send(formData);
document.querySelector('[id$=' + uploadID + ']').value = '';
因此,当document.querySelector('[id$=' + refreshID + ']').click();
被触发时,它仅在 xp.this.rendered 自页面开始时为真时刷新元素。否则,它会删除它应该刷新的 DOM 元素,我必须重新加载页面或单击“上一步”按钮才能查看我刚刚上传的文件。
这个案子太深奥了,我什至不知道该怎么办,为什么会这样。希望你会有所帮助。提前致谢。
【问题讨论】:
我在您的代码 sn-p 中找不到 div_main。确保刷新组件树中的元素(没有呈现的属性)。您无法刷新隐藏的组件 我添加了div_main
。请看一下
Finnaly 我设法在另一个页面上检查了它 - 它甚至可以使用呈现的弹出窗口。 div_main
是罪魁祸首,我敢肯定
请正确格式化代码
【参考方案1】:
这里有很多附带的东西,而 SoC 完全缺乏。您的 html 标记中散布着用于不同目的的 SSJS,这使得检查代码变得不必要地繁重(如果 XSP + SSJS 不刺耳,我不知道是什么)。
我建议退后几步,更深入地了解正在发生的事情以及如何处理事情以减少代码失控(和眼睛)。
首先,重要的是要理解为什么 MVC 和 SoC 是重要的概念:换句话说,有用于检索和保存后端数据的代码和用于呈现它的代码。您不想在呈现它的同一个地方操作您的后端数据。为了实现这样的目标,代码应该被组织成“层”:一层与数据库对话,一层用于业务逻辑,一层用于表示。为简洁起见,我不会过多地谈论前 2 层 - 我剥离了大部分其他层 - 但我将以更简单的方式组织代码,提供可用于完成该部分的钩子。
托管 Bean
托管 bean 可以被视为应用程序的助手:如果它们足够通用或“绑定”到页面以用作特定控制器,则它们可以在整个应用程序中使用。在这种情况下,我们将设置一个 bean 作为页面的控制器。
绑定
通常,您不应与组件方法本身对话,而应更改此类组件绑定的属性值。您将看到的是此类规则的应用。
代码
在faces-config.xml
我们这样定义它:
<managed-bean>
<managed-bean-name>wam</managed-bean-name>
<managed-bean-class>demo.bean.WhatAMess
</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
</managed-bean>
wam
是用于引用它的友好名称。 demo.bean.WhatAMess
是包含包的类的名称。 view
定义只要您停留在页面上,bean 就会一直存在。我注意到您的演示文稿被认为是一个可以来回切换的向导。在控制器中,我创建了一个enum
,它应该有助于这种方法。我们可以使用与该枚举配对的switchFacet
,而不是使用数字并评估是否显示某些内容,因为您在给定的编号步骤上并且易于理解。
package demo.bean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import com.ibm.xsp.component.UIFileuploadEx.UploadedFile;
import com.ibm.xsp.util.FacesUtil;
public class WhatAMess implements Serializable
public enum Step
// The string param is a dead-simple to handle labels for the various phases
// It could be handled differently, especially if your app is multi-language
PURPOSE, CLIENT, CONDITIONS, DOCUMENTATION("Docs"), SIGNING;
private final String label;
private Step()
this(null);
private Step(String label)
this.label = label;
public String getLabel()
return label != null ? label : name();
// This method returns the step next to the current one
// in the order the enums are actually declared above
public Step getNext()
return (ordinal() < values().length - 1) ? values()[ordinal() + 1] : this;
// This method returns the step previous to the current one
// in the order the enums are actually declared above
public Step getPrevious()
return (ordinal() > 0) ? values()[ordinal() - 1] : this;
// This method says whether there's a step previous to the current one
public boolean isGoingBackward()
return getPrevious() != this;
// This method says whether there's a step next to the current one
public boolean isGoingForward()
return getNext() != this;
private static final long serialVersionUID = 1L;
// Let's step the initial first step
private Step step = Step.values()[0];
// This is a variable used as a proof of concept instead of your business logic
private Map<Step, List<String>> stepFileNames;
public Step getStep()
return step;
public Map<Step, List<String>> getStepFileNames()
if (stepFileNames == null)
stepFileNames = new HashMap<Step, List<String>>();
return stepFileNames;
public void removeStepFileName(String name)
getStepFileNames().get(step).remove(name);
// This action commands the wizard and moves it to the previous step
public void stepBack()
step = step.getPrevious();
// This action commands the wizard and moves it to the next step
public void stepForward()
step = step.getNext();
// This method is your entry point to deal with the uploaded file
public void uploadFile()
// uploadedFile is an arbitrary variable we assigned
// to the fileUpload control on the page
UploadedFile uploadedFile = (UploadedFile) FacesUtil.resolveVariable(
FacesContext.getCurrentInstance(), "uploadedFile");
if (uploadedFile == null)
return;
// Here you are supposed to do something
// more interesting than what I am doing here
List<String> fileNames = getStepFileNames().get(step);
if (fileNames == null)
fileNames = new ArrayList<String>();
getStepFileNames().put(step, fileNames);
fileNames.add(uploadedFile.getFilename());
然后我们将这个类的方法绑定到页面上的各种组件属性:
<xp:div id="containerSteps"
style="width: 500px; background-color: #F0F0F0; padding: 20px">
<xp:text tagName="h1" value="#wam.step.label" style="margin-bottom: 20px" />
<xe:switchFacet selectedFacet="#javascript:wam.step.name()">
<xp:this.facets>
<xp:div xp:key="PURPOSE">
<xc:whatamessupload />
</xp:div>
<xp:div xp:key="CLIENT">
<xc:whatamessupload />
</xp:div>
<xp:div xp:key="CONDITIONS">
<xc:whatamessupload />
</xp:div>
<xc:whatamessupload xp:key="DOCUMENTATION" />
<xp:div xp:key="SIGNING">
<xc:whatamessupload />
</xp:div>
</xp:this.facets>
</xe:switchFacet>
<hr />
<xp:link id="linkStepNext" text="Go to Next" rendered="#wam.step.goingForward"
style="float: right">
<xp:eventHandler event="onclick" submit="true"
execMode="partial" execId="containerSteps" refreshMode="partial"
refreshId="containerSteps" action="#wam.stepForward" />
</xp:link>
<xp:link id="linkStepBack" text="Go to Previous" rendered="#wam.step.goingBackward">
<xp:eventHandler event="onclick" submit="true"
execMode="partial" execId="containerSteps" refreshMode="partial"
refreshId="containerSteps" action="#wam.stepBack" />
</xp:link>
<br />
</xp:div>
xe:switchFacet
将确保仅显示当前步骤的容器。可以分配给任何 XPages 组件的 xp:key
属性采用枚举名称本身的值 (#javascript:wam.step.name()
)。此时,您可以更好地组织代码,而无需一次又一次地评估同一事物。
现在,<xc:whatamessupload />
是一个自定义控件,我在其中移植了您的上传逻辑,我想您会竭尽全力让用户更感兴趣,对 fileUpload 控件执行正常的onchange
操作会产生相同的结果,同时简化相同的过程。无论如何,我理解一种更用户友好的方法。话虽如此,自定义控件目前没有任何特定属性,我只是想让事情变得简单而简短。
<xp:this.resources>
<xp:script src="js/uploader.js" clientSide="true" />
</xp:this.resources>
<xp:div id="containerUpload">
<xp:fileUpload id="fileUpload" value="#requestScope.uploadedFile"
style="display: none"
onchange="file_onchange(this, '#id:eventOnFileUpload', '#id:uploadButton', '#id:containerFiles')">
<xp:eventHandler id="eventOnFileUpload" event="onfileupload"
submit="true" execMode="partial" refreshMode="norefresh" action="#wam.uploadFile" />
</xp:fileUpload>
<xp:button id="uploadButton" type="button" onclick="dojo.byId('#id:fileUpload').click()"
value="Add" />
<xp:div id="containerFiles">
<ul>
<xp:repeat value="#wam.stepFileNames[wam.step]" var="fileName"
disableOutputTag="true">
<li>
<xp:text value="#fileName" />
<xp:text value=" - " />
<xp:link id="linkRemoveFileName" text="Remove">
<xp:eventHandler event="onclick" submit="true"
execMode="partial" refreshMode="partial" refreshId="containerUpload"
action="#javascript:wam.removeStepFileName(fileName)" />
</xp:link>
</li>
</xp:repeat>
</ul>
</xp:div>
</xp:div>
上面的代码假定 wam
存在,但可以对其进行重构以确保 wam 作为 compositeData
属性传递,从而提高代码的可重用性。
最后是上传器js函数:
function update_button_label(buttonId, text, enable)
var button = dojo.byId(buttonId);
button.innerText = text;
if (enable)
button.disabled = !enable;
function file_onchange(inputFile, actionId, buttonId, refreshId)
if (!inputFile || !actionId || !buttonId || !refreshId)
return;
var button = dojo.byId(buttonId);
var buttonOriginalLabel = button.innerText;
// Request
var xhr = new XMLHttpRequest();
// Event listeners
xhr.upload.addEventListener("abort", function()
update_button_label(buttonId, "Upload cancelled.");
setTimeout(update_button_label.bind(this, buttonId, buttonOriginalLabel, true), 1000);
, false);
xhr.upload.addEventListener("error", function(e)
update_button_label(buttonId, "An error occurred. See console log!");
console.log(e);
setTimeout(update_button_label.bind(this, buttonId, buttonOriginalLabel, true), 1000);
, false);
xhr.upload.addEventListener("progress", function(e)
if (!e.lengthComputable)
return;
update_button_label(buttonId, buttonOriginalLabel + ": "
+ Math.round(e.loaded * 100 / e.total) + "%");
, false);
xhr.onreadystatechange = function(res)
if (this.readyState === XMLHttpRequest.DONE && this.status === 200)
update_button_label(buttonId, buttonOriginalLabel, true);
if (refreshId)
XSP.partialRefreshGet(refreshId);
/*
* Here something else could be done with the same request if only
* one is willing to process xhr.response. In it there's the new HTML to replace
* plus scripts to be processed. The above lazy approach requires another request
* instead of be done with the one at hand.
*
* If you want to explore more you should replace "?$$ajaxid=" + "none" with
* "?$$ajaxid=" + refreshId
*
* The below code is copied from the XSP object but it's not complete
*/
// var _23a = xhr.response;
//
// var extractScripts = function xrn_e(_23e, _23f)
// var _240 = _23a.indexOf(_23e);
//
// if (_240 >= 0)
// var _241 = _23a.lastIndexOf(_23f);
// if (_241 >= 0)
// var _242 = _23a.substring(_240 + _23e.length, _241);
// _23a = _23a.substring(0, _240) + _23a.substring(_241 + _23f.length);
// return _242;
//
//
// ;
//
// var _246 = extractScripts(
// "<!-- XSP_UPDATE_SCRIPT_START -->", "<!-- XSP_UPDATE_SCRIPT_END -->\n");
//
// var refreshElement = document.getElementById(refreshId);
//
// refreshElement.innerHTML = _23a;
xhr.open("POST", location.origin + location.pathname
+ "?$$ajaxid=" + "none", true);
// Payload
var form = XSP.findForm(inputFile.id);
var formData = new FormData();
formData.append(inputFile.id, inputFile.files[0]);
formData.append("$$viewid", dojo.query("input[name='$$viewid']")[0].value);
formData.append("$$xspsubmitid", actionId);
formData.append("$$xspexecid", inputFile.id);
formData.append("$$xspsubmitvalue", dojo.query("input[name='$$xspsubmitvalue']")[0].value);
formData.append("$$xspsubmitscroll", dojo.query("input[name='$$xspsubmitscroll']")[0].value);
formData.append(form.id, form.id);
button.disabled = true;
xhr.send(formData);
inputFile.value = '';
您在编写稍后要使用的 id 时所做的使用是不必要的,并且更容易出错。没有什么是当前框架和语法无法处理的,而且它是绝对可重复的(您可以在页面上拥有多个自定义控件)。您也不需要input type="file"
和上传控件。你可以用后者处理一切。在xp:fileUpload control
值绑定到#requestScope.uploadedFile
,这是我在控制器方法uploadFile
方法上解决的问题。如您所见,我利用xp:eventHandler
组件来绑定将在服务器端执行的特定操作。还有很多要讨论的,但你可能很快就会放弃我的答案,因此没有必要写更多,否则你需要一些时间来消化所有内容并提出最终的问题。到那时我就写这么多。
【讨论】:
以上是关于XPages execMode partial 在渲染时移除 DOM 元素的主要内容,如果未能解决你的问题,请参考以下文章