XPages execMode部分删除DOM元素在渲染时

J.D*_*Doe 8 javascript file-upload xpages ssjs

我的一个XPage上有一个fileUploader.我遇到的问题是当我尝试将它与xp.this.rendered属性一起使用时,它实际上删除了一个它应该更新的DOM元素.没有属性它运行得很好,但有时我只有在某些条件为真时才显示fileUploader:

这就是我的意思:enter image description here

我在这里做了什么:

  1. 打开一个步骤为1的页面(默认).它只是主div_main元素中的一个表(input_step是1)
  2. 在那里上传了一个文件
  3. 点击下一步按钮
  4. 它刷新div_main元素并将input_step组件值设置为2
  5. 显示第二个表(呈现的条件是rendered="#{javascript:getComponent('input_step').getValue()=='2'}")
  6. 我试图将文件上传到第二个表
  7. 然后是触发刷新按钮的代码(我将在下面发布)
  8. 它会将文件上传到服务器,但会删除它应该刷新并执行的DOM元素
  9. 然后,为了实现经验,我点击了"上一步",刷新了整个 div_main
  10. 但!而不是刷新div_main它没有做任何事情!它只刷新自己,但不会进入设置input_step为1 的代码.
  11. 当我再次单击"上一步"时,它会返回到它应该正常工作的第一步

我绝对没有IDEA为什么会这样.这真的很烦人,我从来没有遇到过这样一个令人毛骨悚然的问题.

这是 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>
Run Code Online (Sandbox Code Playgroud)

这是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>
Run Code Online (Sandbox Code Playgroud)

Just take a look at the Refresh button and hidden div besides these elements nothing should be wrong

The code which does the upload is here:

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 = '';

}
Run Code Online (Sandbox Code Playgroud)

So, when document.querySelector('[id$=' + refreshID + ']').click(); is triggered it refreshes the element only if xp.this.rendered has been true since the beginning of the page. Otherwise, it removes the DOM element which it's supposed to refresh and I have to reload page or click the "Previous step" button in order to see the files which I've just uploaded.

This case is so esoteric, I don't even no what to do and why this happens. Hope you'll help. Thanks in advance.

shi*_*lem 0

这里有很多附带的事情,而 SoC 却完全缺乏。您的 html 标记中散布着用于不同目的的 SSJS,这使得检查代码变得不必要的繁重(如果 XSP + SSJS 不刺耳的话我不知道是什么)。

我建议退后几步,更深入地了解正在发生的事情以及如何处理事情,以使代码不那么失控(和眼睛)。

首先,了解为什么 MVC 和 SoC 是重要的概念很重要:换句话说,有用于检索和保存后端数据的代码以及用于呈现它的代码。您不想在呈现后端数据的同一位置操作后端数据。为了实现这样的目标,代码应该按“层”组织:一层用于与数据库通信,一层用于业务逻辑,一层用于表示。为了简洁起见,我不会过多讨论前两层 - 并且我剥离了其他大部分 - 但我将以更简单的方式组织代码,提供可用于完成该部分的钩子。

托管 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>
Run Code Online (Sandbox Code Playgroud)

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());
    }

}
Run Code Online (Sandbox Code Playgroud)

然后,我们将此类的方法绑定到页面上的各个组件属性:

<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>
Run Code Online (Sandbox Code Playgroud)

xe:switchFacet负责确保仅显示当前步骤的容器。可以分配给任何 XPage 组件的属性xp:key采用枚举名称本身的值 ( #{javascript:wam.step.name()})。此时,您可以更好地组织代码,而无需一次又一次地评估相同的内容。

现在,<xc:whatamessupload />是一个自定义控件,我在其中移植了您的上传逻辑,我认为您将采用这些长度以使用户更感兴趣,onchangefileUpload 控件上的正常操作将产生相同的结果,同时简化相同的过程。无论如何,我理解一种更用户友好的方法。话虽这么说,自定义控件目前没有任何特定属性,我只是想让事情变得简单明了。

<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="&#160;-&#160;" />
                    <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>
Run Code Online (Sandbox Code Playgroud)

上面的代码假设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 = '';
}
Run Code Online (Sandbox Code Playgroud)

您在编写稍后要使用的 id 时的使用是不必要的,并且更容易出错。当前的框架和语法没有什么不能处理的,而且它绝对是可重复的(您可以在页面上有多个自定义控件)。您也不需要有input type="file"上传控件。你可以用后者来处理一切。在xp:fileUpload control值绑定到#{requestScope.uploadedFile}哪个是我在控制器方法uploadFile方法上解析的。正如您所看到的,我利用xp:eventHandler组件来绑定将在服务器端执行的特定操作。还有更多内容需要讨论,但您很可能很快就会放弃我的答案,因此没有必要写更多内容,否则您将需要一些时间来消化所有内容并提出最终的问题。到那时我就写这么多。