I'm trying to concatenate a string in Expression Language to access a property of an object and failing to do so.
Within an XPage in Lotus Notes, I want to programatically select which field to which I want to bind the control on the current XPage.
The result I would like to achieve is as follows.
#{poDoc[advertisingDateStart];}
I have a variable named fieldName that would supply "advertisingDate" and simply want to append "Start" to this field and "End" to the end date field. I tried several variations that do not work, such as:
#{poDoc[fieldName{'Start'}];}
Note that it would work if I passed in "advertisingDateStart" and used
#{poDoc[fieldName];}
The goal is to be able to place a start date field and an end date field while dynamically binding based on configuration documents. That is, adding fields to my XPage using configuration documents and repeats instead of changing the design. Here is one of the ways I tried to create the ending date field:
<xp:inputText id="inputText5"
style="padding-top:2px;text-align:left">
<xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Date Range"; }]]></xp:this.rendered>
<xp:dateTimeHelper id="dateTimeHelper3"></xp:dateTimeHelper>
<xp:this.converter>
<xp:convertDateTime type="date"></xp:convertDateTime>
</xp:this.converter>
<xp:this.value><![CDATA[#{javascript:poDoc[fieldName+"End"];}]]></xp:this.value>
</xp:inputText>
I just can't figure it out.
Unfortunately, you cannot bind using 'javascript:' notation. In SSJS there is no way of pointing to an 'object property' (getter and setter) that you would like bound to component property.
Only Expression Language can do this with dot notation.
If you really need to bind to dynamic fields, you have to compute the fieldname before using it as Per Henrik Lausten suggested (xp:dataContext is the way to go). Since you want two date fields from one entry, you should use different variables for the new values, computing the value using Javascript
<xp:this.dataContexts>
<xp:dataContext var="fieldName">
<xp:this.value>
<![CDATA[#{javascript:rowData.getColumnValue ("FieldName");}]]>
</xp:this.value>
</xp:dataContext>
<xp:dataContext var="fieldNameDateStart">
<xp:this.value>
<![CDATA[#{javascript:return rowData.getColumnValue ("FieldName") + "Start";}]]>
</xp:this.value>
</xp:dataContext> <xp:dataContext var="fieldNameDateEnd">
<xp:this.value>
<![CDATA[#{javascript:return rowData.getColumnValue ("FieldName") + "End";}]]>
</xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
fieldName would be used for single field entries, while the others would be used only for start dates and end dates.
Related
I have two forms in a database: fmKopf (head) and fmPos (position). I have a DataView which I am using to show me all the fmKopf documents - this works perfectly. The var name for the records are "rowHandle". I have added a repeat control to the details section of the dataView where I would like to display the fmPos documents for the fmKopf. The repeat var name is "rowData". The value for the repleat control is:
var posView:NotesView = database.getView("xpPositions");
if (rowHandle.isDocument()) {
var key = rowHandle.getColumnValue("searchKey");
var vecLieferscheine:NotesViewEntryCollection = posView.getAllDocumentsByKey(key);
return vecLieferscheine;
} else {
return null;
}
I then added a computed field within the repeat control but for the life of me do not know what to use to display the corresponding data from the fmPos document. If I just display rowData I get the UID of the Notes documents - this makes sense as we are returning a NotesViewEntryCollection. I then tried the following code:
var doc:NotesDocument = database.getDocumentByUNID(rowData);
return doc.getItemValue("DLNTLP");
to display the field DLNTLP from the fmPos document - this cannot work as rowData is a NotesViewEntryCollection and not a single value. This causes the page to crash.
What would be the code that I need to get the computed field to display the values from the underlying documents?
Thank you for any help given.
Ursus
Sorry i had to edit the answer multiple times dont know why but stackoverflow timed me out.
getAllDocumentsByKey(key) does not return a NotesViewEntryCollection it returns a NotesDocumentCollection wich does not really mather you just skip the .getDocument();
So in your xp:repeat you already have NotesDocuments, you can work with them you dont need to get the documents from your database using database.getDocumentByUNID().
You can use following code to access your documents via a datasource inside your repeat (wich does more or less the same as database.getDocumentByUNID()):
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:this.data>
<xp:dominoDocument var="document1"
action="openDocument"
documentId="#{javascript:detailDoc.getUniversalID()}">
</xp:dominoDocument>
</xp:this.data>
</xp:panel>
</xp:repeat>
or direkt access every Document from your Collection:
<xp:repeat
id="repeat1" rows="30"
value="YourCode" var="detailDoc">
<xp:panel
<xp:text>
<xp:this.value><![CDATA[#{javascript://
var doc:NotesDocument = detailDoc;
return doc.getItemValueString('DLNTLP');
</xp:this.value>
</xp:text>
</xp:panel>
</xp:repeat>
If you just need one or two fields from your documents i would use the secound methode, the first is very usefull if you also want to manipulate those fields because you can use the document datasource with all it's features. The first example also is usefull if you want to display attachments.
I'm using an Edit Box control to display a date field. When the XPage is saved, I would like to save the date only (now both date and time are being saved). Is there any way of doing this?
Here is my code:
<xp:inputText id="dateReparatur" value="#{document1.dateReparatur}">
<xp:this.converter>
<xp:convertDateTime type="date" dateStyle="long">
</xp:convertDateTime>
</xp:this.converter>
<xp:dateTimeHelper></xp:dateTimeHelper>
</xp:inputText></xp:td>
UPDATE: I have now implemented the following code:
var dt = currentDocument.getItemValueDateTime("dateReparatur");
var dateonly = dt.getDateOnly();
currentDocument.replaceItemValue("dateReparatur",dateonly);
This gives me the date only, however in Notes the field type is now text rather than Date/Time, which is what I was hoping for.
This code worked for me:
<xp:this.postSaveDocument><![CDATA[#{javascript:
var dt:DateTime = document1.getItemValueDateTime("dateReparatur");
dt.setAnyTime();
currentDocument.getDocument(true).replaceItemValue("dateReparatur", dt);
currentDocument.getDocument(true).save()
}]]></xp:this.postSaveDocument>
It does work at postSaveDocument event only. If you put the same code into querySaveDocument event (without document save() line of course) the date field gets polluted with time after event during saving.
An alternative is to execute computeWithForm at querySaveDocument event:
<xp:this.querySaveDocument><![CDATA[#{javascript:
document1.getDocument(true).computeWithForm(true, true)
}]]></xp:this.querySaveDocument>
You'd have to add an Input Translation formula to date field(s) in your form:
#Date(#ThisValue)
computeWithForm has a poor performance and causes sometimes side effects on field values though but might be a good solution especially if you have a lot of such date-only-fields.
getDateOnly() returns a string. Try this:
dt.setAnyTime();
currentDocument.replaceItemValue("dateReparatur", dt);
Or you may have to get the Document:
currentDocument.getDocument(true).replaceItemValue("dateReparatur", dt);
One of my bright ideas is looking a little less bright than I first thought.
Background - I needed to do the classic repeat control solution to the view with DBLookups in one of the columns. I need to have totals for the columns, so I decided that I would use a viewScope variable to hold the subtotal, and add the values as I compute them in the rows of the repeat control.
Sounds good in theory, but for some reason, if I use a Computed Field inside the repeat control, the value is computed (and appended to the subtotal) twice. I have established this with code along the following lines, using both a computed field and an edit box with the same code inside the repeat:
<xp:text escape="true" id="computedField9">
<xp:this.value><![CDATA[#{javascript:
//some calculations, nothing to see here*
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}]]></xp:this.value>
//converters and stuff, nothing to see here
</xp:text>
<xp:inputText id="inputText1">
<xp:this.value><![CDATA[#{javascript:
//Same as the computed field above
</xp:this.value>
</xp:inputText>
For values of 1,2 and 3, my sessionScope variable shows 0~1~1~1~2~2~2~3~3~3 and the subtotal comes out as 18
If I remove the text box, I get 0~1~1~2~2~3~3 and a subtotal of 12
In other words, I'm getting double values for the computed field and single values for the edit box. I'm also getting a zero in there that I can't explain, but I'm assuming that's just an initial value and not something to worry about.
I thought it might have been a case of the repeat being refreshed by something, but then I would expect 1~2~3~1~2~3.
I'm guessing that I just don't understand something fundamental about when computed fields are computed, but I'm stumped. I guess I could switch to edit boxes, but I think Computed Fields are more appropriate here.
Can anybody shed some light on this one?
The JSF Lifecycle sheds some light on the challenge you are facing. The formula you use is executed in multiple phases as Per pointed out. The easiest way to ensure that they are computed only once is to use this code:
if (view.isRenderingPhase() {
var subTotal = viewScope.get("valueColumnTotal");
viewScope.put("valueColumnTotal", subTotal + sumVals);
sessionScope.put("Testing", sessionScope.get("Testing") + "~" + sumVals);
return sumVals;
}
You just need to make sure to reset the value at the appropriate time (e.g. beforeRenderResponse)
The other option to be independent from cycles (you might want to compute a value even if you don't render the page) is to use a small Java class:
public class SumItUp {
private HashMap<String, Integer> totals = new HashMap<String,Integer>();
public void add(String key, int value) {
Integer i = new Integer(value);
this.totals.put(key,i);
}
public int getTotal() {
int result = 0;
for (Map.Entry<String, Integer> me : this.totals.entrySet()) {
result += me.getValue().toInt(); // <-- check that one - off my memory
}
}
Use this as data context or initialize it in your SSJS. Regardless how often the computation is called. When you submit to a Hashmap there won't be duplicate values. Use the clientid (not the id, that's the same for all controls in a repeat) or the document's UNID as key - they are unique
If I read the requirement correctly, the requirement is to show totals for a column.
If the view has categorized columns, here is the solution I used, based off of Notes In 9 – 019 – Xpages: Getting Column Totals From a View
A datacontext is used to pull in the column total from the view:
{** This line of code if (entry==null) {return false}else{return entry} returns a zero if there are no categories for the key value}
The display of the totals in the table used to display the values using a row within a repeat control, is done using a xp:facet with the below code as the Total line:
With the entire facet looking like:
{** Column #4, 5th column over, in the view was the column showing the category total}
Cut-N-Paste Below {**Only to be used by professional Cut-n-Paste'ers}
<xp:dataContext var="contractCategory">
<xp:this.value>
<![CDATA[#{javascript:
try{
var nav:NotesViewNavigator = contractView.createViewNav();
var query = new java.util.Vector();
query.addElement(sGrantId);
var entry:NotesViewEntry = contractView.getEntryByKey(query);
var entry = nav.getPrev(entry);
if (entry==null) {return false}else{return entry}
}catch(e){
return false;
}
}]]>
</xp:this.value>
</xp:dataContext>
<xp:this.facets>
<xp:panel xp:key="footer" id="TotalDisplayArea">
<xp:tr style="background-color:#c9c9c9">
<xp:this.rendered>
<![CDATA[#{javascript:getComponent("repeat1").getRowCount();}]]>
</xp:this.rendered>
<xp:td colspan="2" styleClass="budget_grid_body lotusBold" style="text-align:left">
Totals:</xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:right"> </xp:td>
<xp:td styleClass="budget_grid_body" style="text-align:center;">
<xp:div style="text-align:left;float:left;padding-left:20px;">
<xp:span>$</xp:span>
</xp:div>
<xp:div style="float:right;padding-right:5px;">
<xp:text escape="true" id="totalCostDisp">
<xp:this.converter>
<xp:convertNumber pattern="#,##0.00"></xp:convertNumber>
</xp:this.converter>
<xp:this.value>
<![CDATA[#{javascript:
(operatingCategory)?
operatingCategory.getColumnValues()[4]:
"CodeError:4:2"}]]>
</xp:this.value>
</xp:text>
</xp:div>
</xp:td>
<xp:td style="background-color:#ffffff"></xp:td>
</xp:tr>
</xp:panel>
</xp:this.facets>
I added the new boolean converter to a checkbox and bound this to a number field on the Domino form. When I view the XPage I get an error: Exception
java.lang.String incompatible with java.lang.Boolean.
My source is
<xp:checkBoxGroup
id="checkBoxGroup1"
value="#{document1.Good}"
defaultValue="true">
<xp:this.converter>
<xp:convertBoolean></xp:convertBoolean>
</xp:this.converter>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:var arr = ["true", "false"];
return arr;}]]></xp:this.value>
</xp:selectItems>
</xp:checkBoxGroup>
Just wondering how this new feature in ND9 works???
Howard I posted a tip on this here: http://xpagetips.blogspot.com/2013/01/before-ibm-connect-5-days-5-tips-tip-1.html.
I think you're right. It is only there to make it easier work with boolean values but does not enable actually storing that data type in a Notes document.
Perhaps there is object storage coming in our future??
I know how to bind a computed field to a document field. And I know how to use javascript to compute a computed field. But how do I do both?
Say my javascript in the computed field is:
#Name("[ABBREVIATE]" ,#UserName());
How do I bind that to say document1.ReqName field?
I use two fields:
a hidden input field that computes the required value (using the xp:inputHidden control). I use "default value" to emulate Computed when composed - and converters to emulate Computed (inspired by Tommy Valand).
a visible computed field or a visible input field set to read only that does the same calculation. Perhaps this can be changed to display the value of the hidden input field using getComponent("").getValue()?
Maybe I miss the Point but why don't You simply do the following?
<xp:inputText id="inputText1" value="#{document1.Reader}">
<xp:this.defaultValue>
<![CDATA[#{javascript:#Name("[ABBREVIATE]",#UserName());}]]>
</xp:this.defaultValue>
</xp:inputText>
This is a Textfield bound per EL to the DocumentField. If the field is empty it is calculated by the default value.
Great! - I missed the point. But You can try this one:
<xp:text escape="true" id="computedField3">
<xp:this.value>
<![CDATA[#{javascript:
if(#IsNewDoc()){
document1.replaceItemValue("Reader",#Name("[ABBREVIATE]",#UserName()));
}
return document1.getItemValue("Reader");}]]>
</xp:this.value>
</xp:text>
For the computed field's Value, use Advanced data binding and select Expression Language (EL).
The expression to use is simply "document1.ReqName" (no quotes).
I wouldn't bind in this case. I would bind every other control and in this case manually add that into the document field directly with script before the save action.
so something like: (forgive the code not at my work comp)
<eventhandler>
<actionGroup>
<script><![CDATA[#{javascript:
doc.replaceItemValue("ReqName", #Name("[ABBREVIATE]" ,#UserName()));
}]]><script>
<saveDocument></saveDocument>
</actionGroup>
</eventhandler>
<xp:text escape="true" id="computedFieldTest1">
[CDATA[#{javascript:
var userName = "Username: "+("Reader",#Name("[ABBREVIATE]",#UserName()));
currentDocument.replaceItemValue("computedFieldTest1",userName);
return userName;
}]]></xp:this.value>
</xp:text>