Здесь происходит много побочных вещей, и 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=" - " />
<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
, чтобы связать определенное действие, которое будет выполнено на стороне сервера. Можно было бы обсудить гораздо больше, но, вероятно, вы быстро отбросите мой ответ, и поэтому вам не нужно больше писать, иначе вам потребуется некоторое время, чтобы все переварить и задать возможные вопросы. Пока все это я напишу.