Setting a document field with replaceItemValue from a rich text control? - xpages

How do you set a richText value with replaceItemValue from a rich tect control?
I found this bit of code here:
http://www.bleedyellow.com/blogs/martin/entry/save_a_richtext_field_from_a_xpage_to_a_document?lang=en_us
var doc = configuratieformulieren.getDocumentByKey("ConfiguratieIVNL", true);
if(doc == null){
return;
}else{
var titel = getComponent("inputTextIBPTitelIVNL").getValue();
doc.replaceItemValue("IBPTitel",titel);
var inhoud = getComponent("inputRichTextIBPInhoudIVNL").getValue();
if (inhoud != null){
var contentType = doc.getMIMEEntity("IBPInhoud").getContentType();
var encoding = doc.getMIMEEntity("IBPInhoud").getEncoding();
var str = session.createStream();
inhoud.toString();
str.writeText(inhoud.toString());
doc.getMIMEEntity("IBPInhoud").setContentFromText(str, contentType, encoding);
}
doc.save(true, true);
}
sessionScope.put("FormulierIVNLInfoBeschPG","Lezen");
Is it correct? It looks like this code depends on the fact that the field already exists. How id this handled if the field does not exist? Is there and easier way to set a field value to the contents of a rich text control?

Let data sources do the heavy lifting. For a long and boring (but thorough) explanation of why, read this article. But here's the quick version:
Don't use:
getComponent("someID").getValue()
Instead, use:
someDataSource.getValue("someFieldName")
This is always a more efficient way to access data: instead of having to spider through the component tree to locate a match, it goes straight to the data source, which the component would have to ask anyway if you asked it what its value is.
Similarly, don't use:
someDataSource.replaceItemValue("someFieldName", someValue)
Instead, use:
someDataSource.setValue("someFieldName", someValue)
The latter is much more flexible on input type. The data source already contains all the logic for determining what to do based on whether the value is text, date, number, rich text, file upload, etc. No need to duplicate any of that logic in your own code.
So if the goal is to update a separate document based on data in the current document, just define a separate document data source that points to the document you want to update. Then it's literally this simple:
configData.setValue("RichTextData", currentDocument.getValue("RichTextData"));
configData.save();
With the above code, if the field you specify on the current document is rich text, then the item it creates on the other document will be rich text. If it's any other type on the current document, it will be the same type on the other document. With getValue() and setValue(), you don't have to pay attention to the data type... the data source handles all of that for you.
For bonus points, scope configData to applicationScope so that any updates to it are immediately cached for all users... or sessionScope if the document you're updating is user-specific.

I was able to solve my orginal issue. To expand on my issue I was having problems with using a dialog box to create Form / Document B from Form / Document A using a dialog box on Form A. What was happening was any changes to Form B would be saved to Document A's datasource.
I found the ingoreRequestParams on Form B's datasource, set it and that solved my problem with Form B writing to Form A document.

Related

How to store a list of dates in a multi-value field using SSJS?

In the past, I've added multi-value text data to a field putting the values into a simple JavaScript array. For example:
doc.replaceItemValue('AlwaysAccess', ["John Doe","Bob Smith"]);
Any recommendations on how to store a series of DATES in a multi-value, Time/Date field in a Notes document?
TL;DR: The concept should be almost identical to a multi-value Field of Strings, your Date(/Time) values need to be valid NotesDateTime values properly stored.
A Notes field can have multiple Date/Time values; you can see this in the Form, selecting a field of type Date/Time and checking "Allow multiple values".
You can also see that a multi-value of from the replaceItemValue page of the Domino Designer Knowledge Center.
To accomplish the same with the NotesDominoAPI (in SSJS), we'll need to:
get a handle on the NotesItem (the field, which I'll create)
create our values to put in the field (I'll create a couple using session.createDateTime)
add these values to a java.util.Vector, which will be interpreted as multi-value (you should also be able to use the SSJS Array, if you prefer)
set the values to the field
Sample code (I just ran it in the onClick event of an xp:button):
//create a new doc
var tmpDoc:NotesDocument = database.createDocument();
//give it a Form
tmpDoc.replaceItemValue("Form","MultiDateFieldForm");
//create a NotesItem
var itm:NotesItem = tmpDoc.replaceItemValue("DateFieldName",new java.util.Vector());
//create the Vector, our multi-value container
var vec:java.util.Vector = new java.util.Vector();
//create a couple NotesDateTime values to store
var first = session.createDateTime(new Date());
vec.add(first);
var second = session.createDateTime("Tomorrow");
vec.add(second);
//save the values to the item
itm.setValues(vec);
//save
tmpDoc.save();
//recycle!
first.recycle();
second.recylce();
itm.recycle();
tmpDoc.recylce();
[Edit]
As Frantisek Kossuth points out in the comments, be sure to recycle your NotesDomino API objects (especially the Date/Time ones). I've updated the code to reflect this.
[/Edit]
Checking a Form-based View after running, I'm giving this (field properties reflect the multi-value field of Date/Time values; two shots as it ran out of the box).
Essentially, I found that I needed to create a vector to store the list of dates, and populate it with NotesDateTime objects.
var vRepeatDates:java.util.Vector = new java.util.Vector();
In my case, I needed to increment the dates x amount of times. So, I used a for loop to add the NotesDateTime elements to the vector (while using .adjustDay(1) to increment the dates)
And finally store the vector in a field using replaceItemValue()
doc.replaceItemValue("RepeatInstanceDates",vRepeatDates);

Can't modify/remove a field from an ActivityNode using sbt

I created an ActivityNode (an Entry) and I can add custom fields with the
setFields(List<Field> newListField)
fonction.
BUT
I am unable to modify these fields. (In this case I try to modify the value of the field named LIBENTITE)
FieldList list = myEntry.getTextFields();
List<Field> updatedList = new ArrayList<Field>();
//I add each old field in the new list, but I modify the field LIBENTITE
for(Field myField : list){
if(myField.getName().equals("LIBENTITE")){
((TextField)myField).setTextSummary("New value");
}
updatedList.add(myField);
}
myEntry.setFields(updatedList);
activityService.updateActivityNode(myEntry);
This code should replace the old list of fields with the new one, but I can't see any change in the custom field LIBENTITE of myEntry in IBM connections.
So I tried to create a new list of fields, not modifying my field but adding a new one :
for(Field myField:list){
if(!myField.getName().equals("LIBENTITE")){
updatedList.add(myField);
}
}
Field newTextField = new TextField("New Value");
newTextField .setFieldName("LIBENTITE");
updatedList.add(newTextField );
And this code is just adding the new field in myEntry. What I see is that the other custom fields did not change and I have now two custom fields named LIBENTITE, one with the old value and the second with the new value, in myEntry.
So I though that maybe if I clear the old list of Fields, and then I add the new one, it would work.
I tried the two fonctions
myEntry.clearFieldsMap();
and
myEntry.remove("LIBENTITE");
but none of them seems to work, I still can't remove a custom field from myEntry using SBT.
Any suggestions ?
I have two suggestions, as I had (or have) similar problems:
If you want to update an existing text field in an activity node, you have to call node.setField(fld) to update the field in the node object.
Code snippet from my working application, where I'm updating a text field containing a (computed) start time:
ActivityNode node = activityService.getActivityNode(id);
node.setTitle(formatTitle()); // add/update start and end time in title
boolean startFound = false;
// ...
FieldList textfields =node.getTextFields();
Iterator<Field> iterFields = textfields.iterator();
while (iterFields.hasNext()) {
TextField fld = (TextField) iterFields.next();
if (fld.getName().equals(Constants.FIELDNAME_STARTTIME)) {
fld.setTextSummary(this.getStartTimeString()); // NOTE: .setFieldValue does *not* work
node.setField(fld); // write updated field back. This seems to be the only way updating fields works
startFound=true;
}
}
If there is no field with that name, I create a new one (that's the reason I'm using the startFound boolean variable).
I think that the node.setField(fld) should do the trick. If not, there might be a way to sidestep the problem:
You have access to the underlying DOM object which was parsed in. You can use this to tweak the DOM object, which finally will be written back to Connections.
I had to use this as there seems to be another nasty bug in the SBT SDK: If you read in a text field which has no value, and write it back, an error will be thrown. Looks like the DOM object misses some required nodes, so you have to create them yourself to avoid the error.
Some code to demonstrate this:
// ....
} else if (null == fld.getTextSummary()) { // a text field without any contents. Which is BAD!
// there is a bug in the SBT API: if we read a field which has no value
// and try to write the node back (even without touching the field) a NullPointerException
// will be thrown. It seems that there is no value node set for the field. We
// can't set a value with fld.setTextSummary(), the error will still be thrown.
// therefore we have to remove the field, and - optionally - we set a defined "empty" value
// to avoid the problem.
// node.remove(fld.getName()); // remove the field -- this does *not* work! At least not for empty fields
// so we have to do it the hard way: we delete the node of the field in the cached dom structure
String fieldName = fld.getName();
DeferredElementNSImpl fldData = (DeferredElementNSImpl) fld.getDataHandler().getData();
fldData.getParentNode().removeChild(fldData); // remove the field from the cached dom structure, therefore delete it
// and create it again, but with a substitute value
Field newEmptyField = new TextField (Constants.FIELD_TEXTFIELD_EMPTY_VALUE); // create a field with a placeholder value
newEmptyField.setFieldName(fieldName);
node.setField(newEmptyField);
}
Hope that helps.
Just so that post does not stay unanswered I write the answer that was in a comment of the initial question :
"currently, there is no solution to this issue, the TextFields are read-only map. we have the issue recorded on github.com/OpenNTF/SocialSDK/issues/1657"

xpages copy field value to another field from other datasource

I followed How do you copy a datetime field from the current document to a new document and I try something like this:
Cdoc.save();
Pdoc.copyItem(Cdoc.getDocument().getFirstItem("mytest1"));
getComponent('exampleDialog').show()
But I get a handling error message.
Thanks for your time!
Assuming Cdoc and Pdoc are defined as xp:dominoDocument data sources then you have to change your code to:
Cdoc.save();
Pdoc.getDocument().copyItem(Cdoc.getDocument().getFirstItem("mytest1"));
getComponent('exampleDialog').show()
So, you only need to add .getDocument() to Pdoc to get the Notes Document. Otherwise it fails and you get the error "Error calling method 'copyItem(lotus.domino.local.Item)' on an object of type 'NotesXspDocument'".
Keep in mind that you have to save Pdoc after copying item too if you want to show the copied item in your exampleDialog.
If you don't want to save document Pdoc at this point yet then you can copy the item on NotesXspDocument level with just:
Pdoc.replaceItemValue("mytest1", Cdoc.getItemValueDateTime("mytest1"));
getComponent('exampleDialog').show()
I do not often use "copyItem". You are not specifying if you are using NotesDocuments or NotesXspDocuments, so I will write a quick thing about both because they should be handled differently.
var currentDoc:NotesDocument = ....
var newDoc:NotesDocument= ...
newDoc.replaceItemValue("fldname", currentDoc.getItemValueDateTimeArray("fldname").elementAt(0))
if currentDoc is a NotesXspDocument, use the following
var currentDoc:NotesXspDocument = ...
var newDoc:NotesDocument=...
newDoc.replaceItemValue("fldname", currentDoc.getItemValueDateTime("fldname"))
Otherwise, you could continue trying with copyItem, I just lack experience with it.
EDIT
Just some things to add, remember that putting calling xspDoc.getDocument(true) will update the background document and this might be needed. Also, in the comments for that article you posted, they mentioned the possible need to put that document into another variable.
var docSource:NotesDocument = xspDoc.getDocument(true);
var docNew:NotesDocument = ...
docNew.copyItem(docSource.getItem("blah");
Also remember that copyItem is a function of NotesDocument and not NotesXspDocument.

getting a list of forms that contain a specified field, is there a better method

The following code is a script object on an XPage in it I loop through an array of all the forms in a database, looking for all the forms that contain the field "ACIncludeForm". My method works but it takes 2 - 3 seconds to compute which really slows the load of the XPage. My question is - is there a better method to accomplish this. I added code to check to see if the sessionScope variable is null and only execute if needed and the second time the page loads it does so in under a second. So my method really consumes a lot of processor time.
var forms:Array = database.getForms();
var rtn = new Array;
for (i=0 ; i<forms.length; ++i){
var thisForm:NotesForm = forms[i];
var a = thisForm.getFields().indexOf("ACIncludeForm");
if (a >= 0){
if (!thisForm.isSubForm()) {
if (thisForm.getAliases()[0] == ""){
rtn.push(thisForm.getName() + "|" + thisForm.getName() );
}else{
rtn.push(thisForm.getName() + "|" + thisForm.getAliases()[0] );
}
}
}
thisForm.recycle()
}
sessionScope.put("ssAllFormNames",rtn)
One approach would be to build an index of forms by yourself. For example, create an agent (LotusScript or Java) that gets all forms and for each form, create a document with for example a field "form" containing the form name and and a field "fields" containing all field names (beware of 32K limit).
Then create a view that displays all these documents and contains the value of the "fields" field in the first column so that each value of this field creates one line in this view.
Having such a view, you can simply make a #DbLookup from your XPage.
If your forms are changed, you only need to re-run the agent to re-build your index. The #DbLookup should be pretty fast.
Place the form list in a static field of a Java class. It will stay there for a long time (maybe until http boot). In my experience applicationScope values dissappear in 15 minutes.

Cascading list boxes, with multi-select

I wound up modifying the source from a publically posted POC: http://datacogs.com/datablogs/archive/2007/08/26/641.aspx, which is a custom field definition for cascading drop downs. The modifications were to allow parent-child list boxes where a user can multiselect for filtering and selecting the values to be written back to a SharePoint list. I got the parent-child cascading behavior working, but the save operation only takes the first value that is selected from the list box. I changed the base type for the custom field control from "SPFieldText" to "SPMultiLineText", along with changing the FLD_TYPES field definition values from:
Text to Note and this did not work. So, I changed the field control base type to "SPFieldMultiChoice" and the FLD_TYPES to "MultiChoice" and still get the same result, which is only the first value that's selected, writing to the SharePoint list.
Does anyone have any idea how to get a custom field with multiple selections to write those multiple selections to a SharePoint list?
Thanks for taking the time to read through my post.
Cheers,
~Peter
I was able to accomplish this by inheriting from SPFieldLookup and overriding its internal handling of AllowMultipleValues:
In your FLDTYPES_*.xml set <ParentType>LookupMulti</ParentType>
In your extension of SPFieldLookup, be sure to override AllowMultipleValues (always true), FieldValueType (probably typeof(SPFieldLookupValueCollection)) and FieldRenderingControl. I also set base.LookupField = "LinkTitleNoMenu", though in retrospect I'm not sure why. :)
In your field editor control's OnSaveChange(), set the field's Mult value to true.
To set Mult, you can either string manipulation on field.SchemaXml:
string s = field.SchemaXml;
if (s.IndexOf(" Mult=", StringComparison.OrdinalIgnoreCase) < 0)
field.SchemaXml = s.Insert(s.IndexOf("/>", StringComparison.Ordinal), " Mult=\"TRUE\" ");
Or use reflection:
var type = field.GetType();
var mi = type.GetMethod("SetFieldBoolValue", BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(field, new object[] { "Mult", true });
It's been a while, so I might be forgetting something, but that should be most of it.

Resources