XPages execMode partial removes DOM elements when it's put in rendered
Asked Answered
R

1

8

I have a fileUploader on one of my XPages. The problem which I have encountered is when I try to use it with xp.this.rendered property it actually REMOVES a DOM element which it's supposed to update. Without the property it works so well, but sometimes I have to display the fileUploader only when some condition is true:

Here's what I mean:enter image description here

What I did here:

  1. Opened a page with step №1 (default). It's just a table in the main div_main element (input_step is 1)
  2. Uploaded a file there
  3. Cliked the Next step button
  4. It refreshed div_main element and set input_step component value to 2
  5. The 2nd table is displayed (the rendered condition is rendered="#{javascript:getComponent('input_step').getValue()=='2'}")
  6. I tried to upload a file to the second table
  7. Then there's the code which triggers the refresh button (I'll post it below)
  8. It does upload file to the server, but removes the DOM element it's supposed to refresh and execute on
  9. Then just for experement I clicked the "Previous step" which refreshs the entire div_main
  10. BUT! Instead of refreshing div_main it doesn't do anything! It only refreshes itself, but doesn't go into the code which sets input_step back to 1.
  11. When I click again on the "Previous step" it goes back to the first step it works as it should

I absolutely have no IDEA why this happens. It's really irritating I have never come across such an eerie problem in my life.

Here's the 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>

Here's the fileUploader itself:

<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>

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

}

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.

Randa answered 7/11, 2018 at 9:31 Comment(4)
I can not find div_main in your code snippet. Make sure that you refresh an element that is in the component tree (without a rendered property). You can not refresh a hidden componentIntransitive
I've added div_main. Please take a lookRanda
Finnaly I managed to check it on another page - it works even with the rendered pop. The div_main is to blame, I'm sureRanda
please format the code properlyCajun
C
0

There's a lot of collateral things going on here and SoC is completely lacking. You have html markup interspersed with SSJS used for different purposes which makes examining the code unnecessarily burdensome (if XSP + SSJS isn't jarring I don't know what is).

I would suggest to take a few steps back and acquire a deeper understanding of what is going on and how things could be handled to have code get less out of hand (and eyes).

First of all it's important to understand why MVC and SoC are important concepts: in other words there's code to retrieve and save your backend data and code to present it. You don't want to manipulate your back-end data in the same place you are presenting it. To achieve such goal code should be organized in "layers": one to talk to the database, one for the business logic, one for the presentation. For brevity sake I'm not going to talk much about the first 2 layers - and I stripped most of the others - but I will organize the code in a simpler way providing hooks you can use to do that part.

Managed Beans

Managed beans can be viewed as helpers to your application: they can be used throughout your app if they are generic enough or "tied" to a page in order to be used as specific controllers. In this case we will set up a bean as controller to the page.

The binding

Generally you shouldn't talk to the component methods themselves but change the property values such components are bound to. What you will see is the application of such rule.

The code

In the faces-config.xml we define it as such:

<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 is the friendly name that will be used to reference it. demo.bean.WhatAMess is the name of the class inclusive of the package. view defines that the bean will be alive for as long as you stay on the page. I noticed your presentation is thought out as a wizard where you can go back and forth. In the controller I created an enum that should facilitate this kind of approach. Instead of using numbers and evaluating whether to show something because you are on a given numbered step we can used a switchFacet paired with that enum and have ease of understanding.

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

}

We will then bind this class' methods to the various component properties on the page:

<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>

The xe:switchFacet will take care of ensuring only the current step's container is shown. The xp:key property that can be assigned to any XPages component takes the value of the enum name itself (#{javascript:wam.step.name()}). At this point you can better organize your code without evaluating the same thing time and again.

Now, <xc:whatamessupload /> is a custom control where I ported your upload logic, I presume you're going to those lengths to make it more interesting to the user, a normal onchange action on the fileUpload control would yield the same result while simplifying the same process. Anyway, I understand a more user-friendly approach. Having that being said the custom control doesn't have any specific property at the moment, I just wanted to keep things dead-simple and be brief.

<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>

The above piece of code assumes wam is around but it could be refactored to make sure wam gets passed as a compositeData property thus promoting code reusability.

Finally the uploader js function:

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

The use you make in composing the ids you are going to use later on is not necessary and makes it more error prone. Nothing that the current framework and syntax can't handle, and it's absolutely repeatable (you can have multiple custom controls on the page). You also don't need to have an input type="file" and the upload control. You can handle everything with the latter. In the xp:fileUpload control value is bound to #{requestScope.uploadedFile} which is what I resolve on the controller method uploadFile method. As you can see I leverage xp:eventHandler component to tie the specific action that will be performed server side. There would be much more to discuss but it's probable you will quickly ditch my answer, and therefore there's no need to write more, or it will take you some time to digest everything and come up with eventual questions. Till that time that's all I will write.

Colicroot answered 13/11, 2018 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.