Server Side Validation - computing the required parameter to a viewScope - xpages

I'm having an issue with some server side validation and I can't tell if this is the expected behavior in XPages or if I'm missing something simple here.
I have a field that has a computed validation formula - basically, it becomes required when a viewScope variable gets set.
My submit button sets the viewScope and does a full refresh.
I would expect the form to be submitted, the validation formula gets evaluated, and if it fails the validation failure would be displayed. But when I click my submit button the form is submitted without error. If I click it again, validation fails as expected.
To illustrate my issue I created a simple example:
<xp:inputText id="inputText1" required="#{javascript:viewScope.validate}">
<xp:this.validators>
<xp:validateRequired message="This field is required."></xp:validateRequired>
</xp:this.validators>
</xp:inputText>
 
<xp:button value="Submit" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete" immediate="false" save="true">
<xp:this.action><![CDATA[#{javascript:viewScope.validate=true;}]]></xp:this.action>
<xp:this.script><![CDATA[dojo.attr(dojo.byId("#{id:inputText1}"),"aria-required","true")]]></xp:this.script>
</xp:eventHandler>
</xp:button>
<xp:br></xp:br>
<xp:message id="message1" for="inputText1"></xp:message>
As you can see, I also tried adding the aria-required attribute with csjs within the button in the hopes it would initiate validation.
If validation is working as expected are there any suggestions for getting dynamic validation like this to function properly? I know I can do something with querySave on the data source (I excluded a data source from the example for simplicity) but was hoping there is a simpler solution than that.

It looks like the problem is misunderstanding the lifecycle. You're specifying whether the field is required in a viewScope variable. When the page is first loaded, the viewScope is false, so the field is not required. After you submit, the viewScope variable is set, the page rendered again. Only now does the field become required, but it is only required the next time the form is submitted.
If you want the field to be required, you'll need the required property to be evaluated to true and passed to the browser before the form is submitted.
There are a number of alternatives for the functionality you're trying to manage, running SSJS and failing if certain criteria are met.
One is to use the validator property rather than calculating the required property. The validator allows you to run code but you will need to use this.getSubmittedValue() to check the field's value - the validator runs earlier in the lifecycle before the submittedValue is passed to the value property.
The second option is to put the validation in the Submit button. You can use facesContext.addMessage(). If you google xpages facesContext addMessage there are plenty of examples of how to do it. Then do "return false" to abort.

It looks like you might want conditional validation. I wrote a blogpost about this a while back:
Making validation behave properly

Related

XPages: disabled property breaks required computation for xp:inputText

On my application I have an xp:inputText control with the disabled property set to true and the required property computed as followed:
<xp:inputText
id="txtSecurityLevel"
styleClass="form-control-static"
value="#{employeeBean.employee.securityLvl}"
disabled="true"
>
<xp:this.validators>
<xp:validateRequired
message="Unsufficient level of security">
</xp:validateRequired>
</xp:this.validators>
<xp:this.required><![CDATA[#{javascript:return submittedBy("btnProceed")}]]></xp:this.required>
</xp:inputText>
The value of the field is pre-populated and I do not want users to alter the value.
When I use the same approach for other inputText controls but with the disabled property set to false the validation is initiated for these fields.
Is there another approach I should use?
To make the field non-editable, use the readonly property.
Internet Explorer used to allow the disabled and readonly property interchangeably. But with IE10 they changed the behaviour so disabled worked as in other browsers, i.e. the value is not passed from the browser to the server. Because it's not passed back from the browser, your validation fails. disabled is for an input that should be unusable for the browser, see https://www.w3schools.com/Tags/att_input_disabled.asp. What you want is for it to be readonly for the user.

Xpages - Call bean method on click of a button

I have a form displayed in read mode using custom control which is bound to a view scoped bean. I need to have a picker on this CC so that users can select other documents. These display as links (generated using repeat control).
I planned to trigger a method in my view scoped bean to save this value when selection changes.
Now I am stuck with:
onChange event of multi-valued field (used with picker) does not trigger SSJS code
I tried creating a button which I clicked using CSJS on onChange of above field - this does not work either.
In short, SSJS code is not being triggered.
This is troubling me as I have a created a file download control wherein I have added a remove button calling a method in bean and it works just fine.
I am using Debugtoolbar by Mark Leusink and I am not able to display a simple message or set any scope variable. this is happening for onChange and onClick events!!
I have provided my CC code below. If you want you can put it in any Xpage bound to a view scoped bean.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:panel id="panel1">
<xp:inputTextarea id="inputTextarea1" style="display:none" value="#{viewScope[compositeData.pickerDetails.beanName][compositeData.pickerDetails.saveToField]}"
multipleSeparator=","
>
<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="repeatLinks">
<xp:this.action><![CDATA[#{javascript://viewScope[compositeData.pickerDetails.beanName][compositeData.pickerDetails.saveToField]= getComponent("inputTextarea1").getValue();
//viewScope.get(compositeData.pickerDetails.beanName).setValue(compositeData.pickerDetails.saveToField,getComponent("inputTextarea1").getValue());
//viewScope[compositeData.pickerDetails.beanName].linkDocs(compositeData.pickerDetails.saveToField,getComponent("inputTextarea1").getValue());}]]></xp:this.action>
<xp:this.script><![CDATA[//console.log("Btn found : "+document.getElementById(getID("btnLinks")));
document.getElementById(getID("btnLinks")).click();]]></xp:this.script>
</xp:eventHandler>
</xp:inputTextarea>
<xe:valuePicker id="valuePicker1" listHeight="auto" listWidth="auto" dialogTitle="#{javascript:compositeData.pickerDetails.title}" for="inputTextarea1">
<xe:this.dataProvider>
<xe:simpleValuePicker labelSeparator="|">
<xe:this.valueList><![CDATA[#{javascript:var cd = compositeData.pickerDetails;
getPickerList(cd.viewName,cd.filter,cd.filterField);}]]></xe:this.valueList>
</xe:simpleValuePicker>
</xe:this.dataProvider>
</xe:valuePicker>
<xp:repeat id="repeatLinks" rows="30" var="docLink">
<xp:this.value><![CDATA[#{viewScope[compositeData.pickerDetails.beanName][compositeData.pickerDetails.saveToField]}]]></xp:this.value>
<xp:text escape="false" id="computedField1">
<!-- Link is generated here -->
</xp:text>
<xp:br></xp:br>
</xp:repeat>
<xp:button value="Click Me" id="btnLinks" refreshId="repeatLinks" refreshMode="partial">
<xp:this.onclick><![CDATA[#{javascript:viewScope[compositeData.pickerDetails.beanName].linkDocs(compositeData.pickerDetails.saveToField,getComponent("inputTextarea1").getValue());}]]></xp:this.onclick>
</xp:button>
</xp:panel>
</xp:view>
You mention this is a custom control. The most likely cause then is validation failure elsewhere on the page. Your button calls SSJS without execMode="partial" and an execId. This means the complete XPage is validated during the partial refresh. Your refreshId doesn't include an errors block, so there's nothing to alert the user (and you!) if there is a validation error.
Setting execMode="partial" and execId="panel1" on your button should resolve the problem.
If that's the case, for the future I would recommend adding a PhaseListener into your applications so you can easily output what phases are being triggered and/or always ensuring the refreshArea includes an errors control.
If you remove the style "display:none;" does the code then trigger?
There might be a validation failure that's taking place. What happens if you select "process data without validation" for these events?

How to set the value of a component to a number

I use the value for 'first' on a repeat control to know where I was as
sessionScope.ssCat1First = getComponent("repeatCat1").first;
so I can set the Starting Index value to ssCat1First and I'm on the right page. However, under some conditions I need to reset first on the component
I tried
getComponent("repeatCat1").first.setValue(0);
but I get the error " Error calling method 'setValue(number)' on an object of type 'Number [JavaScript Object]'"
So it looks like where I have 0 in need a Java Object? If so how would I do that?
You get a list of all properties and methods of Xsp components from JavaDoc Package com.ibm.xsp.component.xp.
How you can find the control's component class? Just print the object's class name to servers console with a button. For your repeat control example you'd write:
<xp:button
value="Show class"
id="button2">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:print(getComponent("repeatCat1"))}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
and console will show you something like
HTTP JVM: com.ibm.xsp.component.xp.XspDataIterator#20f620f6.
Now, look for class XspDataIterator in JavaDoc. This class doesn't have a method getFirst() or setFirst() but the parent class com.ibm.xsp.component.UIDataIterator has:
Keep in mind that getComponent("repeatCat1").first is the Expression Language version of getComponent("repeatCat1").getFirst(). That's what really gets executed.
The same applies to a pager. You'd look for XspPager and then for parents class com.ibm.xsp.component.UIPager. There you can see how to set the page number for a pager:
Use setFirst():
getComponent("repeatCat1").setFirst(0);

XPages form won't save field values if save button is within a xe:dialog

I have a Xpage with two custom controls containing editable fields tied to the single data source on the Xpage. On the Xpage I use a xe:dialog that contains a button to save the data source document1 (using SSJS). No validation is occurring yet. I use a xe:dialogButtonBar to call the xe:dialog (using CSJS) which opens fine and then click the OK button containing Action Save Document data source.
diablogButtonBar onClick call to open dialog.
XSP.openDialog("#{id:dialogSaveAsDraft}");
With this configuration the document is saved but the editable fields are not created nor data saved. The Xpage has the following two properties set, computeWithForm: onsave, action:editDocument but have tried createDocument too.
Here is the twist: If I take the button in the xe:dialog and place it outside the xe:dialog, the button works and the Xpage and all editable fields save properly.
What am I missing? I have done almost exactly the same thing before but instead of using the xe:dialogButtonBar I used a string of buttons. I wanted to use the xe:dialogButton Bar to organize the UI.
Can some one explain why that would occur?
The problem is about the form submission. When you launch the dialog, editable fields on the other parts of the page are not submitted (dialog is launched upon partial refresh). Therefore the back-end component tree is not aware of the field updates of the client-side.
You can either open the dialog from the SSJS (so it submits the page) or create a "noupdate" submission with onComplete script to launch the dialog.
<xp:link
escape="true"
text="Open Dialog with SSJS"
id="link1">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="norefresh"
action="#{javascript:getComponent('dialogSaveAsDraft').show()}">
</xp:eventHandler>
</xp:link>
<xp:link
escape="true"
text="Open Dialog with onComplete"
id="link2">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="norefresh"
onComplete="XSP.openDialog('#{id:dialogSaveAsDraft}');">
</xp:eventHandler>
</xp:link>
If you use ComputeWithForm then you have to have a DisplayErrors control. That is where validation error messages will appear. Do you have that? If not you could be getting a validation error from the formulas on the form and they have no place to display the error messages. By the way, using ComputeWithForm is not really a good thing to do. You should repeat your validation logic directly on the XPage. Otherwise, you got too many things going on, the validation/translation that happens at the XPage level and again at the form level.

OnClick SSJS with partial refresh on xp:link control doesnt work?

I have the following LINK control on the form and would like to call the managed bean method onclick and do partial refresh (refreshing specifing part of the page). But I just found out, that this doesnt work, I can see that clicking on link sends XHR request to server, but the managed bean method call (entire onClick SSJS event) is not triggered. If I redesign thi as button controll, thigs are working properly, but I need link in this case. Is it some bug or my misuse of concept?
<xp:link escape="true" text="" id="link2" >
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" disableValidators="true" refreshId="create_recipe_form_panel">
<xp:this.action><![CDATA[#{javascript:F.getRecipe().adjustWt()}]]></xp:this.action>
</xp:eventHandler>
</xp:link>
I would say: misuse of concept. A links should "send you somewhere else" while a button "does something for you". So most likely the link destination and the click action get in each others way. Use a button and give it a class. Play with the css until you have the visual you desire.

Resources