I have a webMethods CAF task that has a big form with a save button and a submit button. Many elements on the form have validation. The user needs to be able to hit Save and have the form submitted to the back end model so it can be saved as task data, without firing validation. Hitting Submit should fire validation.
How can I configure the page to do this. It's such a normal requirement, and I'm stuck!
It's not much fun.
Give your Save button a nice ID. Say, saveButton
Create a getter in your Java code that returns a boolean. Inside it, return true if the button's ID is one of the submitted fields, otherwise false:
private boolean validationRequired() {
return mapValueEndsWith((Map<String, String>)getRequestParam(),
new String[] {
"saveButton", // Your save button
"anotherButton", // Perhaps another button also shouldn't validate
"myForm:aThirdButton" // perhaps you want to be specific to a form
});
}
In every field that should be required, except on Save, bind the Validation->required attribute to your validationRequired getter.
That's it! Very tedious with a lot of fields on the screen, but it works.
P.s. what's mapValueEndswith? Just a utility; removed whitespace for compactness' sake:
private boolean mapValueEndsWith(Map<String, String> haystack, String[] needles) {
for(String needle : needles) if(mapValueEndsWith(haystack, needle)) return true;
return false;
}
private boolean mapValueEndsWith(Map<String, String> haystack, String needle) {
for(String value : haystack.values()) if(value.endsWith(needle)) return true;
return false;
}
As I see the approach provided, works only if the form contains only string type fields. If there are any other data types like integer, float, data-time are mapped to UI fields and conversion is used then this might fail if wrong data is entered in those fields.
Related
(You may be able to figure out an answer by ONLY reading the last question by itself, but I included everything for reference if necessary)
The onChange of one field is causing displayErrors to show validation results prior to when I need it to do that.
On an xpage, I have these two comboboxes:
1.) locationType
2.) locationEtc
Choices for locationEtc are dependent on what was selected in locationType field.
In the locationEtc field, the choices are using one computed value with this code:
try
{
var locType = getComponent("locationType").getValue();
var key = '';
switch(locType) {
case 'Commuity Service Center':
key = 'loc_cso';
break;
case 'RYDC':
key = 'loc_rydc';
break;
case 'YDC':
key = 'loc_ydc';
break;
case 'HQ':
key = 'loc_hq';
break;
default:
key = 'facilities';
}
var luChoices = #DbLookup('','keywords', key, 'choices');
luChoices.unshift("Select one|''");
return luChoices;
}
catch(e)
{
print("Error:::::"+e);
}
The choices were NOT changing for locationEtc whenever I pick a locationType, but then they do when I add code to clear the location field whenever locationType changes, using a simple action for onChange event of locationType field:
This is great, but when it all refreshes, my displayErrors control appears (yellow background area) showing required field validation results, and a lot of the fields are not filled in yet by design.
How can I make the displayErrors to show ONLY when tying to submit?
Thanks everyone who can help.
Matt
Set disableValidators="true" on the simple action that you created
I have overwritten the data view for a custom graph in an extension, which returns the correct data without issue, both by re-declaring the view, and using the delegate object techniques. The issue is that when I do, the AllowSelect/AllowDelete modifications on the view in the primary graph stop working, once I comment out the overwrite, the logic works as normal.
Not sure what I'm missing, but any thoughts would be appreciated
Edit: To clarify, on the main graph, without the extension, the data retrieval and Allow... work without issue
public class FTTicketEntry : PXGraph<FTTicketEntry, UsrFTHeader>
{
public PXSelect<UsrFTHeader> FTHeader;
public PXSelect<UsrFTGridLabor, Where<UsrFTGridLabor.ticketNbr, Equal<Current<UsrFTHeader.ticketNbr>>>> FTGridLabor;
And with the extension, the data is returned correctly from the modified view, but the Allow... do not work from the main graph, only when entered on the extension
public class FTTicketEntryExtension : PXGraphExtension<FTTicketEntry>
{
public PXSelect<UsrFTGridLabor, Where<UsrFTGridLabor.ticketNbr, Equal<Current<UsrFTHeader.ticketNbr>>, And<UsrFTGridLabor.projectID, Equal<Current<UsrFTHeader.projectID>>, And<UsrFTGridLabor.taskID, Equal<Current<UsrFTHeader.taskID>>>>>> FTGridLabor;
I have also tried the other process on the extension with the same results, the data is filtered correctly, but the Allow... commands fail.
public PXSelect<UsrFTGridLabor, Where<UsrFTGridLabor.ticketNbr, Equal<Current<UsrFTHeader.ticketNbr>>>> FTGridLabor;
public virtual IEnumerable fTGridLabor()
{
foreach (PXResult<UsrFTGridLabor> record in Base.FTGridLabor.Select())
{
UsrFTGridLabor p = (UsrFTGridLabor)record;
if (p.ProjectID == Base.FTHeader.Current.ProjectID && p.TaskID == Base.FTHeader.Current.TaskID)
{
yield return record;
}
}
}
My main concern with not wanting to use PXSelectReadOnly, is that there is a status field on the header which drives when certain combinations of the conditions are required and are called on the rowselected events, sometimes all and sometimes none, and the main issue is that I obviously don't want to have to replicate all of the UI logic into the extension, when overwriting the view was the main intent of the extension for the screen.
Appreciate the assistance, and hopefully you see something I'm overlooking or have missed
Thanks
Every BLC instance stores all actual data views and actions within 2 collections: Views and Actions. Whenever, you customize a data view or an action with a BLC extension, the original data view / action gets replaced in the appropriate collection by your custom object declared within the extension class. After the original data view or action was removed from the appropriate collection, it's quite obvious that any change made to the original object will not make any effect, since the original object is not used by the BLC anymore.
The easiest way to access actual object from either of these 2 collections would be as follows: Views["FTGridLabor"].Allow... = value;
Alternatively, you might operate with AllowInsert, AllowUpdate and AllowDelete properties on the cache level: FTGridLabor.Cache.Allow... = value;
By changing AllowXXX properties on the cache level, you completely eliminate the need for setting AllowXXX on the data view, since PXCache.AllowXXX properties have higher priority when compared to identical properties on the data view level:
public class PXView
{
...
protected bool _AllowUpdate = true;
public bool AllowUpdate
{
get
{
if (_AllowUpdate && !IsReadOnly)
{
return Cache.AllowUpdate;
}
return false;
}
set
{
_AllowUpdate = value;
}
}
...
}
With all that said, to resolve your issue with UI Logic not applying to modified view, please consider one of the following options:
Set AllowXXX property values in both the original BLC and its extensions via the object obtained from the Views collection:
Views["FTGridLabor"].Allow... = value;
operate with AllowXXX property values on the cache level: FTGridLabor.Cache.Allow... = value;
First check if your DataView should/should not be a variant of PXSelectReadonly.
Without more information my advice would be to set the Allow properties in Initialize method of your extension:
public override void Initialize()
{
// This is similar to PXSelectReadonly
DataView.AllowDelete = false;
DataView.AllowInsert = false;
DataView.AllowUpdate = false;
}
I searched the whole web and I couldn't found any good topic which expose the proper way to do it.
I have a very simple web which I developed using Kentico 9 CMS. This web only contains two subpages and a header to navigate between those.
The "Home" subpage contains a custom form which remains connected to a SQL table which is populated every time you press submit with certain data.
On the other hand, the other page, shows the stored data by using a custom web part which connects to the DB by using BizFormItemProvider and this object is used as a layer to binding the data in a control.
Now is my point. If you see, there is a button to "Edit" a certain row and my intention is to Redirect to "Home" (which contains the form) and sending via QueryString the ID of the row attempted to edit.
I was unable to understand how can you re-fill the form with its data using an ID.
Maybe because I never worked with a CMS before, I'm looking the development such as pure ASP.NET and it could not be the correct one.
Custom
Given that your solution uses a custom form for entering the data, as well as a custom web part for listing the stored data, you will need to use a custom solution to handle the editing of data, as well.
In the custom webpart on the Home page, in a load event, you can retrieve the form data and set the values on the form controls.
protected void Page_Load(object sender, EventArgs e)
{
// Ensure that the form is not being posted back,
// to prevent entered data from being overwritten
if(!IsPostBack)
{
// Get the form item ID from the query string
var personId = QueryHelper.GetInteger("personId", 0);
if(personId > 0)
{
// Get the biz form item, and set form control values
var bizFormItem = BizFormItemProvider.GetItem(personId, "customFormClassName");
txtFirstName.Text = bizFormItem.GetStringValue("FirstName", string.Empty);
}
}
}
Similarly, when Submit is clicked, you can update the existing form item with the new data
protected void btnSubmit_OnClick(object sender, EventArgs e)
{
// Get the form item ID from the query string
var personId = QueryHelper.GetInteger("personId", 0);
if(personId > 0)
{
// Retrieve the existing biz form item,
// and update it from the form control values
var bizFormItem = BizFormItemProvider.GetItem(personId, "customFormClassName");
bizFormItem.SetValue("FirstName", txtFirstName.Text);
bizFormItem.Update();
}
else
{
// Your code for inserting a new form item...
}
}
The Kentico way
You should really consider using the Kentico form engine for accomplishing this task. Instead of using a custom form for entering the data, use the built-in On-line form webpart.
The benefits are numerous, such as:
Ability to set the form layout through the CMS, and use alternative layouts
Automatic confirmation email to the submitter of the form, as well as notification emails to the administrators
To accomplish your task, you can customise the On-line form webpart to support loading of existing data.
In the bizform.ascx.cs file, add code to the SetupControl method:
protected void SetupControl()
{
if (StopProcessing)
{
// Existing code...
}
else
{
// Existing code...
// Get the form item ID from the query string
var personId = QueryHelper.GetInteger("personId", 0);
if(personId > 0)
{
// Get the biz form item, and set form control values
var bizFormItem = BizFormItemProvider.GetItem(personId, "customFormClassName");
if(bizFormItem != null)
{
// Set the item ID
viewBiz.ItemID = bizFormItem.ItemID;
}
}
}
}
This will automagically switch the form to Edit mode, instead of Insert mode, as soon as you set the ItemID property. Clicking the Submit button will save changes on the existing form item.
You will not need to worry about validation in your code, and inserting data will still work.
Is this a contact form that you are using Kenticos built in form application for, or is it a custom form? If it is a custom form you can create a transformation with a link that will contain the ID. If it is a biz form, you can still create a transformation in Page Types (create a new page type and select "The page type is only a container without custom fields"), then write a custom query to get the biz form data, and use a repeater to display the data with that transformation.
Suppose I want to pass one object (reference) through several pages. I can navigate and pass parameters via Frame.Navigate(typeof(FirstPage), object). But how to pass the reference back on back press properly?
protected override void OnNavigatedTo(NavigationEventArgs e) {
if (e.Parameter is SomeClass) {
this.someObject = (SomeClass)e.Parameter;
}
else {
this.someObject = new SomeClass();
}
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += OnHardwareButtonsBackPressed;
base.OnNavigatedTo(e);
}
private void OnHardwareButtonsBackPressed(object sender, BackRequestedEventArgs e) {
// This is the missing line!
Frame.Navigate(typeof(FirstPage), this.someObject);
}
But when I press back button it goes back to the FirstPage OnNavigatedTo with no parameter, and then back to the SecondPage OnHardwareButtonsBackPressed and then back to FirstPage OnNavigatedTo with filled parameter.
Could you please advice me some better approach?
In your back handler, don't navigate forwards again, just call GoBack -- and it's typically easier if you handle that at a global level rather than at a page level.
You can store your application state (the things you want to persist across page navigations) in global / static objects, or you could directly modify the object that was passed from the initial navigation (if the calling page still has a reference, it will be able to see the changes).
I would consider doing a search for "MVVM Windows Apps" and looking at some of the results to learn about a common way of building XAML apps.
Im using subsonic 2.2
I tried asking this question another way but didnt get the answer i was looking for.
Basically i ususally include validation at page level or in my code behind for my user controls or aspx pages. However i haev seen some small bits of info advising this can be done within partial classes generated from subsonic.
So my question is, where do i put these, are there particular events i add my validation / business logic into such as inserting, or updating. - If so, and validation isnt met, how do i stop the insert or update. And if anyone has a code example of how this looks it would be great to start me off.
Any info greatly appreciated.
First you should create a partial class for you DAL object you want to use.
In my project I have a folder Generated where the generated classes live in and I have another folder Extended.
Let's say you have a Subsonic generated class Product. Create a new file Product.cs in your Extended (or whatever) folder an create a partial class Product and ensure that the namespace matches the subsonic generated classes namespace.
namespace Your.Namespace.DAL
{
public partial class Product
{
}
}
Now you have the ability to extend the product class. The interesting part ist that subsonic offers some methods to override.
namespace Your.Namespace.DAL
{
public partial class Product
{
public override bool Validate()
{
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.Errors.Add("ProductName cannot be empty");
return Errors.Count == 0;
}
// another way
protected override void BeforeValidate()
{
if (string.IsNullOrEmpty(this.ProductName))
throw new Exception("ProductName cannot be empty");
}
protected override void BeforeInsert()
{
this.ProductUUID = Guid.NewGuid().ToString();
}
protected override void BeforeUpdate()
{
this.Total = this.Net + this.Tax;
}
protected override void AfterCommit()
{
DB.Update<ProductSales>()
.Set(ProductSales.ProductName).EqualTo(this.ProductName)
.Where(ProductSales.ProductId).IsEqualTo(this.ProductId)
.Execute();
}
}
}
In response to Dan's question:
First, have a look here: http://github.com/subsonic/SubSonic-2.0/blob/master/SubSonic/ActiveRecord/ActiveRecord.cs
In this file lives the whole logic I showed in my other post.
Validate: Is called during Save(), if Validate() returns false an exception is thrown.
Get's only called if the Property ValidateWhenSaving (which is a constant so you have to recompile SubSonic to change it) is true (default)
BeforeValidate: Is called during Save() when ValidateWhenSaving is true. Does nothing by default
BeforeInsert: Is called during Save() if the record is new. Does nothing by default.
BeforeUpdate: Is called during Save() if the record is new. Does nothing by default.
AfterCommit: Is called after sucessfully inserting/updating a record. Does nothing by default.
In my Validate() example, I first let the default ValidatColumnSettings() method run, which will add errors like "Maximum String lenght exceeded for column ProductName" if product name is longer than the value defined in the database. Then I add another errorstring if ProductName is empty and return false if the overall error count is bigger than zero.
This will throw an exception during Save() so you can't store the record in the DB.
I would suggest you call Validate() yourself and if it returns false you display the elements of this.Errors at the bottom of the page (the easy way) or (more elegant) you create a Dictionary<string, string> where the key is the columnname and the value is the reason.
private Dictionary<string, string> CustomErrors = new Dictionary<string, string>
protected override bool Validate()
{
this.CustomErrors.Clear();
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.CustomErrors.Add(this.Columns.ProductName, "cannot be empty");
if (this.UnitPrice < 0)
this.CustomErrors.Add(this.Columns.UnitPrice, "has to be 0 or bigger");
return this.CustomErrors.Count == 0 && Errors.Count == 0;
}
Then if Validate() returns false you can add the reason directly besides/below the right field in your webpage.
If Validate() returns true you can safely call Save() but keep in mind that Save() could throw other errors during persistance like "Dublicate Key ...";
Thanks for the response, but can you confirm this for me as im alittle confused, if your validating the column (ProductName) value within validate() or the beforevalidate() is string empty or NULL, doesnt this mean that the insert / update has already been actioned, as otherwise it wouldnt know that youve tried to insert or update a null value from the UI / aspx fields within the page to the column??
Also, within asp.net insert or updating events we use e.cancel = true to stop the insert update, if beforevalidate failes does it automatically stop the action to insert or update?
If this is the case, isnt it eaiser to add page level validation to stop the insert or update being fired in the first place.
I guess im alittle confused at the lifecyle for these methods and when they come into play