I am currently facing a weird issue with JSF, more precisely the crippled IBM dialect called XPages.
Basically what I want is to loop over an array which is set via SSJS because EL3 isn't a thing in Xpages. So far, so bad.
I have to following source code:
<xp:repeat rows="3" value="#{javascript: ['1','2','3']}" var="i">
<xp:div>
<xp:text styleClass="columnLabel" value="Bei Zahlung innerhalb von "></xp:text>
<xp:text value=" Tagen, "/>
<xp:text style="font-weight: bold" value="#{crmDoc['SKONTO_PZ'+i]}"/>
<xp:text value=" % Skonto "/>
<div class="cleaner4px"></div>
</xp:div>
</xp:repeat>
The type of i is java.lang.String, however, within the EL I am getting the following error (translated from German):
Exception while trying to convert String "SKONTO_PZ" to type "java.lang.Long"
Partial stacktrace:
com.sun.faces.el.impl.ElException: Ausnahmefehler beim Versuch, String "SKONTO_PZ" in Typ "java.lang.Long" zu konvertieren
com.sun.faces.el.impl.Coercions.coerceToPrimitiveNumber(Coercions.java:536)
com.sun.faces.el.impl.Coercions.applyArithmeticOperator(Coercions.java:832)
com.sun.faces.el.impl.ArithmeticOperator.apply(ArithmeticOperator.java:101)
com.sun.faces.el.impl.BinaryOperatorExpression.evaluate(BinaryOperatorExpression.java:189)
com.sun.faces.el.impl.ArraySuffix.evaluateIndex(ArraySuffix.java:141)
com.sun.faces.el.impl.ArraySuffix.evaluate(ArraySuffix.java:170)
com.sun.faces.el.impl.ComplexValue.evaluate(ComplexValue.java:163)
com.sun.faces.el.impl.ExpressionEvaluatorImpl.evaluate(ExpressionEvaluatorImpl.java:257)
com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:150)
com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:134)
javax.faces.component.UIOutput.getValue(UIOutput.java:159)
com.ibm.xsp.util.FacesUtil.convertValue(FacesUtil.java:1121)
I just don't get why on earth he's treating two strings combined with a plus as Long.
As the comments on your post are intimating, XPages EL doesn't really have proper syntax for this: + is addition, and then I don't think there's a string-concatenation operator. The quickest route would be to switch to SSJS for that binding.
Alternatively, if you made that xp:div an xp:panel, you could attach some xp:dataContextss and do what BalusC is suggesting.
Related
Strange issue, I'm using the extlib name picker, looking up a view of names (we don't use a NAB), which can be searched, a name selected and then added. This all works fine. EXCEPT, if a user has an apostrophe in their name. The search works, the name shows, but when you click Add, it simply doesn't do anything, and this is for ANY name with an apostrophe. Has anyone encountered this or know how to get around it? Any help greatly appreciated as there are no errors, or anything written to the console logs or online articles I can find describing it. Thanks Code below:
<xp:inputText id="lstPELName" value="#{document1.PELName}"
maxlength="0" style="display: none;">
<xp:eventHandler event="onchange" submit="true"
refreshMode="partial" refreshId="refreshResponse">
<xp:this.action><![CDATA[#{javascript:try{
document1.save();
// Set People Leader details
var agent:NotesAgent = database.getAgent("SetPELDetails");
agent.runWithDocumentContext(currentDocument.getDocument());
}catch(e){
openLogBean.addError(e,this.getParent());
}}]]></xp:this.action>
</xp:eventHandler>
</xp:inputText>
<div class="col-xs-3 no-border" id="divPELPicker">
<xe:namePicker id="approversNamePicker" for="lstPELName">
<xe:this.dataProvider>
<xe:dominoViewNamePicker databaseName="ppg\dpi.nsf"
viewName="CurrentProfilesByOwner" labelColumn="SystemName">
</xe:dominoViewNamePicker>
</xe:this.dataProvider>
<xe:this.pickerText><![CDATA[#{javascript:"Select your People Leader:";
}]]></xe:this.pickerText>
</xe:namePicker>
</div>
<div class="col-xs-9 no-border" id="divPELName">
<xp:text escape="true" id="cmpPELName"
value="#{document1.PELName}">
</xp:text>
</div>
I assume somewhere a character conversion is missing, probably to JSON. Can you modify the column in the view, and replace an apostrophe by its Unicode equivalent? That would be \u0027. You might have to escape the \ to make it work, as \\ or even \\\\.
I have simple page that I'm using to try and test the #EncodeUrl function from the Extension Library :
<xp:panel id="encodeurl">
<xp:inputText value="#{viewScope.encodeurl}" />
<xp:br />
<xp:text value="#{javascript:var x = #EncodeUrl(viewScope.encodeurl); print( x ); return x; }"
escape="true" />
<xp:button value="submit" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="encodeurl">
</xp:eventHandler>
</xp:button>
</xp:panel>
Using the form to encode
http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces and $ymbol$
doesn't encode do as I would expect as exampled elsewhere, i.e :
http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces+and+%24ymbol%24
Rather, all I get is the same string back, both at the console and the screen. I've tried this on both a stock 9.0 installation, and 9.0.1FP7 with v17 of the extension library.
Am I missing something ?
#EncodeUrl works different than you expect. It
Adds any necessary attributes to a Domino® URL, such as a session
identifier or parameters.
Use
java.net.URLEncoder.encode("your string to encode", "utf-8")
instead. It encodes the spaces and special characters in URL.
Encode only the parameters, not the entire URL. Also, don't encode parameter separator character & nor the parameter name-value separator character =.
When i use multiLine editbox (shown in FIRST ONE) It cannot be editable If the document is in editMode.
But SECOND ONE is editable. My point is if use formula at the value of inputTextArea It can not be editable.
I could not find what it is that i missed?
FIRST ONE:
<xp:inputTextarea id="muvName" rows="2" cols="70"><xp:this.value><![CDATA[#{javascript:#Implode(document1.getItemValue("muvName"))+ #NewLine() + "C/o";}]]></xp:this.value></xp:inputTextarea>
SECOND ONE:
<xp:inputTextarea id="muvName" rows="2" cols="70" value="#{document1.muvName}">
</xp:inputTextarea>
Use the property defaultValue to define a default value:
<xp:inputTextarea
id="muvName"
rows="2"
cols="70"
value="#{document1.muvName}">
<xp:this.defaultValue><![CDATA[#{javascript:
#Implode(document1.muvName)+ #NewLine() + "C/o"
}]]></xp:this.defaultValue>
</xp:inputTextarea>
and use property value for binding a document's item (or a scope variable) to the editable field. value has to be an object to which XPage can write the submitted content to.
In your first example you calculate a string and it's impossible to write something back to a calculated string. That's why the field is read only.
Update:
If you want to correct document field's value before editing then use a custom converter instead:
<xp:inputTextarea
id="muvName"
rows="10"
cols="70"
value="#{document1.muvName}">
<xp:this.converter>
<xp:customConverter
getAsObject="#{javascript:value}">
<xp:this.getAsString><![CDATA[#{javascript:
if (!value.endsWith("C/o")) {
value += #NewLine() + "C/o";
}
value
}]]></xp:this.getAsString>
</xp:customConverter>
</xp:this.converter>
</xp:inputTextarea>
I'm attempting to display the values of a multi-value field as an unordered list on my XPage. Unfortunately, when doing this with documents which do not already have the bound field, I'm informed 'rowData' not found for my itemValueArray. I had created a document prior to these machinations that has values in the field, so that one still opens for me.
Where have I gone wrong?
<xp:div style="display:none;">
<xp:inputText id="linkages" value="#{poDoc.Linkages}" multipleTrim="true" multipleSeparator=";">
</xp:inputText>
</xp:div>
<ul>
<xp:repeat id="linkagesDisplayRepeat" rows="30" var="rowData">
<xp:this.value><![CDATA[#{javascript:var linkages = poDoc.getItemValueArray("Linkages");
return linkages;}]]></xp:this.value>
<li>
<xp:text escape="true" id="computedField7">
<xp:this.value><![CDATA[#{javascript:rowData;}]]>
</xp:this.value>
</xp:text>
</li>
</xp:repeat>
</ul>
Error 'rowData' not found is caused by non-existing item "Linkages".
Render the ul-repeat-block only if item "Linkages" is in document
<xp:panel rendered="#{javascript: poDoc.hasItem('Linkages')}">
<ul>
<xp:repeat
...
</xp:repeat>
</ul>
</xp:panel>
As an alternative you could set a default value to document's item "Linkages" on beforePageLoad event:
<xp:this.beforePageLoad><![CDATA[#{javascript:
if (!poDoc.hasItem("Linkages")) {
poDoc.replaceItemValue("Linkages", "defaultValue");
}
}]]></xp:this.beforePageLoad>
similar to knut's second idea, you could test for a value during data binding. if there is no value, return a default array (e.g., ["data missing"]). otherwise, return the field's value(s). this way you don't have to force a value into the otherwise empty or absent field...unless that is what you want.
You can use simple EL for hide the repeat:
<xp:repeat id="linkagesDisplayRepeat" rows="30" var="rowData"
rendered="#{not empty poDoc.Linkages}">
When viewing a Xpage on the web containing a table with a comboBox in a column the html generated by Domino creates a table for the combobox but a span for other components (tested currently on textFields and computedFields).
This is then rendered with a pixel of vertical alignment difference that annoys me.
How can I overcome this?
<table>
<tbody>
<tr>
<td colspan="3">
<table id="view:_id1:_id2:_id3:legend1:callbackFieldControlSet:CounterpartyName">
<tbody>
<tr>
<td>
AVANZA SWEDEN
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<span id="view:_id1:_id2:_id3:legend1:callbackFieldControlSet:CounterpartyShort">AVANZA SWEDEN</span>
</td>
</tr>
</tbody>
</table>
In edit mode the difference is still there but the generated html of the combobox is not a table but a select and then I can control it using css.
See picture below.
Example read mode
Example edit mode (css edited):
<xp:table>
<xp:tr>
<xp:td>
<xp:label
value="Counterparty name:"
id="counterpartyName_Label1"
for="CounterpartyName"
>
</xp:label>
</xp:td>
<xp:td>
<xp:comboBox
id="CounterpartyName"
value="#{document1.CounterpartyName}"
required="true"
>
</xp:comboBox>
</xp:td>
</xp:tr>
<xp:tr>
<xp:td>
<xp:label
value="Counterparty short:"
id="counterpartyShort_Label1"
for="CounterpartyShort"
>
</xp:label>
</xp:td>
<xp:td>
<xp:inputText
value="#{document1.CounterpartyShort}"
id="CounterpartyShort"
required="true"
styleClass="xspInputFieldEditBox"
>
</xp:inputText>
</xp:td>
</xp:tr>
<xp:tr>
<xp:td>
<xp:label
value="Bic Code:"
id="bicCode_Label1"
for="BicCode"
>
</xp:label>
</xp:td>
<xp:td>
<xp:inputText
value="#{document1.BicCode}"
id="BicCode"
styleClass="xspInputFieldEditBox"
>
</xp:inputText>
</xp:td>
</xp:tr>
</xp:table>
I see you have accepted an answer but I am proposing an alternate solution which simply eliminates the generated HTML. I realize that this can be a tedious solution :)
I had a similar problem with XPages generating tables for checkbox group. Tim Tripcony suggested me to create a custom renderer to modify the output of the control. Its a little tricky at first but once you get a hang of it it does a pretty good job. In your case of read-only fields, it would be more simple :)
1. Create a custom renderer
Here I create a custom renderer Java class called pkg.MyRenderer. You can create this Java file in "Code > Java". Here I just output the selected value of the combo box and nothing else.
package pkg;
import java.io.IOException;
import java.io.Writer;
import javax.faces.component.UISelectOne;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.render.Renderer;
public class MyRenderer extends Renderer {
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
UISelectOne obj = (UISelectOne)component;
Writer writer = context.getResponseWriter();
writer.write(obj.getValue().toString());
}
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
}
}
2. Register your custom renderer
Now in the Package Explorer go to "YourDatabase.nsf > WebContent > WEB-INF > faces-config.xml". There you need to register your custom renderer. You just need to make sure the component-family is same as the one to which the combo box belongs to.
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>pkg.MyRendererType</renderer-type>
<renderer-class>pkg.MyRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
3. Assign your custom renderer to control
As you want the custom renderer to be active only when it is in reading only mode you check for it and then set the rendererType of control as the renderer-type defined in faces-config.xml i.e. pkg.MyRendererType.
Here I check if the control comboBox1 is in read-only mode, if yes then use pkg.MyRendererType else use the control's renderer.
<xp:comboBox id="comboBox1" .....>
<xp:this.rendererType><![CDATA[${javascript:var cb:com.ibm.xsp.component.xp.XspSelectOneMenu = getComponent("comboBox1");
cb.isReadonly() ? "pkg.MyRendererType" : cb.getRendererType();}]]></xp:this.rendererType>
.....
.....
.....
</xp:comboBox>
When you run this with control on read-only mode then only the value of combo box is displayed and nothing else (no tables, yay!). And when the control is not in read-only mode then a drop-down list is shown as before.
problem is that the inner table has its own borders (as always: one for the table itself and one for the td) which need to be collapsed. Just create some CSS to handle this:
give your outer table its own style-class e.g. "myOuterTable"
your css would then address any table inside ".myOuterTable", like this:
table.myOuterTable table {border-collapse: collapse;}
This works fine in both edit and read mode because only in read mode you have that extra "inner" table; in edit mode, there's nothing to be collapsed...
works even in IE, btw...
Hope it works for you
3 possible actions (in depending complexity) :
write your own drop down component
add a text field. Use the visible property to show/hide only one based on read/edit mode
use css and padding (can take negative values) to move one pixel
This code will fix all comboboxes on the page with dojo, run onClientLoad:
if("#{javascript:document1.isEditable()}" == "false"){
// Search for all nested tables with ids
dojo.query('table td table[id]').forEach(function(node) {
var value = '';
// Locate the first table cell, which contains the value
dojo.query('td:first-child', node).forEach(function(innerNode) {
value = innerNode.innerHTML;
});
// Replace the table with only the value (or a blank)
node.outerHTML = value;
});
}
Notes:
Update the name of the document in line 1 to match the name of the document on your page.
This code assumes that your combobox is already nested in a table cell. If that's not the case, you can refine the selector in line 3.
If you run into conflicts with this affecting other tables in your page, you may need to refine the selector in line 3.