Xpages and computed field in a composite control - xpages

I've been able to successfully create dynamic field names and save the values for input fields using the technique described here: http://lpar.ath0.com/2014/04/07/repeated-xpages-input-controls-with-number-indexed-field-names/
I also have a computed field which has a number indexed name but whose value is computed based on a keyword choice. I can assign the dynamic field name for it like so:
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{compositeData.dataSource[compositeData.fieldName2]}]]></xp:this.value>
</xp:text>
The property definition for this field looks like this:
and the call to the composite control looks like this:
<xc:cc_dynamicAssetItems
row="#{(rownum lt 10)? '0':''}#{rownum}"
dataSource="#{document1}"
fieldName1="replace#{(rownum lt 10)? '0':''}#{rownum}"
fieldName2="budget#{(rownum lt 10)? '0':''}#{rownum}" >
</xc:cc_dynamicAssetItems>
I am at a loss however on how to pass the value to this computed field. The SSJS for it would have been:
var projectNumber = getComponent("ProjectNumber").getValue();
if(projectNumber == ""){
return "Nothing found";
}else{
return projectNumber + "A";
}
I'd appreciate some direction.
Thanks,
Dan

Instead of setting the value of your component, you should set the value of the underlying datasource. This means that your logic should not run in your computed field, than in your onChange event of the ProjectNumber component.
Then you have to update e.g. document1.budget01 only, which is a lot faster.
As an alternative, you can still pass a method binding* to your custom control, as described here: Pass javascript code to Custom Control
(*: in your case a value binding)

did you try javascript instead expression language
ex:
fieldName1="#{javascript: 'replace'+(rownum > 10? '0':'')+rownum}"

Related

Xpages: Cannot compute property "defaultValue" in custom control

I have a custom control which contains a comboBox. One property of the CC is default value, which I want the developer to be able to pick in the Xpage.
Static values pass into the CC fine, but if I try to default the value, it fails. I would like to default the default property to the current user, but it cannot.
This is the custom control on an the Xpage. I can return a name statically and it works, but if I compute the name it doesn't work.
<xc:cc_commonfieldselect2fromcache
datasource="#{javascript:return Ticket}"
cacheitem="employees"
fieldname="tckReqs"
fieldlabel="Requester">
<xc:this.defaultvalue>
<![CDATA[#{javascript:var usrNme:String = ("[CN]",session.getEffectiveUserName());
return usrNme;
//return "Bryan S Schmiedeler";}]]>
</xc:this.defaultvalue>
</xc:cc_commonfieldselect2fromcache>
Here is part of the custom control: I am trying to pass in the compositeData.defaultValue that was set above. If I hard code it it works, otherwise it doesn't.
<xp:comboBox
id="${javascript:compositeData.fieldName}"
value="#{compositeData.dataSource[compositeData.fieldName]}"
defaultValue="${javascript:compositeData.defaultValue}">
<xp:selectItems
value="${javascript:'#{CacheBean.'+compositeData.cacheItem+'}'}">
</xp:selectItems>
</xp:comboBox>
Here is how I have set the property in the custom control:
Your code used the defaultValue (inside your cc) before it was stored into your cc´s compositeData.
${} => computed a single time on pageload (and before all dynamically computed)
#{} => dynamically computed
You have 2 options
set your cc defaultValue to dynamically computed (#)
provide the defaultValue also on pageload ($)
In your code you compute the default value at "load time":
defaultValue="${javascript:compositeData.defaultValue}"
while you need to compute it at runtime:
defaultValue="#{javascript:compositeData.defaultValue}"
that should do the trick

XPages CSJS compositeData fields

I've got a repeat control in my form with a composite control to handle the fields that are bound to the datasource. For example:
<xp:comboBox id="replace"
styleClass="form-control"
value="#{compositeData.dataSource[compositeData.fieldName1]}">
<xp:selectItem itemLabel="Select a Code"
itemValue="Select a Code">
</xp:selectItem>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:var db = sessionScope.serverPath + "!!" + sessionScope.dbName;
var companyCode = #Trim(#Unique(#DbLookup(db,"vwTblCompany", company,2)));
return #Trim(#Unique(#DbLookup(db,"vwTables","Replacement",3)));
}]]></xp:this.value>
</xp:selectItems>
</xp:comboBox>
The fields are bound as follows:
<xp:repeat indexVar="rownum" first="1"
rows="#{javascript:viewScope.rowCount }" var="data"
value="#{javascript:viewScope.rowCount + 1}">
<xc:cc_dynamicAssetItems
row="#{(rownum lt 10)? '0':''}#{rownum}"
dataSource="#{document1}"
fieldName1="replace#{(rownum lt 10)? '0':''}#{rownum}"
fieldName2="item#{(rownum lt 10)? '0':''}#{rownum}"
fieldName3="class#{(rownum lt 10)? '0':''}#{rownum}"
fieldName4="cur#{(rownum lt 10)? '0':''}#{rownum}"
fieldName5="costEst#{(rownum lt 10)? '0':''}#{rownum}"
fieldName6="costEstUS#{(rownum lt 10)? '0':''}#{rownum}"
fieldName7="life#{(rownum lt 10)? '0':''}#{rownum}">
</xc:cc_dynamicAssetItems>
</xp:repeat>
Since I have a handle to the viewScope variable that holds the rowCount in the repeat (thanks to an RPC call) in CSJS, I'd like to be able to validate each row of the repeat in CSJS. How do I get a handle to the field? I know Tim Tripcony used to recommend going straight to the datasource. Since the id "comboBox1" in my example is in the repeat control and being used for every row, I'm not sure I should be using that to get the value. In my mind since that field is bound to replace01, replace02 ..., I should be trying to get the value of replace01, right?
I can't use the following either because the id of the combobox field is not being computed dynamically.
var val = XSP.getElementById("replace01").value
I looked into Brad's example of generating dynamic component id's within a repeat control but when I use that method, the ability for me to add rows within the repeat control breaks.
http://xcellerant.net/2013/07/29/access-repeat-components-from-outside/
Can anyone assist with an example?
Follow Tim: go after the data source. Get element by ID doesn't do that. There are 2 approaches you might consider:
Validate your data source on the server side and use an errors control to show the result. This follows the idea: validation validates data, not UI interaction
drop the repeat control and make your data available using an ExtLib Rest control. Implement the UI logic complete in CSJS including rendering the individual rows.
Anything in between could get messy. But if you have to: you can always generate a script call inside the repeat that takes the generated ids of that iteration as parameters. In a script tag simply compute something like:
Return 'var x = {"'+getId('replace01')+'","'+getId('anotherfield')+'"};
mastervalid.push(x);'
(Replace getId with the SSJS function that gives you the rendered id). Mastervalid would be an array defined in CSJS outside the repeat. You end up with an array that has all the client Id fields in a JS object. Feed that into your validation function
A big thank you to STW, Brad Balassaitis and Keith Strickland for their assistance helping me think through this and giving me various options.
I ended up using dojo.query to get a handle to the dynamically generated client ID's within the repeat control and then used foreach to validate each node.
//validate Replacement Code
dojo.query('[id$="replace"]').forEach(function(node, index) {
//alert(index + ': ' + node.value);
if(node.value == "Select a Code"){
dojo.style(node, {
border: 'red solid 1px'
});
}
});

How do I compute the Selected property of a BasicLeafNode for a Dynamic Content Control - Updated 03/26/2014

I have created an XPage with the following: Started by creating a custom layout control using the Application Layout. I aded the layout control to the xpage and then dropped in a Dynamic Content Control. I configured the control as follows:
<xe:dynamicContent id="dynamicContent1" defaultFacet="GovernanceReviews"
useHash="true">
<xp:this.facets>
<xc:ccViewDocumentTemplates xp:key="DocumentTemplates"></xc:ccViewDocumentTemplates>
<xc:ccViewGovProcurementReviews xp:key="GovProcurementReviews"></xc:ccViewGovProcurementReviews>
<xc:ccViewGovRevReporting xp:key="GovRevReporting"></xc:ccViewGovRevReporting>
<xc:ccViewGovRevWOCompleted xp:key="GovRevWOCompleted"></xc:ccViewGovRevWOCompleted>
<xc:ccViewGovernanceReviews xp:key="GovernanceReviews"></xc:ccViewGovernanceReviews>
<xc:ccViewProfilesByType xp:key="ProfilesByType"></xc:ccViewProfilesByType>
<xc:ccViewProfilesWithTargetCompl xp:key="ProfilesWithTargetCompl"></xc:ccViewProfilesWithTargetCompl>
<xc:ccViewLastUpdated xp:key="LastUpdated"></xc:ccViewLastUpdated>
<xc:ccViewUserGuide xp:key="UserGuide"></xc:ccViewUserGuide>
<xc:ccViewTracking xp:key="Tracking"></xc:ccViewTracking>
</xp:this.facets>
</xe:dynamicContent>
Then I dropped in a navigator control in the left column and created BasicLeafNodes to correspond to the dynamic content control I used the href property and used the #content="" to display the correct content.
This works just fine, but I am having problems figuring out how to make the selections in the navigator highlight when they are selected. I know I need to compute the Selectd property,but I can't figure out how to get the xp:key value so I can compare it to the SubmitValue. I know this is probably something simple, but I can't figure it out.
Can someone please enlighten me.
Thanks,
MJ
ADDED 03/26/2014 - I have a feeling that it has something to do with Using the href property of the Dynamic Content Control to perform the content switching. I know that makes the BasicLeafNodes Links. So, not sure how the Navigator records which link is being executed and how to capture that.
MJ
Add a value is the submitValue property
And in the onItemClick Event
Assign the submitted value to a viewScope variable
viewScope.Selected=context.getSubmittedValue()
And finally check if the viewScope variable equals your item submit value in the selected property. This needs to be calculated
if(viewScope.Selected="byCategory"){
return true
}else{
return false
}
The following is working for me:
if(viewScope.Selected == "byCategory"){
return true
} else{
return false
}
An equality test must be made with two equal symbols (or three). One equal symbol evidently always returns true.
I did it by jQuery. Just put the following code to the custom control, which contains navigator.
$( function() {
if (window.location.hash.length > 0) {
select()
}
});
$(window).on('hashchange', function() {
select();
});
function select() {
$(".lotusColLeft .lotusMenu .lotusBottomCorner li").removeClass(
"lotusSelected")
$(".lotusColLeft .lotusMenu .lotusBottomCorner li a")
.filter(
function() {
return window.location.hash.indexOf($(this).attr(
'href')) > -1
}).parent().addClass("lotusSelected")
}

How do you make a computed field both computed and databound to a field?

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>

Binding an edit box within a custom control to a form field programatically

I have a notes form with a series of fields such as city_1, city_2, city_3 etc.
I have an XPage and on that XPage I have a repeat.
The repeat is based on an array with ten values 1 - 10
var repArray = new Array() ;
for (var i=1;i<=10;i++) {
repArray.push(i) ;
}
return(repArray) ;
Within the repeat I have a custom control which is used to surface the fields city_1 through city_10
The repeat has a custom property docdatasource which is passed in
It also has a string custom property called cityFieldName which is computed using the repeat
collection name so that in the first repeat row it is city_1 and in the second it is city_2 etc..
The editable text field on the custom control is bound using the EL formula
compositeData.docdatasource[compositeData.cityFieldName]
This works fine but each time I add new fields I have to remember to create a new custom property and then a reference to it on the parent page.
I would like to be able to simply compute the data binding such as
compositeData.docdatasource['city_' + indexvar]
where indexvar is a variable representing the current row number.
Is this possible ? I have read that you cannot use '+' in Expression Language.
First: you wouldn't need an array for a counter. Just 10 would do (the number) - repeats 10 times too. But you could build an array of arrays:
var repArray = [];
for (var i=1;i<=10;i++) {
repArray.push(["city","street","zip","country","planet"]) ;
}
return repArray;
then you should be able to use
#{datasource.indexvar[0]}
to bind city,
#{datasource.indexvar[1]}
to bind street. etc.
Carries a little the danger of messing with the sequence of the array, if that's a concern you would need to dig deeper in using an Object here.
compute to javascript and use something like
var viewnam = "#{" + (compositeData.searchVar )+ "}"
return viewnam
make sure this is computed on page load in the custom control
I was never able to do the addition within EL but I have been very successful with simply computing the field names outside the custom control and then passing those values into the custom control.
I can send you some working code if you wish from a presentation I gave.

Resources