How do I keep focus on PXNumberEdit field after postback? - acumatica

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)]

Related

How to get the value of a cms:FormField in a form layout script block?

I have a form that has a layout like so:
<cms:FormField runat="server" ID="fMemberType" Field="MemberType" />
<cms:FormField runat="server" ID="fEmployeeCount" Field="EmployeeCount" />
<asp:Literal runat="server" ID="test" Text="test" />
<script runat="server">
protected void Page_PreRender(object sender, EventArgs e)
{
test.Text = fMemberType.Value.ToString();
}
</script>
However this produces Object reference not set to an instance of an object. because it can't find fMemberType for some reason. Looking for the correct way of doing this.
It's worth noting that the form fields are dropdowns with depending flags set so changing them triggers a postback, or at least it would, but I set the webpart container to be an update panel so it's AJAXing which means the data isn't available in the page POST params. I could turn this off and grab the data from the POST data but wanted to know if there was a better way first.
So you're fully defining the fields and everything for your form? Why not use the DataForm control and dynamically create the form for you? You can then get the data like so: (formUserSettings is a cms:DataForm)
EditingFormControl ctrState = formUserSettings.BasicForm.FieldEditingControls["UserState"] as EditingFormControl;
Then do some checking and assign the value:
if (ctrState != null)
{
fState = ctrlState.Value;
}
Most likely the form value is not set until after the pre-render. Alen Genzic's recommendation will show that. May want to try OnInit.

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();
});

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

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.

How to bind an event to an element in a chrome extension popup window

I've searched quite a bit, and none of the answers I've found have worked 100%.
Basically, I want the popup to show a handful of buttons, then call the same function with different parameters. Concept:
<button onclick="foo(1,2);">Button 1</button>
<button onclick="foo(2,3);">Button 2</button>
I've tried a few simple and direct methods, none of which work. If I take the code out of the function, and have popup.js contain the code, it works (my function is fine).
I've tried:
$('#btn1').click(function(e) {
alert('btn1');
});
as well as
document.getElementById('btn1').addEventListener('click',doSomething(1,2));
Also, i've tried adding the button addEventListener inside of a document.addEventListener('DOMContentLoaded', function() {....});
The curious part is that if my popup.js contains button.addEventListener, the first one is fired upon clicking the browser action, and then nothing works. This happens regardless of whether the click event listner is inside of a DOMContentLoaded listener or not.
I have a feeling this is a CSP issue, but I can't seem to get it to work.
For those scanning for a question mark:
From within a popup.html/popup.js, how can I call a single function with different parameters based on an onclick event?
Issues
I'm not sure if it was just through your abbreviation when copying to SO but none of the code you have given would work "as pasted":
You have no id's on the buttons so binding to the id (I assume this is just because they weren't relevant in the first paste!)
The second paste uses the jQuery library and so you would need to make sure jQuery is included and allowed: https://stackoverflow.com/a/10928761/969807
In the third paste, the second parameter of addEventListener should be a function accepting the event as the first and only parameter (if wanted). (Read On)
Solution
The most common way to bind an event in the scenario you describe would be like so:
document.getElementById('btn1').addEventListener('click', function (){
foo(1, 2);
}, false);
However if there were a lot (or a variable amount) of buttons, I would probably do it like so:
popup.html:
<div id="buttons">
<button data-param1="1" data-param2="2">Button 1</button>
<button data-param1="2" data-param2="3">Button 2</button>
</div>
popup.js:
var els = document.querySelectorAll('#buttons button'),
i, total, param1, param2;
for (i = 0, total = els.length; i < total; i++) {
el = els[i];
param1 = parseInt(el.getAttribute('data-param1'));
param2 = parseInt(el.getAttribute('data-param2'));
el.addEventListener('click', function (){ foo(param1, param2) }, false);
}

Custom validator fires but does not prevent postback

I've seen a lot of questions about this already, but I'm stumped! Please help!
I have a customvalidator. It's firing but it's not preventing postback. Please help me in doing so! I can see that console.log registers before the post. But, it posts back anyway. How do I prevent the postback?
I've tried adding a control to validate, and validate empty text equal to true. I also tried adding e.preventdefault, which did not work :(
How can I prevent the postback?
<script type="text/javascript">
//<![CDATA[
function validateWhyUnlikely(source, args) {
console.log(1);
args.isValid = false;
}
//]]>
<asp:TextBox ID="txtWhyUnlikely" runat="server" Rows="4" cols="20"
CssClass="surveyTextArea" />
<asp:CustomValidator runat="server" ID="cfvWhyUnlikley" ErrorMessage="Please provide a reason since you rated an item as unlikely to provide."
CssClass="surveyError surveySmallIndent" Display="Dynamic"
ClientValidationFunction="validateWhyUnlikely" />
<asp:Button ID="btnSubmit" runat="server" Text="Submit" CssClass="smallSpecial" OnClick="btnSubmit_Click" />
jQuery(document).ready(function () {
jQuery('#<%= btnSubmit.ClientID %>').click(function (e) {
if (Page.IsValid == false) {
console.log(false);
e.preventDefault();
return false;
}
});
});
Everything looks ok althought I am not sure why you are attaching the Click function to your submit button. I would remove that and test it as it maybe be overriding the default behavior.
Also I think you need to capitalize the IsValid property:
args.IsValid = false;
I too faced this issue, I was trying to add a custom validator to a dropdownlist which had a selectedIndexChange event attached to it. After i gave incorrect value for dropdown, i was able to se ethe error message i gave in Custom Validator but immediately after it Postback was happening.
However on adding this property CausesValidation="true" to the dropdownlist control resolved my issue.
Postback wasn't happening on incorrect value after adding this property to my dropdown.
If it helps other people, I had a Validation group that I forgot to add the button to.
Make sure to add the button, the textbox and the validator to the same validation group for the postback to be prevented.
I experienced this problem as well.
What I did was, in the C# procedure that was called by the button, at the top I added
if (IsValid == false)
return;
I could not stop it performing the postback so this seemed to me like the only solution.
You are misssing ControlToValidate="txtWhyUnlikely"
Posting this as it might help someone that is getting the same weird behavior.
Initially I had the same issue as this post title. I checked all the suggestions here but my code seemed to be fine.
To fix this I replaced my cause validation control <asp:Button.. with a <button.. . Not sure why this is happening but happy it's working now.
hth
<button tags are missing the correct javascript code to validate.
<asp:Button does have the correct javascript rendered.
I've added this to any button tags:
btn.Attributes("onclick") = StringFmt("if(!Page_ClientValidate(''))return false;")
and that solved the post-back issue. No post-back occurs if the client-side detects an issue.
I solved this problem by creating a variable:
Boolean fieldIsValid = true;
and at the custom validating expression I would change the value if arguments weren't true:
if(args.IsValid == false)
{
fieldIsValid = false;
}
else
{
fieldIsValid = true;
}
Then, I also put that in the submit click method:
protected void submit_Click(object sender, EventArgs e)
{
if (fieldIsValid)
{
//submit my things
}
}

Resources