ComboBox onBlur to refresh the values of other ComboBoxes using a function - xpages

I have an xpage with 5 fields on it. Each field has code in the onBlur event to refresh the values of the ComboBoxes below it. I now have to add a bunch more fields to this application and I don't want to write the refresh code for each field. Rather, I would like to create a function that takes a parameter of which field I'm in and do the refresh with a loop.
I can't seem to get this to work. Below is the code I'm using in the onBlur event. I don't know the semantics of putting this code in a script library that can access each combobox and call the refresh code in a loop.
Any ideas?
<xp:comboBox id="vendorAppAdvSkills1">
<xp:selectItem itemLabel="-Select a Category-"
itemValue="-Select a Category-"></xp:selectItem>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:getComponent( "vendorAppSkills1" ).getValue();}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onblur" submit="false">
<xp:this.script><![CDATA[
XSP.partialRefreshPost("#{id:panelVendorAppSkills2}",
{
onComplete: function()
{
XSP.partialRefreshPost("#{id:panelVendorAppSkills3}",
{
onComplete: function()
{
XSP.partialRefreshPost("#{id:panelVendorAppSkills4}",
{
onComplete: function()
{
XSP.partialRefreshPost("#{id:panelVendorAppSkills5}",
{
onComplete: function()
{
XSP.partialRefreshPost("#{id:panelNextFinish}",
{
} )
}
} )
}
} )
}
} )
}
} );]]></xp:this.script>
</xp:eventHandler>
</xp:comboBox>

Do you have validation on your XPage? If so, validation will be preventing any of the partial refreshes running.
If possible just set the refresh ID of the eventHandler to an area that encompasses all combo boxes. That would just call one partial refresh from the browser to the server.
With your current code you're calling 5 partial refreshes, each time posting the whole content of the browser across to the server, each time updating the whole page, but just pushing back an individual component. Performance is not going to be good, so the single refresh area is better practice (as well as being easier to code!).
As best practice, unless you're preventing validation, also ensure the refresh area includes a Display Errors control. Otherwise your users (including you when testing) will not know if validation has failed.

Related

Xpages attach event to partial refresh of pager in Data View

In a previous post I asked how to add a bootstrap class to a Data View. The answer was to add the class to the "table.dataview" in a script block. After the table is created the class is applied and all is well.
But when I use a pager the formatting disappears. I am using a partial refresh on the pager to only refresh the data table but doing so means that the bootstrap class does not get applied to the table.
I believe I need to add an event handler that will attach to the refresh action of the dataView to add the class. However I cannot get the event handler to work.
My code for the event handler is below.
<xp:eventHandler refreshMode="partial" submit="true"
id="applyCSS" refreshId="dataView1" event="parialRefresh"
value="what" loaded="false">
<xp:this.binding><![CDATA[#{javascript:"pager1"}]]></xp:this.binding>
<xp:this.action><![CDATA[#{javascript:("table.dataview").addClass("table-striped table-hover table-bordered table-condensed")}]]></xp:this.action>
</xp:eventHandler>
Oliver, the rendered=false was simply a typo - I was testing something and needed to temporarily suppress that.
Oliver and Paul,
Last night I was able to get the partial refresh to work.
I ran across this post by Mark Roden which explained how to do it. There were two different ways to accomplish this, one less and one more efficient. The code I used is below.
<xp:scriptBlock id="scriptBlock3">
<xp:this.value><![CDATA[$('.dataView1PanelWrapper').on("DOMNodeInserted", function(){
$("table.dataview").addClass("table-striped table-hover table-bordered table-condensed")
})]]></xp:this.value>
</xp:scriptBlock>
However, and isn't there almost always a however in Xpages, I have some sortable columns in the view and clicking on the sort brings up the same problem! I lose the class assignment!
So now I would have to intercept that event too, right?
Concerned where this will end. Don't like the idea of DOM Manipulation, and only want to do it if I have too.
I started by using a simple view. It worked great, but for some reason the spacing was messed up in the pagers. I found that by moving the pagers out of the view itself, I was able to get the alignment issue fixed. I think it would be better just to use a view, as I can assign the class directly and won't have to do all this manipulation. It is however very good to know how to do this for the future.
Is that what you would suggest?
==================================================
I have tried Paul Withers suggestion using an output script. This works on the initial page load, but not on any other changes to the data view - when the pager fires, or sorting or any of that. I am close, but no cigar yet. Any suggestions?
<xp:scriptBlock id="scriptBlock5" loaded="false">
<xp:this.value><![CDATA[dojo.require("dojo.behavior");
Behavior = {
".dataview": {
found: function(node) {
dojo.addClass(node,".table-striped table-hover table-bordered table-condensed");
//node.addClass("table-striped table-hover table-bordered table-condensed");
}
}
}
dojo.ready(function() {
dojo.behavior.add(Behavior);
dojo.behavior.apply();
});
//Make sure that future pagers are also straightened out
dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {
dojo.behavior.apply();
});]]></xp:this.value>
</xp:scriptBlock>
Move your existing xp:scriptBlock with the working code inside a facet of the xe:dataView. Then the styling will get applied on initial load and on all partial refreshes.
You should call your CSJS stuff to add the class in the onComplete property of the event handler - hard to find, just highlight the event handler object in source code or outline and then open "All properties" to find the "onComplete" property. This event allows CSJS to be called.
BTW: why is the loaded property = false? The event will never we rendered.
dojo.behavior, dojo.ready and dojo.subscribe should allow you to manage this. dojo.behavior allows you to define a particular behaviour for a particular collection of elements which will be retrieved via a Dojo query. dojo.ready will (I believe) run the code when the page initially loads and dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {...} will run the code aftedr a partial refresh.
Here's sample code I used for converting a DataView's category column images to Font Awesome icons, so the Behavior = {...} bit will need amending.
Behavior = {
// Convert xp:pagers to Bootstrap
".catColumn a img": {
found: function(img_node) {
var imgSrc = dojo.getNodeProp(img_node, "alt").toLowerCase();
if (imgSrc.indexOf("collapse") >= 0) {
dojo.place('<i class="fa fa-minus-square"></i> ', img_node, 'replace');
} else {
dojo.place('<i class="fa fa-plus-square"></i> ', img_node, 'replace');
}
}
}
}
dojo.ready(function() {
dojo.behavior.add(Behavior);
dojo.behavior.apply();
});
//Make sure that future pagers are also straightened out
dojo.subscribe("partialrefresh-complete", null, function(method, form, refreshId) {
dojo.behavior.apply();
});

How do I keep focus on PXNumberEdit field after postback?

I have a PXNumberEdit field which, on enter, adds a product to a grid on a customized SO301000 page. The insert works and the field
is emptied after the product is added. However, I would like to return the focus to that field. There doesn't seem to be
a SetFocus method for the field.
I have tried using the SO301000.cs code behind to set the focus, by adding a function for onValueChanged
to save the object as session variable and on pageload to set the focus on the saved object. This causes
the page to never finish loading.
I have also tried to use jquery in various ways but that hasn't worked either. Is there a way to do this?
The Hack
There is no SetFocus method in the framework like there is for SetEnabled/SetDisplayName/SetVisibility because most events are raised on focus changes and the framework ensures that the focus is not lost on every record updates. To set it up manually, you will then need to wait that the callback is completed before setting the focus.
To do so you will need to add a Javascript delegate to the list of event handlers to be called once the callback is over. The following code will set the focus on Customer Reference Nbr. every time Customer ID is changed (in SO301000):
<script type="text/javascript">
function FormView_Load() {
px_callback.addHandler(setFocusOnCustRef);
return;
}
var setFocus = false;
function CustomerID_ValueChanged() {
setFocus = true;
return;
}
function setFocusOnCustRef(context, error) {
if (setFocus === true)
{
setFocus = false;
var refNbr = px_alls["edCustomerRefNbr"];
refNbr.focus();
}
return;
}
</script>
<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-index: 100" Width="100%" DataMember="Document" Caption="Order Summary"
NoteIndicator="True" FilesIndicator="True" LinkIndicator="True" EmailingGraph="PX.Objects.CR.CREmailActivityMaint,PX.Objects"
ActivityIndicator="True" ActivityField="NoteActivity" DefaultControlID="edOrderType" NotifyIndicator="True"
TabIndex="14900" ClientEvents-Initialize="FormView_Load">
...
<px:PXSegmentMask CommitChanges="True" ID="edCustomerID" runat="server" DataField="CustomerID" AllowAddNew="True"
AllowEdit="True" DataSourceID="ds" ClientEvents-ValueChanged="CustomerID_ValueChanged"/>
...
</px:PXFormView>
Note that I added ClientEvents-Initialize="FormView_Load" on the PXFormView and ClientEvents-ValueChanged="CustomerID_ValueChanged" on the CustomerID's PXSegmentedMask.
As you can see this is a hack... When the setFocusOnCustRef is raised we have just refreshed the record (RowSelected) and we don't know what was changed prior to that (what field has been changed? was the change canceled?). The context that is passed to the delegate is only related to re-updating the records. To get a better understanding of what events are raised and in which order, please refer to the Update Scenario Event Model:
Thoughts and Tips
I don't know much of your implementation but I would like to point out that your needs look very similar to the function Add Stock Item that opens a SmartPanel with the buttons Add/Add & Close/Cancel. If the callback is raised from a button you will have meaningful information in your context and won't need to add a Javascript event on ValueChanged.
When you Save/Cancel. The focus will return to your first form element in the tab order (if successful).
You can set the Tab Order directly in the PXUIFieldAttribute :
PXUIField(DisplayName = "Asset ID", Visibility = PXUIVisibility.SelectorVisible, TabOrder=1)]

Using JQuery to mask input fields. When I refresh a panel that contains those fields the masking stops working

I have several phone number fields that use jquery masking to format the input. See the code below. The fields work great until a combo box change above refreshes a panel that contains those fields. Once the refresh happens my masking stops working.
Any idea why and how to prevent this from happening?
<xp:scriptBlock id="scriptBlock7">
<xp:this.value><![CDATA[
jQuery(function($){
x$("#{id:dayPhone1}").mask("(999) 999-9999? x 9999999", {placeholder : " " } );
x$("#{id:eveningPhone1}").mask("(999) 999-9999? x 9999999", {placeholder : " " } );
x$("#{id:cellular1}").mask("(999) 999-9999? x 9999999", {placeholder : " " } );
});
]]></xp:this.value>
</xp:scriptBlock>
The jQuery is only run one time. It manipulates the DOM to give the mask effect. Once you run a partial refresh the original DOM is returned to the user and the mask is no longer in effect.
When the partial refresh happens the jQuery code does not know to apply itself back to the mask again. You have a number of choices, but the best is probably:
In the onComplete event of the partial refresh you can call the mask code again to reapply the "Mask". What I don't know is if the mask code will reset the fields or honor the values therein. I that is the case then take a look at the plugin code and see what options you have.
<xp:button value="Label" id="button1" styleClass="startLoginProcess" style="display:none">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="somethingHere"
onComplete="applyMaskCodeAgainHere">
</xp:eventHandler>
</xp:button>
To improve the code above because it looks like you are applying the same mask I suggest using a class selector and simplifying your code to look more like this:
<xp:scriptBlock id="scriptBlock7">
<xp:this.value><![CDATA[
jQuery(function($){
$('.phoneMask').mask("(999) 999-9999? x 9999999", {placeholder : " " } );
});
]]></xp:this.value>
</xp:scriptBlock>
put a styleClass="phoneMask" on your field :)
Is your scriptBlock also on the panel that is being refreshed? It needs to be in order for the the mask to be reapplied to the fields after the refresh.
You can remove the mask and reset it...
$(`#${id:dayPhone1}`).val(newPhoneValue).unmask().mask('(00) 00000-0000', {clearIfNotMatch: true});
This worked for me and should solve your problem!
TLDR: .unmask() before .mask()

xpages validation on field having onChange script

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.

Disable commandButton in JSF

This seems like it should be pretty straightforward but I'm not feeling it.
I have a JSF CommandButton that executes a long running serverside task (10-15 seconds). I've seen forms where the button context changes after it's been clicked (The label on the button changes and the button becomes disabled until the processing is complete).
I'm using ICEFaces and have the disabled property set to a boolean on the underlying page code.
The action listener bound to the button changes that boolean to disable it but alas, no changes on the JSP.
Anyone?
What you can do is to change the status of the button using Javascript:
<h:commandButton ... onclick="this.disabled=true"/>
Edit regarding the comment:
If the previous code does not submit the form, then you have to disable the button a little time after the click, not "during" the click itself. You can do that using the following code:
<h:commandButton ... onclick="setTimeout('this.disabled=true', 100);"/>
I'm not sure if the fact to use the this keyword directly in the setTimeout method will work correctly. If not, you can use another way to do that:
<h:commandButton ... onclick="disableButton(this.id);"/>
with the following Javascript function:
function disableButton(buttonId) {
setTimeout("subDisableButton(" + buttonId + ")", 100);
}
function subDisableButton(buttonId) {
var obj = document.getElementById(buttonId);
if (obj) {
obj.disabled = true;
}
}
(I'm sure this code can be enhanced, thus)
You should use an ice:commandButton instead of h:commandButton, since it has the partialSubmit property, which will perform the action as an AJAX call. This should refresh your button's state, so if the property on the server has been set to false, your button should be disabled.
do a javascript submit(); first and then disable the button
Similar to the solution from romaintaz
For a Firefox specific solution, the following works (it does not work in IE):
<h:commandButton ... onclick="disableButton(this.id);" />
Using Javascript function:
function disableButton(buttonId) {
var obj = document.getElementById(buttonId);
if (obj) {
setTimeout(function(thisObj) { thisObj.disabled=true; }, 50, obj);
}
}
do it after icefaces has updated the DOM. you can use ice.onAfterUpdate(callback):
Here with jQuery
ice.onAfterUpdate(function(){
updateButtons();
});
function updateButtons(){
if(!isButtonEnabled()){
jQuery(".myButton").attr('disabled', true);
jQuery(".myButton").removeClass("iceCmdBtn").addClass("iceCmdBtn-dis");
}else{
jQuery(".myButton").removeAttr('disabled');
jQuery(".myButton").removeClass("iceCmdBtn-dis").addClass("iceCmdBtn");
}
}

Resources