Validate value in field - xpages

Is there anyway to check when you type in to a field if there already are any document saved with that value in that field. Ex, if you type projectno i want to check if any other document already have that projectno. Any suggestion how i will validate that
Regards

You need a view in the database that is sorted in the first column by the field that you are using. I will assume it is a hidden view, called "(lookupUnique)". Build it and test it to make sure it is showing the field that you want in the first column, and that the values are sorted.
Now you need a way to do a lookup into this view. Ideally, you're wanting the lookup to fail -- because there is no document with the same value, in which case you allow the save to continue. But there's one other case where you might want to allow the save to continue. That's the case where the lookup succeeds because the lookup found the document that you are working on right now, which was previously saved and therefore is found in the view, and a user is now editing it again.
The #DbLookup function with the [RETURNDOCUMENTUNIQUEID] and [FAILSILENT] arguments is the IBM-recommended solution for this. I.e.,
foundId := #DbLookup("Notes":"NoCache";"":"";"(lookupUniqe)";theUniqueFieldNameGoesHereWithoutQuotes;1;[RETURNDOCUMENTUNIQUEID]);
If this formula returns "", then no match was found, therefore your code should return #Success to let the save continue. If it returns anything else, then compare the result with #DocumentUniqueId. If they match, then your code should return #Success to let the save continue. If they do not match, then you have found another document with the same value in the field, so your code should return #Failure with an appropriate error message.
Now here's the caveat: there have been known problems with [RETURNDOCUMENTUNIQUEID] in some versions of Domino, including a bug that caused Domino 6 servers to crash if an agent called ComputeWithForm on a document based on a form that used this feature. There's also a bug that causes it to return only the unid of the first match out of many matches, and so if you have duplicates this strategy in your code will allow users to re-save old documents that are already non-unique instead of forcing them to change them to make them unique, and that may or may not be what you want.
If either of those known issues might create a problem for you, then you would be better off not using [RETURNDOCUMENTUNIQUEID], and instead just do what Notes and Domino programmers did before IBM added the [RETURNDOCUMENTUNIQUEID] option in the first place: add another column to your (lookupUnique) view, and set the column value to #Text(#DocumentUniqueId). Change the 1 in the above #DbLookup formula to the number of the column that you added, and write your validation code to anticipate the possibility that you might get back an empty string, a single value, or a list of values.

If a type 45678 i return a value because there already are a document with that value. I don’t understan how i will validate it.
var dbname = session.getServerName() + "!!" + "proj\\webno.nsf";
getFieldValue = getComponent("oNo").getValue();
tmp = #DbLookup(dbname, "(webNo)", getFieldValue, ”obNo”);
if (tmp == getFieldValue)
{
Here i will do a validate. If value i return are the same as in the getFieldValue
and tmp or just getFieldValue is empty.
}
else
{
Here is it OK
}

Taking your code and modifying it. Assuming we're in the database we're creating the document in, just use #DbName() instead of trying to build the name from the session and some hard-coding. When using validation, the value of the control should be accessible simply with value. Then, just get all the values in the column and see if your value is in there.
I think the following should work.
<xp:inputText id="projectNumber" value="#{doc.ProjectNumber}">
<xp:this.validators>
<xp:validateExpression message="Value already in use">
<xp:this.expression><!CDATA[#{javascript:var usedValues = #DbColumn(#DbName(), "(webNo)", 1);
if ( #IsMember ( value, usedValues ) ) { return false };
return true;
</xp:this.expression>
</xp:validateExpression>
</xp:this.validators>
</xp:inputText>
Why don't you just generate a value for them? The simplest would be to use #Unique, but there are plenty of other ways besides having them have to create one.....

Related

Exposing the current combo selection index for the CGridCellCombo class

For several years I have been using the CGridCellCombo class. It is designed to be used with the CGridCtrl.
Several years ago I did make a request in the comments section for an enhancement but I got no replies.
The basic concept of the CGridCellCombo is that it works with the text value of the cell. Thus, when you present the drop list it will have that value selected. Under normal circumstances this is fine.
But I have places where I am using the combo as a droplist. In some situations it is perfectly fine to continue to use the text value as the go-between.
But is some situations it would have been ideal to know the actual selected index of the combo. When I have a droplist and it is translated into 30 languages, and I need to know the index, I have no choice but to load the possible options for that translation and then examine the cell value and based on the value found in the array I know the index.
It works, but is not very elegant. I did spend a bit of time trying to keep track of the selected index by adding a variable to CInPlaceList and setting it but. I then added a wrapper method to the CGridCellCombo to return that value. But it didn't work.
I wondered if anyone here has a good understanding of the CGridCellCombo class and might be able to advise me in exposing the CComboCell::GetCurSel value.
I know that the CGridCtrl is very old but I am not away of another flexible grid control that is designed for MFC.
The value that is transfered back to the CGridCtrl is choosen in CInPlaceList::EndEdit. The internal message GVN_ENDLABELEDIT is used, and this message always use a text to set it into the grid.
The value is taken here via GetWindowText from the control. Feel free to overwrite this behaviour.
The handler CGridCtrl::OnEndInPlaceEdit again calls OnEndEditCell. All take a string send from GVN_ENDLABELEDIT.
When you want to make a difference between the internal value and the selected value you have to manage this via rewriting the Drawing and selecting. The value in the grid is the GetCurSel value and you have to show something different... There isn't much handling about this in the current code to change.
More information
The key is CInPlaceList::EndEdit(). There is a call to GetWindowText (CInPlaceList is derived from CComboBox), just get the index here. Also in CGridCellCombo::EndEdit you have access to the m_pEditWnd, that is the CInPlaceList object and derived from CComboBox, so you have access here too.
I have found this to be the simplest solution:
int CGridCellCombo::GetSelectedIndex()
{
int iSelectedIndex = CB_ERR;
CString strText = GetText();
for (int iOption = 0; iOption < m_Strings.GetSize(); iOption++)
{
if (strText.CollateNoCase(m_Strings[iOption]) == 0) // Match
{
iSelectedIndex = iOption;
break;
}
}
return iSelectedIndex;
}

How to make #DbLookup in XPages verify the existence of a value in another database?

I have an XPage that is doing an #DbLookup with a user's input and trying to find that value in a view in a different database yet on the same server.
I have already verified that the view is in fact sorted by the first column and therefore #DbLookup friendly. The following code below appears in the server-side Javascript OnClick event handler for a button on my XPage.
My problem is that the an error occurs when trying to assign the value of lRep to the 'firstNameLabel'. lRep is returning a null value from the dbLookup even though the a record under the 'FirstName' field exists with the key 'P301993'. This dbLookup should be finding a returning a single 'FirstName' result. However, it is not.
var resultLabel = getComponent("firstNameLabel");
var dbName = new Array(#DbName()[0],"UKCSandbox.nsf");
var lRep = #DbLookup(dbName,"customerLookup","P301993","FirstName");
resultLabel.setValue(lRep.toString());
Unless your formatting was lost in copy and paste, your code has flaws. This is not Java, this is JavaScript. Line endings matter and functions don't act on the object, but return a value. Also #DbLookup returns a string when you have exactly one match, so checking for string doesn't help you.
Your code should look like that:
var ukcNumber = Registration.getItemValueString('UKCNumber').toUpperCase();
var resultLabel = getComponent("ukcNumberLabel");
var dbName = #DbName();
dbName[1] = "UKC\\UKCSandbox.nsf";
var lRep = #DbLookup(dbName,"customerLookup",ukcNumber,1);
resultLabel.setValue((lRep) ? "Success" : "Failed");
Does that work for you?
Update: 2 things to check:
does the lookup work in the same database using #DbName?
XPages have the same security constraints as Java agents. Do you have enough rights in the server document to do a 'get value from other database'? Default is No!
Have you tried making the dblookup work outside of xpages, i.e. with ScanEZ or in the Notes client?
Check your ukcNumber variable so it contains a value.
Edit
Check so the user has rights to do the lookup in the other database.
Also try a similar code on an old Notes Form and see if you get the same result.
Why can't you use keyword '[FAILSILENT]' in your #DBLookup call. It'll return "", if no entry matches with your key.
If you still have issues, use SSJS/java code to see where it's breaking up.

Insert/update Doctrine object from Excel

On the project which I am currently working, I have to read an Excel file (with over a 1000 rows), extract all them and insert/update to a database table.
in terms of performance, is better to add all the records to a Doctrine_Collection and insert/update them after using the fromArray() method, right? One other possible approach is to create a new object for each row (a Excel row will be a object) and them save it but I think its worst in terms of performance.
Every time the Excel is uploaded, it is necessary to compare its rows to the existing objects on the database. If the row does not exist as object, should be inserted, otherwise updated. My first approach was turn both object and rows into arrays (or Doctrine_Collections); then compare both arrays before implementing the needed operations.
Can anyone suggest me any other possible approach?
We did a bit of this in a project recently, with CSV data. it was fairly painless. There's a symfony plugin tmCsvPlugin, but we extended this quite a bit since so the version in the plugin repo is pretty out of date. Must add that to the #TODO list :)
Question 1:
I don't explicitly know about performance, but I would guess that adding the records to a Doctrine_Collection and then calling Doctrine_Collection::save() would be the neatest approach. I'm sure it would be handy if an exception was thrown somewhere and you had to roll back on your last save..
Question 2:
If you could use a row field as a unique indentifier, (let's assume a username), then you could search for an existing record. If you find a record, and assuming that your imported row is an array, use Doctrine_Record::synchronizeWithArray() to update this record; then add it to a Doctrine_Collection. When complete, just call Doctrine_Collection::save()
A fairly rough 'n' ready implementation:
// set up a new collection
$collection = new Doctrine_Collection('User');
// assuming $row is an associative
// array representing one imported row.
foreach ($importedRows as $row) {
// try to find an existing record
// based on a unique identifier.
$user = Doctrine_Core::getTable('User')
->findOneByUsername($row['username']);
// create a new user record if
// no existing record is found.
if (!$user instanceof User) {
$user = new User();
}
// sync record with current data.
$user->synchronizeWithArray($row);
// add to collection.
$collection->add($user);
}
// done. save collection.
$collection->save();
Pretty rough but something like this worked well for me. This is assuming that you can use your imported row data in some way to serve as a unique identifier.
NOTE: be wary of synchronizeWithArray() if you're using sf1.2/doctrine 1.0 - if I remember correctly it was not implemented correctly. it works fine in doctrine 1.2 though.
I have never worked on Doctrine_Collections, but I can answer in terms of database queries and code logic in a broader sense. I would apply the following logic:-
Fetch all the rows of the excel sheet from database in a single query and store them in an array $uploadedSheet.
Create a single array of all the rows of the uploaded excel sheet, call it $storedSheet. I guess the structures of the Doctrine_Collections $uploadedSheet and $storedSheet will be similar (both two-dimensional - rows, cells can be identified and compared).
3.Run foreach loops on the $uploadedSheet as follows and only identify the rows which need to be inserted and which to be updated (do actual queries later)-
$rowsToBeUpdated =array();
$rowsToBeInserted=array();
foreach($uploadedSheet as $row=>$eachRow)
{
if(is_array($storedSheet[$row]))
{
foreach($eachRow as $column=>$value)
{
if($value != $storedSheet[$row][$column])
{//This is a representation of comparison
$rowsToBeUpdated[$row]=true;
break; //No need to check this row anymore - one difference detected.
}
}
}
else
{
$rowsToBeInserted[$row] = true;
}
}
4. This way you have two arrays. Now perform 2 database queries -
bulk insert all those rows of $uploadedSheet whose numbers are stored in $rowsToBeInserted array.
bulk update all the rows of $uploadedSheet whose numbers are stored in $rowsToBeUpdated array.
These bulk queries are the key to faster performance.
Let me know if this helped, or you wanted to know something else.

Unexpected ArgumentExecption when accesing a Field Value in a SPListItem

I have the following helper method that returns the value from a field.
public static string GetValueFrom(SPListItem item, string fieldName)
{
string value = string.Empty;
if (item.Fields.ContainsField(fieldName))
{
SPField field = item.Fields.GetField(fieldName);
if (item[field.InternalName] != null)
{
value = item[field.InternalName].ToString();
}
}
return value;
}
However for one Field (normal Choice Field) I am getting a ArgumentExecption on this line
if (item[field.InternalName] != null)
I am using
SPListItem item = list.GetItemById(itemId);
To get the item.
I cant find why I am getting the exception when I am checking to see if the field exists?
Any ideas as to why I am getting this Exception for only one field.
Update.
When debugging
The call to GetField() returns the correct field object.
Field.InternalName contains the correct Internal name of the field
If I try and access the value using item["internal name of the field"] it still throws and exception for only this one field.
Sometimes strange things happens and we do not have logical answer to those questions. Try by deleting the list and then creating the list again from scratch. DO NOT try to save it as template and DO NOT try to create the list from that template.
One possible reason of such type of ugly messages is that the security/permissions are not allowing to manipulate that field/column.
Another possible reason of such type of unwanted/unexpected messages is that when the field was created for the first time, its data type was different and later on it was changed to choice. Technically there should be no problem in doing so but sometimes we face odd behavior.
Have you tried debugging? Questions you should answer (because we can't):
Is field a valid value, or null, after the call to GetField()?
If field is not null, what does field.InternalName actually return?
If field.InternalName returns a valid value, can you access it by hard-coding that value in the indexer? i.e. item["fieldInternalName"]
Finding that information may help you solve the problem yourself, but if it doesn't add it to your post so the community has a better chance of helping you.
I do experienced this many a times. The reason for this is if you are logged-in as a non Admin Account(System Account) the default List View Lookup Threshold for the User is 8 for the lookup columns. i.e for the default view the user can access upto the 8 lookup fields only. If you change the List Throttling to >8 it will be resolved. But increasing this will degrade the performance.
Go to Central Admin >> Manage Web Applications >> Select the Web Application >> General Settings Dropdown >> Resource Throttling >> Change the "List View Lookup Threshold" to more than 8
Thanks,
-Codename "Santosh"

sharepoint - add custom column to list via object model

I'm having trouble figuring out how to add a custom column type to a list with the object model.
SPFieldCollection.Add() has a parameter SPFieldType, but that must be one of the enumerated values in the Microsoft.SharePoint.SPFieldType enumeration, thus it cannot be used to create columns of a custom type.
I next attempted using SPFieldCollection.CreateNewField() but when I call SPField.Update() on the returned value I get an exception: "ArgumentException was unhandled. Value does not fall within the expected range.".
I see a reference to SPFieldCollection.AddFieldAsXml() here: How do I add custom column to existing WSS list template but there's hardly any info and I'm not sure that's the right track to take.
UPDATE: I found a post on AddFieldAsXml: http://weblogs.asp.net/bsimser/archive/2005/07/21/420147.aspx and it turns out it's very easy and worked well for me. Posting anyway in hopes it will help someone else.
SPFieldCollection.AddFieldAsXml() is the way to go as far as I can tell. See here for an example: http://weblogs.asp.net/bsimser/archive/2005/07/21/420147.aspx
Try with:
SPField newField = null;
newField= web.Fields.CreateNewField("MyFieldTypeName", fieldName);
web.Fields.Add(newField);
newField = web.Fields[fieldName];
// set some properties
newField.ShowInDisplayForm = false;
newField.ShowInViewForms = true;
newField.Update();

Resources