Xpages - JS code fails to execute when clicked via a button - xpages

I have a strange issue, where I have a simple javascript button, that call's 1 function to write some document history and replace the value of one field. It then does a partial update of a container div ID.
This button works fine when accessed via the UK, however when used by Swiss colleagues, the button is doing nothing.
I have in 1 instance, witnessed it not working for 1 user, only for it to suddenly work later in the day, so I'm stumped as to what the issue may be.
Button Code
<xp:button id="btnProceed" styleClass="btn btn-primary" value="Proceed">
<xp:this.disabled><![CDATA[#{javascript:try{
if (getComponent("chkConfirmed").getValue() == "false") {
return true;
} else {
return false;
}
}catch(e){
openLogBean.addError(e,this);
}
}]]></xp:this.disabled>
<xp:eventHandler event="onclick" submit="true" refreshMode="partial"
refreshId="contentWhiteBackground">
<xp:this.action><![CDATA[#{javascript:var dt = new Date();
var arrHistory:array = AddIndepConfirmationHistoryItem(currentDocument, dt, "Confirmed understanding of the opening guidance", userBean.displayName);
document1.replaceItemValue("showIntroduction", "2");
document1.save();
}]]></xp:this.action>
</xp:eventHandler></xp:button>
Called code
Sub AddIndepConfirmationHistoryItem(confdoc As NotesDocument, dt As Variant, action As String, usrname As String)
Dim hlist As StringList
If Len(confdoc.History(0)) = 0 Then
confdoc.History = IndepConfirmationHistoryItem(dt, action, usrname)
Else
Set hlist = New StringList(confdoc.History, "")
Call hlist.Append(IndepConfirmationHistoryItem(dt, action, usrname))
confdoc.History = hlist.Items
End If
End Sub
EDIT:
function AddIndepConfirmationHistoryItem(doc, dt, action, username){
var ArrDocHistory:array = doc.getItemValueArray("History");
var dateTimeFormat = new java.text.SimpleDateFormat("dd/MM/yyyy kk:mm");
var dateTimeString = dateTimeFormat.format(dt);
if(ArrDocHistory.length < 1){
// This should always return an object as it is created when an objectives document is first
// created but do this check to be safe and create an array if for some reason it doesnt exist
ArrDocHistory = [dateTimeString+"|"+action+"|"+username];
}else{
// append new value to the array
ArrDocHistory.push(dateTimeString+"|"+action+"|"+username);
}
doc.replaceItemValue("History",ArrDocHistory);
doc.replaceItemValue("LastUpdatedByName",username);
doc.replaceItemValue("LastUpdatedDate",dt);
//doc.Save();
}

Related

Number of selected documents in Xpages using CSJS

I have view panel in my xpages which shows checkbox against each document, now I would like to know how many documents are being selected by user in csjs so I can confirm with user on how many documents are being deleted and same would be part of alert message ( ex, you have selected 10 documents are you want to proceed? )
and then once confirmation is done I will proceed with SSJS to delete them.
Thanks Man
If You just want to count them, then You can use the CSS selector which is beign distributed to view checkboxes by default (unless You changed it explicitly):
function getSelectedCount() {
var checkboxes = dojo.query(".xspCheckBoxViewColumn");
var selectedCount = 0;
for (var i = 0; i < checkboxes.length; i++) {
//check if it is selected
if (checkboxes[i].checked) selectedCount++;
}
return selectedCount;
}
If You need the documents Note IDs, then the elements returned by this query have a "value" property with NoteID:
dojo.query(".xspCheckBoxViewColumn")[0].value // NoteID of first selected element
If You need the array of selected IDs:
function getSelectedIds() {
var checkboxes = dojo.query(".xspCheckBoxViewColumn");
var selectedIds = new Array();
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) selectedIds = checkboxes[i].value;
}
return selectedIds ;
}
I do something similar but all in ssjs. sessionScope variable store the Note ID which when selected. The count is display on a panel. You use a dialog box to confirm the documents to delete.
Code example:
<xp:viewColumn
id="viewColumn1"
value="">
<xp:checkBox
text=""
id="checkBox1">
<xp:this.readonly><![CDATA[#{javascript:var v = rowData.getColumnValue("ChkInDate");
v == ""?false:true;}]]></xp:this.readonly>
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial"
refreshId="panelAction">
<xp:this.action><![CDATA[#{javascript:var rv = rowData.getNoteID();
var vector:java.util.Vector = sessionScope.get("Selected");
var idx = vector.indexOf(rv);
if(idx == -1){
vector.addElement(rv);
}else{
vector.removeElementAt(idx)
}
}]]></xp:this.action>
</xp:eventHandler>
</xp:checkBox>
<xp:this.facets>
<xp:viewColumnHeader
xp:key="header"
id="viewColumnHeader1"
style="text-align:center"
value="Select">
</xp:viewColumnHeader>
</xp:this.facets>
</xp:viewColumn>

XPages ClientSide Validation in for Loop

I have 50 radiobuttonGroup in XPages. Field names are sequential like
Field_1, Field_2, Field_3 ... up to 50.
I would like to validate all this fields in one for loop. Please find what i have tried so far but i could not solve syntax. is it possible or I am running in circles.
<xp:button id="btnSave"
styleClass="btn btn-icon btn-primary">
<i class="fa fa-share" aria-hidden="true"></i>
<xp:eventHandler event="onclick"
submit="false">
<xp:this.script><![CDATA[for (var c=1; c<50; c++)
{
var fID = "\#{id:Question_"+ c +"}";
var elements = document.getElementsByName(fID);
alert(elements);
var syc = 0;
for(i=0; i<elements.length; i++)
{
if (elements[i].checked == false)
{
syc += 1;
}
}
if (elements.length==syc)
{
alert("Please select a value for question " + c);
return false;
}
}
]]></xp:this.script>
</xp:eventHandler>
</xp:button>
<xp:radioGroup id="Question_1" value="#{document1.Question_1}">
<xp:selectItem itemLabel="One" itemValue="One"></xp:selectItem>
<xp:selectItem itemLabel="Two" itemValue="Two"></xp:selectItem>
</xp:radioGroup>
Change the approach and give the input fields a common class. Then you get the collection of all fields to validate and loop through them.
You will need to check the name (radio buttons with the same name but different Id form a group) to get the grouping right.
I would loop through them and create an object with the names as property names that are set to true if the radio button is checked and created if not checked but missing.
At the end you have one js object with all Boolean properties. If one is false validation went wrong and you get the elements by name and add the class aria-invalid for the error

XPages SSJS Action Group

I have an action group on click of a button. First action, does some validation, second action throws up a confirm (Do you want to save?) And if yes, the third action goes off and does some other stuff.
The issue I have, is if validation fails on the first action, I don't want the other 2 actions to run, so I should get no confirmation etc.
If validation fails, I've tried doing a break, and a return false, but neither seem to work. I'm sure I'm missing something obvious, and suffering from Monday syndrome, but I can't seem to work it out!
Event handler code below, thanks:
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action>
<xp:actionGroup>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:for (var i = 1; i < viewScope.rows+1; i++) {
print("Starting Array.....");
var fieldName:string = "ObjectiveSelfAssessment" +i;
var fieldName2:string = "ObjectiveDetails" +i;
print ("Field Name: " + fieldName);
var fieldValue = document1.getItemValueString(fieldName);
var fieldValue2 = document1.getItemValueString(fieldName2);
print ("Field Value: " + fieldValue);
if (fieldValue =="" || fieldValue==null){
print("Assessment Empty");
if(!fieldValue2 =="" || !fieldValue2 == null){
print("Objective Empty");
//Do validation
var o = {};
o.title = "Validation Failed";
o.body = "You must enter self assessment details for each objective";
o.alertIcon = "fa-thumbs-down fa-lg";
o.autoClose = true;
o.alertType = "danger";
requestScope.put("alertServer",o);
//requestScope.put("validated",false);
return false;
break;
}
}
}
}]]></xp:this.script>
</xp:executeScript>
<xp:confirm>
<xp:this.message><![CDATA[#{javascript:"Are you sure you want to submit your self assessment?"}]]></xp:this.message>
</xp:confirm>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:document1.replaceItemValue("rows",viewScope.rows);
//document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {};
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o);
}]]></xp:this.script>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
Update 1: Conditional code on action group:
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:actionGroup>
<xp:this.condition><![CDATA[#{javascript:for (var i = 1; i < viewScope.rows+1; i++) {
print("Starting Array.....");
var fieldName:string = "ObjectiveSelfAssessment" +i;
var fieldName2:string = "ObjectiveDetails" +i;
print ("Field Name: " + fieldName);
var fieldValue = document1.getItemValueString(fieldName);
var fieldValue2 = document1.getItemValueString(fieldName2);
print ("Field Value: " + fieldValue);
if (fieldValue =="" || fieldValue==null){
print("Assessment Empty");
if(!fieldValue2 =="" || !fieldValue2 == null){
print("Objective Empty");
var o = {};
o.title = "Validation Failed";
o.body = "You must enter self assessment details for each objective";
o.alertIcon = "fa-thumbs-down fa-lg";
o.autoClose = true;
o.alertType = "danger";
requestScope.put("alertServer",o);
print("FALSE");
return false;
break;
}else{
print("TRUE");
return true;
}
}
}
}]]></xp:this.condition>
<xp:confirm>
<xp:this.message><![CDATA[#{javascript:"Test11111"}]]></xp:this.message>
</xp:confirm>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:document1.replaceItemValue("rows",viewScope.rows);
//document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {};
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o);}]]></xp:this.script>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action></xp:eventHandler>
Paul, I've accepted your answer reg doing more manual coding as that's the approach I've taken. I'm submitting an answer so I can show my solution using code formatting....
I now do my validation client side - The reason for getting the children etc, is I create/remove fields dynamically which are shown with repeat controls for my text box's, so I do not know the element id's....
var objcolparent = document.getElementById("ObjColOuter").children[0];
var sacolparent = document.getElementById("SAColOuter").children[0];
var rows = sacolparent.getElementsByTagName("TEXTAREA").length;
for (var i = 0; i < rows; i++) {
var saValue = sacolparent.getElementsByTagName("TEXTAREA")[i].value;
if(saValue ==""){
var objValue = objcolparent.getElementsByTagName("TEXTAREA")[i].value;
if(!objValue==""){
// Validation failed, do client side bootalert
var o = {};
o.title = "Validation Failed";
o.body = "You must enter self assessment details for each objective";
o.alertIcon = "fa-thumbs-down fa-lg";
o.alertType = "danger";
bootAlert.show('alertServer',JSON.stringify(o))
return false;
break;
}
}
}
if(confirm("Are you sure you want to submit your self assessment?")){
return true;
}else{
return false;
}
If validation is successful, it then continues to run my server side stuff:
document1.replaceItemValue("rows",viewScope.rows);
document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {}
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o)
Thanks to everyone who contributed!
Action groups are useful for basic, minimum-coding actions. But, as you're seeing, "easier" point-and-click, configuration-driven actions mean flexibility is harder. Manual coding may seem harder, but makes flexibility easier. If you put a confirm action on an XPage, preview it, and look at the source, it will tell you what function you need to use for manual coding. When I looked at the XSP class about seven years ago, I think the function used was XSP.confirm() equivalent to a basic JavaScript confirm() call. Using that may make your code more readable and make flexibility easier.
You can add a condition to your actionGroup. Here's a simple example:
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:actionGroup condition="#{javascript:myCondition()}">
xp:confirm>
<xp:this.message><![CDATA[#{javascript:"Are you sure you want to submit your self assessment?"}]]></xp:this.message>
</xp:confirm>
<xp:executeScript>
...
</xp:executeScript>
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
So in your case you just add your first action as a condition and have it return true or false depending on whether the logic in the action group must run or not.
There is a solution but it involves implementing a series of elements to properly support this scenario. But when it's up I believe it can be very helpful.
Your question is somewhat connected to a blog post of mine.
To help you in your specific case I will try to summarize here all the moving parts. Hope you like reading...
1st element: a server-side helper method
If you do validation outside of the validation phase there's no real way to let the client know something went wrong, or better there might be but it would be disconnected from an eventual flow you want to stay in (your case). What you want to do first, therefore, is to create a way to tell the client. First thing we build a static class with a simple helper method:
public enum Helper {
;
public static void setErrorHeader(HttpServletResponse response, PhaseId phaseId) {
response.setHeader("Application-Error", phaseId.toString());
}
}
We will later invoke this method to "flag" the validation as failed
2nd element: a client-side helper method
We need a JavaScript function that helps us tap into the response header we eventually add (add this function to your XPages through a JS library).
var helper = {
isBadRequest : function(xhr) {
return xhr.getResponseHeader("Application-Error") !== null;
}
}
3rd element: a beefed up event handler
To get things started in the proper way we must leverage the event handler parameters:
<xp:button id="myButton" value="Three-step action">
<xp:eventHandler
id="myEventHandlerId"
event="onclick"
submit="false"
action="#{myBean.makeItFlow}"
script="threeStep('validate', 'threeStepSave(arguments[1].xhr)')"/>
</xp:button>
It's very important for the eventHandler to have the attributes defined as written above:
id: used to grab the event handler later on
submit="false": doesn't submit the form, we will do it manually
action: contains the reference to the server side bean method that will be invoked. I used the name myBean.makeItFlow. I'm making the assumption you know how to use managed beans
script: where the "real" action takes place
At this point in the script attribute we call the XPages client-side javascript method that does the post - XSP.partialRefreshPost() - and we pass the various parameters manually. In this case I chose to avoid putting all the necessary script in-line with the event. At the bottom of the page you can add the following helper functions:
<xp:scriptBlock
value="
function threeStep(param, onCompleteFunc) {
var execId = '#{id:containerId}';
var refreshId = '#{id:containerId}';
var eventHandlerId = '#{id:myEventHandlerId}';
var opts = {
execId: execId,
params: { '$$xspsubmitid': eventHandlerId, action: param },
onError : 'console.log(arguments[0])'
};
if (onCompleteFunc) {
opts.onComplete = onCompleteFunc;
}
XSP.partialRefreshPost(refreshId, opts);
}
function threeStepSave(xhr) {
if (helper.isBadRequest(xhr)) {
return alert('Validation failed');
}
if (!confirm('Do you want to continue?')) return;
threeStep('save');
}" />
The method's first parameter is the id that will be refreshed. Then we have an object whose properties define:
param: a parameter we want to pass in order to tweak the server-side method behaviour
onCompleteFunc: the client side javascript that will be invoked at the end of the server side method evaluation (the js must be a string and will be evaluated)
var execId: the block (and therefore scope) that will be submitted with the POST
var refreshId: the block that will be refreshed
var eventHandlerId: the very important reference to the event handler ID
4th element: the bean action method
Now, by means of a Java bean we define all the action logic behind the button. I decided to go with 1 specific method that will react to the action parameter value passed by the event handler.
public void makeItFlow() {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
String action = (String) externalContext.getRequestParameterMap().get("action");
if ("validate".equals(action)) {
// Your logic
boolean failed = false;
// When validation fails add the error response header
if (failed) {
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
Helper.setErrorHeader(response, PhaseId.INVOKE_APPLICATION);
}
} else if ("save".equals(action)) {
// Let's save this thing
}
}
I am adding a brief video of how it should behave (in the example I added some additional code to make the various phases obvious)
https://youtu.be/JayzcGex-rQ

Xpages - Add a flag via through a button click on a repeat item

The cards above are populated via a repeat and the view is not categorised. What I want to add now is the ability to append a delete flag to any of the card in any order.
Below is the code for the delete button:
<xp:link>
<span class="glyphicon glyphicon-trash pull-right text-primary"></span>
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><xp:actionGroup><xp:executeScript>
<xp:this.script><![CDATA[#{javascript:
var name=getComponent("FullName").getValue();
var vec:NotesView = database.getView("SupportTeam");
var docEv:NotesDocument = vec.getFirstDocument();
if (name == docEv.getItemValueString("FullName")) {
docEv.replaceItemValue("SupportAction", "Delete");
docEv.save();
}
}]]></xp:this.script>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action></xp:eventHandler>
</xp:link>
The code above works, but the delete button need to be clicked twice for it work and it has to be in the order, that is if 'Test 6' is clicked it will not delete since 'Test 5' is in the way.
I tried using the getdocumentbykey() but the view will need to be categorised which will then show multiple entries. In this case, it will display lots of blank cards.
Your help will be appreciated.
We need to see your repeat code, however, aslong as you set the var property of your repeat to something, that then makes row data available to you, so you could use something like:
var id = rowData.getUniversalID();
var docEv:NotesDocument = database.getDocumentByUNID(id);
docEv.replaceItemValue("SupportAction", "Delete");
docEv.save();
//Or to do a hard delete
docEv.remove(true);
I decided to loop through the collection twice using the while loop to get the collection and then the for loop for the action and that seems to work, but I'm sure there should be a better way of doing this.
Below is the final code:
var name = getComponent("FullName").getValue();
var vec:NotesView = database.getView("SupportTeam");
var docEv:NotesDocument = vec.getFirstDocument();
var collection = [];
while (docEv != null){
try{
var member = docEv.getItemValueString("SupportFullName"), memberLength = collection.length;
collection.push(member);
for (var i = 0; i < memberLength; i++) {
if(memberLength != null && name == docEv.getItemValueString("SupportFullName")){
docEv.replaceItemValue("SupportAction", "Delete");
docEv.save();
}
}
}catch(e){
print("error: " + e.toString());
}
var tmp = vec.getNextDocument(docEv);
docEv.recycle();
docEv = tmp;
}
Thanks for all the response.

Xpages - Dominoview return all values if user in getRoles()

The code below filters data in a view.
The else statement is working fine, it basically returns values for the particular user, but the if statement is where the issue is; as I want admin users to be able to view all views/records from the database.
While the if statement works, it only returns views/records for the first user, but I want it to return views for all users if the user has the Role('[Admin]').
Your help will be appreciated!
<xp:dominoView var="users" viewName="userView" keysExactMatch="true">
<xp:this.keys><![CDATA[#{javascript:var fullName = context.getUser().getFullName();
var users:NotesView = database.getView("userView");
var entryCol:NotesViewEntryCollection = users.getAllEntries();
var doc:NotesDocument = users.getFirstDocument();
var columnValues = [];
while(doc != null){
columnValues.push( doc.getItemValueString("CreatedBy") );
doc = entryCol.getNextEntry();
}
if(context.getUser().getRoles().contains('[Admin]')){
print(columnValues);
return columnValues;
}else{
return fullName;
}}]]></xp:this.keys>
</xp:dominoView>
Return an empty value return "" for the [Admin] people. This will not set the keys property and deliver all entries in view.
Your code would look like this then:
<xp:dominoView var="users" viewName="userView" keysExactMatch="true">
<xp:this.keys><![CDATA[#{javascript:
if(context.getUser().getRoles().contains('[Admin]')){
return "";
} else {
return context.getUser().getFullName();
}
}]]></xp:this.keys>
</xp:dominoView>

Resources