XPages CSJS compositeData fields - xpages

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'
});
}
});

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 and computed field in a composite control

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}"

Set focus to field using SSJS in Xpages?

I am validating my form using SSJS from a submit button. To display the error to the user I am using the extension library dialog box.
How can I set the focus to the field failing validation, using SSJS?
One thing I might be able to do is use CSJS in the OK button of the dialog box. I close it with the OK button as follows:
var errorField = '#{javascript:viewScope.get("errorField")}';
I tried the following but it does not seem to work.
if (errorField != null && errorField != "")
{
var ef = document.getElementsByName("#{id:" + errorField + "}");
ef.focus();
}
I am setting the scope variable errorField when I do the actual validation.
Yes my bad on the missing colon. Ok tried it out and it doesn't seem to be working for me either when I break up the id expression.
you have a viewScope set to the id of the error field, is it possible for you to set that to :
getComponent('...').getClientId();
if you could do that the you would be able to run:
var errorField = '#{javascript:viewScope.get("errorField")}';
var ef = dojo.byId(errorField);
ef.focus();
that works fine for me.
try using:
var ef = dojo.byId('#{id'+errorField+'}');
ef.focus();
getElementsByName is looking for multiple elements with name="..." and returns an array. you can't set focus to an array of controls.
where as in XPages all controls have unique id's, so using dojo to search for ids will return a single element
Try to set the focus after the partial (or full) refresh is done.
<xp:eventHandler event="onClientLoad" submit="false">
<xp:this.script><![CDATA[ var ef = dojo.byId('#{javascript:getClientId(viewScope.get("errorField"))}');
ef.focus();]]></xp:this.script>
</xp:eventHandler>

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.

How to get radio button group functionality for each row in a table using jsf

How to get layout like the following using h:selectOneRadio encapsulated within a rich:dataTable
row1 col1 col2 col3
row2 radio1 radio1 radio1
row3 radio2 radio2 radio2
You can use Tomahawk's <t:selectOneRadio> with layout="spread" and place one <t:radio> in each <rich:column>
I think there are three ways of doing that:
Use the Tomahawk <t:selectOneRadio> and <t:radio> components, as described by Bozho. This is the simplest solution. The only drawback is that you have to integrate another components library in your project (Tomahawk). However, this library is compatible with others libraries, like Richfaces or Icefaces.
Create your own component for that. This is more complex than the previous solution, and may not be really interesting as the Tomahawk library is already providing this component...
Use the "basic" <h:selectOneRadio/> component and then use Javascript code to move the radio elements where you want. In my project, I had a popup that contains a list of checkboxes, but I wanted to display all of them in two columns. Unfortunately, the <h:selectManyCheckbox> component only provides the pageDirection and lineDirection layouts. So I decided to display them using the pageDirection layout, and then manipulate the generated HTML code to break the list into two parts, using Javascript.
So here is what I did on the JSF code:
<h:selectManyCheckbox id="rolesSelection" value="#{myRolesBean.selectedRoles}" layout="pageDirection">
<f:selectItems value="#{myRolesBean.RolesList}"/>
</h:selectManyCheckbox>
<script type="text/javascript">
splitElementsIntoTwoColumns("myForm\\:rolesSelection");
</script>
and the Javascript function (using jQuery, which is bundled in Richfaces library):
function splitElementsIntoTwoColumns(tableId) {
var idx = 1;
var row, next;
while ((row = jQuery('#' + tableId + ' tr:nth-child(' + idx++ + ')')).length) {
if ((next = jQuery('#' + tableId + ' tr:nth-child(' + idx + ')')).length) {
row.append(next.find('td'));
next.remove();
}
}
}
(more explanations about this code here)
In your case, the Javascript function will be harder to write, as you want to move the radio buttons within a table generated by a <rich:datatable>.

Resources