I have an extension library outline control. I have two basicLeafNodes in the outline. The onclick event on each of these nodes is supposed to run some code. The problem is both of those onClick events are being executed when the page is open but does not seem to execute when you actually click on the node.
Any idea what could be wrong?
<xe:outline id="outline1">
<xe:this.treeNodes>
<xe:basicLeafNode label="Set Value 1">
<xe:this.onClick><![CDATA[#{javascript:getComponent("inputText1").value = "123";}]]></xe:this.onClick>
</xe:basicLeafNode>
<xe:basicLeafNode label="Set Value2">
<xe:this.onClick><![CDATA[#{javascript:getComponent("inputText2").value = "456";}]]></xe:this.onClick>
</xe:basicLeafNode>
</xe:this.treeNodes>
</xe:outline>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:br></xp:br>Value 1:
<xp:inputText id="inputText1"></xp:inputText>
<xp:br></xp:br>Value 2:
<xp:inputText id="inputText2"></xp:inputText>
The onClick event of the basicLeafNode is for client-side JS only. You need to use the submitValue property of each basicLeafNode to and then add SSJS to the onItemClick event of the outline control. You can then use context.getSubmittedValue() to check what node was clicked and then act accordingly:
<xe:outline id="outline1">
<xe:this.treeNodes>
<xe:basicLeafNode label="Set Value 1" submitValue="1"></xe:basicLeafNode>
<xe:basicLeafNode label="Set Value2" submitValue="2"></xe:basicLeafNode>
</xe:this.treeNodes>
<xe:this.onItemClick><![CDATA[#{javascript:
if (context.getSubmittedValue() == "1") {
getComponent("inputText1").value = "123"
} else if (context.getSubmittedValue() == "1") {
getComponent("inputText2").value = "456"
}
}]]></xe:this.onItemClick>
</xe:outline>
From the XPages Extension Library book (page 240):
The onClick property allows the developer to execute a piece of
Client-Side JavaScript code, and the submit- Value property allows the
developer to specify a value that is passed back to the server. This
value is accessed from the onItemClick event of the control that
contains the tree nodes.
Related
Is it possible to define some SSJS via property definition on a custom control?
For example, to test, I have a property definition on a custom control called onClickCode. Within said custom control, I have a radio button, and in the onClick/onChange event I try to write compositeData.onClickCode. This seems to execute when the page loads, rather than on the the onClick event.
Code that passes in property definitions is:
<xc:ccQuestionInterimRadiosYesNo required="true"
dataSource="#{document1}" fieldName="Q1"
helpText="Please select an answer"
placeholder="Enter any further details here..."
questionHeader="primary" questionTextNJD="QuestionTextNJD">
<xc:this.radioOptions><![CDATA[#{javascript:return ['Yes', 'No'];}]]></xc:this.radioOptions>
<xc:this.questionText><![CDATA[${javascript:"Ready for section 2?"}]]></xc:this.questionText>
<xc:this.onClickCode><![CDATA[#{javascript:'print("Hello")'}]]></xc:this.onClickCode>
</xc:ccQuestionInterimRadiosYesNo>
Code on the radio button within said custom control is:
<!-- RADIO BUTTON GROUP -->
<xp:radioGroup
loaded="${!empty compositeData.placeholder}"
value="#{compositeData.dataSource[compositeData.fieldName]}"
required="${compositeData.required}" layout="lineDirection">
<xp:this.validators>
<xp:validateRequired
message="#{javascript:compositeData.helpText}">
</xp:validateRequired>
</xp:this.validators>
<xp:this.id><![CDATA[${javascript:"radioGroup"+compositeData.fieldName}]]></xp:this.id>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:compositeData.radioOptions}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onchange"
submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:compositeData.onClickCode}]]></xp:this.action>
</xp:eventHandler></xp:radioGroup>
UPDATE:
Custom control on a page passes:
onClickCode="#{javascript:test1}"
Code on the onChange event of a radio button within the custom control:
if (compositeData.onClickCode) {
compositeData.onClickCode.call();
}
Script containing the code is part of my theme:
<script src="/StdServerScripts.jss" clientSide="false"></script>
Code within said script is:
function test1() {
print("Hello");
}
UPDATE
This is now what I have in my xpage:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:panel id="ContainerPanel">
<xp:inputText id="typeAhead">
<xp:eventHandler event="onchange" submit="true"
refreshMode="norefresh">
<xp:this.action><![CDATA[#{javascript://code here to trap the click in the list of values
//displayed by the type ahead. Code redirects to another page
//the goal here would be to avoid the full/partial update and only use the CSJS
//code to trigger the button's partial/full refresh so we can avoid having the onchange code
//trigger a refresh when the user decides to click the button instead of clicking on a suggestion
//that is offered by the typeahead
//I thought of using the temporary field and use it to store the value selected in the type ahead list,
//so the code knows wheter we are running the CSJS from the type ahead list or from the button (the onchange
//of the type ahead will fill this field so we know wether to trigger the CSJS partial refresh }]]></xp:this.action>
<xp:this.script><![CDATA[var selectedValue = document.getElementById("#{id:typeAhead}").value;
var targetField = document.getElementById("#{id:temporaryField}");
alert("typeAhead CSJS onchange: " + selectedValue);
targetField.value = selectedValue;
//init the partial refresh here!!!]]></xp:this.script>
</xp:eventHandler>
<xp:typeAhead mode="partial" minChars="2">
<xp:this.valueList><![CDATA[aaaa
bbbb
cccc
dddd]]></xp:this.valueList>
</xp:typeAhead></xp:inputText>
<xp:button value="GO" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript://same call to the code called in the onchange event of the typeahead field
mySSJSFunction();}]]></xp:this.action>
</xp:eventHandler></xp:button>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:inputText id="temporaryField"></xp:inputText>
</xp:panel>
</xp:view>
Because the problem comes from the fact that the typeahead's onchange event is triggered even though the user just wants to type in something and go click on the button (therefore triggering the partial update of the event, so the code can call a SSJS function), I thought I'd get rid of the type ahead field's partial update and try to trigger it manually from CSJS.
I am trying to use a temporary field to store the value selected in the type ahead so I can figure out if I need to run the SSJS function right away (the user selected a value in the list) or just don't do nothing as the user just wants to go and click on the button.
Unfortunately, the code is at the office and I'm home right now, but the comments in the above code should give an idea of what I'm trying to achieve.
Not sure either if I should use XSP._partialRefresh(), XSP.PartialRefreshGet() and which control ID should I use (if it has any importance).
Hope it all makes sense... Just trying to give the user the possibility to select a value in the type ahead list, or type something in the field and click the search button, both cases calling the same SSJS function (using either the value selected in the type ahead list or what has been typed in the field).
FIRST POST
I have a page with a field and a button, just like the Google page. I have type ahead set up so it returns a list of documents title that correspond to the "search" term entered. Because I want the application to load a page when the user clicks on an item from the type ahead list, I added some code to the onChange event of the type ahead field. That works great, but...
Let's say a user wants to go ahead and click on the button anyways, because the list displayed by the type ahead field only show the top 10 search results, and the document he is looking for is not in that list, so he wants to trigger a "full search" by clicking on the search button. What happens is that the onchange code of the field is triggered before the onclick code of the button, and due to a fullrefresh on the type ahead field, this triggers a page refreshand the user needs to click a second time on the button to actually open the search page.
The question is quit esimple: how can I get the same behaviour as the Google page, opening up the page if I click on one of the suggestions, but also opening up a search page/search results page when clicking on the button?
Here is the code so far:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xc="http://www.ibm.com/xsp/custom"
xmlns:xp_1="http://www.ibm.com/xsp/coreex" dojoParseOnLoad="true">
<xp:this.resources>
<xp:script src="/xpSortedSearches.jss" clientSide="false"></xp:script>
<xp:script src="/xpadSearch.jss" clientSide="false"></xp:script>
<xp:dojoModulePath loaded="true"
url="/xsp/.ibmxspres/dojoroot/dojo/dojo.js">
</xp:dojoModulePath>
</xp:this.resources>
<xp:panel id="PanelSearch" style="text-align:right;">
<xp:inputText id="inputSearch"
style="width:225px;height:20px;font-size:10pt;font-family:sans-serif;padding-left:5.0px;padding-top:5.0px;padding-bottom:2.0px; border:1px solid gray;"
tabindex="0" disableClientSideValidation="false"
disableModifiedFlag="true">
<xp:this.attrs>
<xp:attr name="placeholder">
<xp:this.value><![CDATA[#{javascript:if(sessionScope.lang=="fr") {
"Entrez votre recherche ici ...";
} else {
"Enter your search query here...";
}}]]></xp:this.value>
</xp:attr>
</xp:this.attrs>
<xp:typeAhead mode="full" minChars="3" valueMarkup="true"
var="searchQuery" ignoreCase="true">
<xp:this.valueList><![CDATA[#{javascript://TODO Memory management???
getTypeAheadValuesFromSearchBar(getComponent("inputSearch").getValue());}]]></xp:this.valueList>
</xp:typeAhead>
<xp:eventHandler event="onchange" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:var key:String = getComponent("inputSearch").getValue();
if(!!key) {
getDocFromSubject(key);
}
}]]></xp:this.action>
<xp:this.script><![CDATA[showStandBy();
return true;]]></xp:this.script>
</xp:eventHandler>
<xp:eventHandler event="onkeypress" submit="true"
refreshMode="complete">
<xp:this.script><![CDATA[//if we have a enter, launch the search
if (thisEvent.keyCode==13) {
var qryField = document.getElementById("#{id:inputSearch}");
if(doSearch(qryField)) {
showStandBy();
return true;
} else {
return false;
}
}else{
return false;
}]]></xp:this.script>
<xp:this.action><![CDATA[#{javascript:launchSearch();}]]></xp:this.action>
</xp:eventHandler>
</xp:inputText>
<xp:button id="button1" icon="/search.png"
styleClass="searchButtonSmall">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.onStart>
StandbyDialog_Started();
</xp:this.onStart>
<xp:this.action><![CDATA[#{javascript:launchSearch()}]]>
</xp:this.action>
<xp:this.script><![CDATA[ var qryField = document.getElementById("#{id:inputSearch}");
if(doSearch(qryField)) {
showStandBy();
return true;
} else {
return false;
}]]></xp:this.script>
</xp:eventHandler>
</xp:button>
<xp:br />
</xp:panel>
<!--
This event handler makes sure the search box has the focus once the
page is loaded
<xp:eventHandler event="onClientLoad" submit="false">
<xp:this.onStart>
StandbyDialog_Started();
</xp:this.onStart>
<xp:this.script><![CDATA[var edit = dojo.byId("#{id:inputSearch}");
if (edit) {
edit.focus();
}]]>
</xp:this.script>
</xp:eventHandler>
-->
<xp:eventHandler event="onClientLoad" submit="false">
<xp:this.script><![CDATA[var edit = dojo.byId("#{id:inputSearch}");
if (edit) {
edit.focus();
}]]></xp:this.script>
</xp:eventHandler></xp:view>
My database has a nice navigator that is dynamic (although it is not a dynamic navigator - I don't understand what that is).
I have some sessionScope variables that I load. The variables are driven by the user entering data in an administration page.
The user enters Apps (Application Bar), Titles (Title Bar) - tied to one or more Apps, and finally Pages, tied to one or more titles.
I use repeats in the nav control for the Pages, then a container node below that for the description, and then another repeat for the categories of the docs in the page, and then a basic node for the views. It works well.
What I want is for some sessionScope.page variables to have a special attribute to indicate that they will not be views of documents, but clicking on that page in the navigator will present one document to the user and they can edit or save it.
I cannot figure out how to do it.
So in the screen shot below, if the user clicks "Best Practices" it should just open a doc in the middle pane, NOT an entry with "All" below it.
I think this might be easier in a repeat control, but an open to any suggestions.
Thanks!
<?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:this.resources>
<xp:styleSheet href="/menus.css"></xp:styleSheet>
<xp:styleSheet href="/buttons.css"></xp:styleSheet>
</xp:this.resources>
<xp:this.beforePageLoad><![CDATA[#{javascript:sessionScope.filterContent = "All";
setTags(sessionScope.selectedPage);}]]></xp:this.beforePageLoad>
<xp:panel>
<xe:navigator id="navigator1">
<xe:this.treeNodes>
<xe:repeatTreeNode var="entry" indexVar="index">
<xe:this.value><![CDATA[#{javascript:var tmpKey:String = sessionScope.selectedTitle;
sessionScope.render[tmpKey]}]]></xe:this.value>
<xe:this.children>
<xe:basicContainerNode
submitValue="#{javascript:entry}" style="font-size:10pt">
<xe:this.label><![CDATA[#{javascript:sessionScope.descriptions[entry]}]]></xe:this.label>
<xe:this.children>
<xe:repeatTreeNode var="entry2"
indexVar="index2">
<xe:this.value><![CDATA[#{javascript:var tmpArr = [];
tmpArr.unshift("All");
tmpArr2 = #Unique(#Trim(#DbColumn(#DbName(),sessionScope.selectedPage,1)));
if (tmpArr2.length == 0)
{var tmpArr3 = tmpArr}
else
{var tmpArr3 = tmpArr.concat(tmpArr2)}
tmpArr3}]]></xe:this.value>
<xe:this.children>
<xe:basicLeafNode
label="#{javascript:entry2}"
submitValue="#{javascript:entry2}" style="font-size:8pt">
<xe:this.rendered><![CDATA[#{javascript:if (sessionScope.selectedPage == entry)
{return true}}]]></xe:this.rendered>
<xe:this.selected><![CDATA[#{javascript:if (sessionScope.filterContent == entry2)
{return true}
else
{return false}}]]></xe:this.selected>
</xe:basicLeafNode>
</xe:this.children>
</xe:repeatTreeNode>
</xe:this.children>
</xe:basicContainerNode>
</xe:this.children>
</xe:repeatTreeNode>
</xe:this.treeNodes>
<xp:eventHandler event="onItemClick" submit="true"
refreshMode="complete">
<xe:this.action><![CDATA[#{javascript:var tmpStr:String = context.getSubmittedValue();
var act:String;
if (tmpStr.substring(0,4) == "page")
{act = "page"}
if (tmpStr.substring(0,4) != "page")
{act = "cat"}
switch (act)
{
case "page":
// Do something
sessionScope.selectedPage = tmpStr;
sessionScope.filterType = "Categories";
sessionScope.filterContent = "All";
setTags(sessionScope.selectedPage);
viewScope.tag = "";
break;
case "cat":
// Do something else
sessionScope.filterType = "Categories";
sessionScope.filterContent = tmpStr;
viewScope.tag = "";
break;
default:
// Default case
break;
}
</xp:view>
Create a session scope variable "type". It defines for every entry a type "doc" or "view".
Depending on entry's type render a basicLeafNode for just the menu entry (in your example "Best Practices") or a basicContainerNode/basicLeafNode like before. Use property rendered:
<xp:panel>
<xe:navigator id="navigator1">
<xe:this.treeNodes>
<xe:repeatTreeNode var="entry" indexVar="index"
value="#{javascript:sessionScope.render[sessionScope.selectedTitle]}">
<xe:this.children>
<xe:basicLeafNode
rendered="#{javascript:sessionScope.type[entry] === 'doc'}"
label="#{javascript:sessionScope.descriptions[entry]}"
submitValue="#{javascript:entry}">
</xe:basicLeafNode>
<xe:basicContainerNode
rendered="#{javascript:sessionScope.type[entry] === 'view'}"
label="#{javascript:sessionScope.descriptions[entry]}"
submitValue="#{javascript:entry}"
style="font-size:10pt">
<xe:this.children>
<xe:repeatTreeNode
var="entry2"
There is a required field:
<xp:this.validators>
<xp:validateRequired
message="Required field. Please add some text.">
</xp:validateRequired>
</xp:this.validators>
Also, the value from this field is copied ( using the onChange event ) to other fields:
<xp:eventHandler event="onchange" submit="true"refreshMode="norefresh">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]>
</xp:this.action>
</xp:eventHandler>
An inconvenient issue appears when I just click the field to fill it: the validation message appears. Is because the field initially is empty and the code I added is into the onChange event?
I'd like to use this field as required before users can save the doc.
I tried set the values by CSJS, but without a result...
var string = XSP.getElementById("#{id:inputText1}").value
XSP.getElementById("#{id:txt_LocalitateS}").value = string
XSP.getElementById("#{id:txt_LocalitateP}").value = string
Also, let say I enter a value for inputText1 and later on I enter a new value... How can I update automatically the other 2 fields with the new value?
I tried something like this:
<xp:inputText id="inputText1" value="#{Cdoc.txt_LocalitateCompanie}"
style="height:20.0px;width:122.0px;font-weight:bold;font-size:10pt;font-family:verdana"
required="true">
<xp:this.validators>
<xp:validateRequired message="Completarea localitatii este obligatorie.">
</xp:validateRequired>
</xp:this.validators>
<xp:typeAhead mode="full" minChars="1" ignoreCase="true"
id="typeAhead1">
<xp:this.valueList><![CDATA[#{javascript:#DbLookup(#DbName(),"vwLocalitati",Cdoc.txt_LocalitateCompanie,1,"[PARTIALMATCH]");}]]></xp:this.valueList>
</xp:typeAhead>
<xp:eventHandler event="onchange" submit="true"
refreshMode="norefresh">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]></xp:this.action>
<xp:this.script><![CDATA[XSP.partialRefreshGet("#{id:txt_LocalitateS}", {
onComplete: function() {
XSP.partialRefreshGet("#{id:txt_LocalitateP}", {
onComplete: function(){ }
});
}
});]]></xp:this.script>
</xp:eventHandler>
</xp:inputText>
Thanks in advance
Two things here. First, you should disable validators for onChange event, therefore it won't display the validation error.
Second, when you use a CSJS script together with a SSJS, it will fire the CSJS one and if it returns true, proceed with the SSJS. So if you want your CSJS code run after SSJS, you can place it into oncomplete.
If I understood your question correctly, the following code would solve it.
<xp:inputText
id="inputText1"
value="#{Cdoc.txt_LocalitateCompanie}"
style="height:20.0px;width:122.0px;font-weight:bold;font-size:10pt;font-family:verdana"
required="true">
<xp:this.validators>
<xp:validateRequired
message="Completarea localitatii este obligatorie.">
</xp:validateRequired>
</xp:this.validators>
<xp:typeAhead
mode="full"
minChars="1"
ignoreCase="true"
id="typeAhead1">
<xp:this.valueList><![CDATA[#{javascript:#DbLookup(#DbName(),"vwLocalitati",Cdoc.txt_LocalitateCompanie,1,"[PARTIALMATCH]");}]]></xp:this.valueList>
</xp:typeAhead>
<xp:eventHandler
event="onchange"
submit="true"
refreshMode="norefresh"
disableValidators="true">
<xp:this.action><![CDATA[#{javascript:Cdoc.setValue("dlg_Localitate",Cdoc.getValue("txt_LocalitateCompanie"));
Cdoc.setValue("dlg_Localitate_1",Cdoc.getValue("txt_LocalitateCompanie"))}]]></xp:this.action>
<xp:this.onComplete><![CDATA[if(dojo.byId("#{id:txt_LocalitateP}")) {
XSP.partialRefreshGet("#{id:txt_LocalitateP}", {
onComplete: function() {
XSP.partialRefreshGet("#{id:txt_LocalitateS}", {
onComplete: function(){ }
});
}
});
}]]></xp:this.onComplete>
</xp:eventHandler>
</xp:inputText>
UPDATE: In your case, the field you want to refresh is on the second tab with partialRefresh="true". It means that at the time of partialRefreshGet, the target fields might not exist in the DOM. I have added a check now.
this is taken from my comments and put into an answer:
onChange events are generally frowned upon due to performance and user experience. If, however, the field is a listed control ie combobox it is not so dramatic. The following options/ideas are available
Take out the onChange() to test whether that makes a difference. If so, move your code.
Use an update button to change all the fields en masse also preventing information that is already inputted from being deleted unwanted-ly
create your own validation method and show/hide a label manually (hack-y)
Research how to manually put text into an errors control
If the field is in a dialog box, move the onChange() to the open/close methods of the dialog
FURTHER EDIT
An idea that I might suggest is using the xspDoc.getDocument(true) method to push all changes from the xpage to the background document. Something tells me that this might make a difference with the server reading the changes to the document and realizing that it is not empty.
ADDITIONAL IDEAS
I did not mention this because it is a bit more advanced, but should also get the job done assuming the refreshes are done. Even that is not that big of a deal. You could read all of your data from the document into a java bean. This bean is then the "data source" for your page and you bind all of your controls to the properties of this bean. You will then use EL to bind your controls to the bean. In the setters for those variables that trigger changes in other fields, change those values. So,
public PageBean(){
//read connection information out of the URL and get the correct information out of the document
//set all variables
firstName=doc.getItemValueString("firstName");
}
private String firstName;
public String getFirstName(){
return firstName;
}
public void setFirstName(String firstName){
this.firstName = firstName;
setLastName("Schmidt");
}
....
Once you register your bean with faces-config.xml, you can then use EL to access the data
#{PageBean.firstName}
#{PageBean.lastName}
Then you can get your document again in save and reset the values, save and release.
I have a xpage that use view.postscript like this:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:dojoModule name="dojox.widget.Toaster"></xp:dojoModule>
<xp:styleSheet href="/.ibmxspres/dojoroot/dojox/widget/Toaster/Toaster.css"></xp:styleSheet>
</xp:this.resources>
<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[var Toaster = function(id, msg, type, duration, pos) {
type = (type == null) ? "message" : type;
duration = (duration == null) ? "5000" : duration;
pos = (pos == null) ? "br-up" : pos;
var obj = dijit.byId(id);
obj.positionDirection = pos;
obj.setContent(msg, type, duration);
obj.show();
}]]></xp:this.value>
</xp:scriptBlock>
<xp:div id="toaster" themeId="toaster" dojoType="dojox.widget.Toaster"></xp:div>
<xp:button value="Toaster" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:view.postScript("Toaster('"+getComponent("toaster").getClientId(facesContext)+"', 'toaster message!')");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:view>
But when I click button,the toaster can't show.Why?
And I need a way to show the toaster called by ssjs function.
Thanks a lot.
As #Frantisek Kossuth said, you're re-submitting the page. When your refreshMode is complete it will re-render the page based on the server values. If you want to make client side changes and reflect them in your UI, you can't do a refresh of complete. Doing a partial refresh of the page on an element that won't change (like button1) should update your UI without rebuilding the page.
Similarly, why do you need this to be done through SSJS? Why not just use Client actions in your button activation? If this isn't part of a larger function call that you're not including, simply change the event code from Server to Client and call the Toaster function that way. This would also allow you to not have to submit anything, leaving submit="false" in your eventHandler to avoid unnecessary functionality.