XPages execMode частично удаляет элементы DOM, когда они помещаются в визуализированный - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть fileUploader на одном из моих XPages. Проблема, с которой я столкнулся, заключается в том, что, когда я пытаюсь использовать его со свойством 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. Отображается 2-я таблица (отображается условие rendered="#{javascript:getComponent('input_step').getValue()=='2'}")
  6. Я пытался загрузить файл во вторую таблицу
  7. Тогда есть код, который вызывает кнопку обновления (я опубликую это ниже)
  8. Загружает файл на сервер, но удаляет элемент DOM, который он должен обновлять и выполнять на
  9. Тогда просто для эксперимента я нажал «Предыдущий шаг», который обновляет весь div_main
  10. НО! Вместо обновления div_main ничего не происходит! Он только обновляется, но не входит в код, который устанавливает input_step обратно в 1.
  11. Когда я снова нажимаю «Предыдущий шаг», он возвращается к первому шагу, который работает как следует

У меня абсолютно нет ИДЕИ, почему это происходит. Это действительно раздражает Я никогда не сталкивался с такой жуткой проблемой в своей жизни.

Вот 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>

Вот сам файл Uploader:

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

Достаточно взглянуть на кнопку Обновить и скрытую 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, который он должен обновить, и мне нужно перезагрузить страницу или нажать кнопку «Предыдущий шаг», чтобы увидеть файлы, которые я только что загрузил.

Этот случай настолько эзотерический, что я даже не знаю, что делать и почему это происходит. Надеюсь, ты поможешь. Заранее спасибо.

1 Ответ

0 голосов
/ 13 ноября 2018

Здесь происходит много побочных вещей, и SoC полностью отсутствует. У вас html-разметка с вкраплениями SSJS, используемой для различных целей, что делает изучение кода излишне обременительным (если XSP + SSJS не вызывает сотрясений, я не знаю, что это).

Я бы предложил сделать несколько шагов назад и получить более глубокое понимание того, что происходит и как можно что-то делать, чтобы код стал меньше выходить из-под контроля (и глаз).

Прежде всего, важно понять, почему MVC и SoC являются важными понятиями: другими словами, есть код для извлечения и сохранения ваших внутренних данных и код для их представления. Вы не хотите манипулировать своими внутренними данными в том же месте, где вы их представляете. Для достижения этой цели код должен быть организован в «слоях»: один для общения с базой данных, один для бизнес-логики, один для презентации. Для краткости я не буду много говорить о первых двух слоях - и я лишил большинство остальных - но я упросту организацию кода, предоставляя хуки, которые вы можете использовать для выполнения этой части.

Управляемые бобы

Управляемые 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 определяет, что бин будет живым до тех пор, пока вы остаетесь на странице. Я заметил, что твоя презентация продумана как волшебник, в котором ты можешь идти вперед и назад. В контроллере я создал 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 позаботится о том, чтобы отображался только контейнер текущего шага. Свойство xp:key, которое можно назначить любому компоненту XPages, принимает значение самого имени перечисления (#{javascript:wam.step.name()}). На этом этапе вы можете лучше организовать свой код, не оценивая одно и то же снова и снова.

Теперь, <xc:whatamessupload /> - это пользовательский элемент управления, в который я перенес вашу логику загрузки. Я предполагаю, что вы пойдете на эти длины, чтобы сделать его более интересным для пользователя, обычное действие onchange в элементе управления fileUpload даст тот же результат, упрощая тот же процесс. Во всяком случае, я понимаю более удобный подход. Учитывая, что в данный момент пользовательский элемент управления не имеет какого-либо конкретного свойства, я просто хотел, чтобы все было просто и кратко.

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

В приведенном выше фрагменте кода предполагается, что 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 = '';
}

Использование, которое вы используете при составлении идентификаторов, которые вы собираетесь использовать позже, не является необходимым и делает его более подверженным ошибкам. Ничего из того, что не может обработать текущая структура и синтаксис, и это абсолютно повторяемое (вы можете иметь несколько пользовательских элементов управления на странице). Вам также не нужно иметь input type="file" и элемент управления загрузкой. Вы можете справиться со всем с последним. В xp:fileUpload control значение связано с #{requestScope.uploadedFile}, что я и решаю в методе контроллера uploadFile метод. Как видите, я использую компонент xp:eventHandler, чтобы связать определенное действие, которое будет выполнено на стороне сервера. Можно было бы обсудить гораздо больше, но, вероятно, вы быстро отбросите мой ответ, и поэтому вам не нужно больше писать, иначе вам потребуется некоторое время, чтобы все переварить и задать возможные вопросы. Пока все это я напишу.

...