Getting properties/parameters from page level - xpages

I wonder if I can get the parameters and/or properties of an xpage or custom control programmatically.
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" id="layout">
<xp:this.properties>
<xp:parameter name="testcc.xsp" value="Test 1"></xp:parameter>
<xp:parameter name="index.xsp" value="Main"></xp:parameter>
</xp:this.properties>
...
How can I access this parameter list to use it e.g. in a repeat control?
EDIT
You both are right, thank you! But this works only on a page, not in a custom control.
EDIT
You both are great :-)
BUT: I should revise my question:
I have a custom control where I defined the properties. Within the SAMe custom control I want to access these properties in a repeat control.
Both your answers seem to assume that the access to these properties is from the view (page) level, right?
I tested Svens way - this works if I access the props in the CC from the page level.
EDIT
So this is the code of the CC:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.properties>
<xp:parameter name="param" value="val"></xp:parameter>
</xp:this.properties>
<xp:label value="#{javascript:facesContext.getProperty('param')}"
id="label1">
</xp:label>
</xp:view>
As you can see I just want to access the property within the CC itselt, not from the page level.

You can get the properties by accessing facesContext:
facesContext.getProperty("index.xsp")
EDIT:
If you set the properties in a custom control, the properties are not added to the view root. The are set as attributes of the custom control (com.ibm.xsp.component.UIIncludeComposite).
To access them you first have to give your CC an Id:
<xc:ccProp id="myId" />
This allows you to access the custom control like a component with the getComponent() method and retrieve the attribute properties which contains the properties:
<xp:label id="labelProperty">
<xp:this.value><![CDATA[#{javascript:
var cc:com.ibm.xsp.component.UIIncludeComposite = getComponent("myId");
var arrList:java.util.ArrayList = cc.getAttributes().get("properties");
arrList.get(0).getName()}]]>
</xp:this.value>
</xp:label>
EDIT 2:
You can access the CC (which is the parent of the label in this example) this way if you don't want to give your CC an ID:
Code of the custom control:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.properties>
<xp:parameter name="param" value="val"></xp:parameter>
</xp:this.properties>
<xp:label id="label1">
<xp:this.value><![CDATA[#{javascript:
this.parent.getAttributes().get("properties").get(0).getName()
}]]></xp:this.value>
</xp:label>
<xp:label id="label2">
<xp:this.value><![CDATA[#{javascript:
this.parent.getAttributes().get("properties").get(0).getValue()
}]]></xp:this.value>
</xp:label>
</xp:view>
Hope this helps to clarify the issue.

To get the property list you can use the view.getProperties(). It returns an object of java.util.List which you can use to loop through individual properties (which are objects of com.ibm.xsp.complex.Parameter). Below is the snippet.
var allProperties:java.util.List = view.getProperties();
for (var i=0 ; i<allProperties.size() ; i++) {
var property:com.ibm.xsp.complex.Parameter = allProperties.get(i);
// property.getName();
// property.getValue();
}
If you want to put it in a repeat then you can bind it to view.getProperties() and then get its individual values. You code would then look something like this:
<xp:repeat rows="30" value="#{javascript:view.getProperties()}" var="property">
<xp:text escape="true">
<xp:this.value><![CDATA[#{javascript:property.getName() + " - " + property.getValue();}]]></xp:this.value>
</xp:text>
<xp:br></xp:br>
</xp:repeat>

If you've got values that you want to use on the various parts of an Xpage, whether directly on the page, in a custom control, or in a repeat, I would recommend that you put the values into sessionScope variables. This allows you to change them easily as the user enters information on the Xpage.
For example, sessionScope.PODocUNID = poDoc.getDocument().getUniversalID(); would put the UNID of the purchase order document that I'm working with into a sessionScope variable named PODocUNID. Then, you can pull up the value any time you want by simply referencing sessionScope.PODocUNID in your code.
Alternatively, you could use Russ Maher's current favorite toy, the Managed Bean (see his three-part video on Notes in 9, starting at: http://notesin9.com/index.php/2012/11/02/notesin9-084-sharing-managed-beans-in-xpages/ )

Related

Save data to compositeData.dataSource

I'm running around in circles...
I'll try to explain what I'm trying to do: we have an XPage in a Notes database for a specific document (all web). We also have defined our own forms inside the database. When the document is opened in XPages, the form is retrieved and the fields are populated. That works, I'm glad to say.
Then, we defined some kind of subform. The idea is that the subform displays specific fields in a different way. There can be multiple subforms attached to a form. All these elements are stored in custom controls, say ccForm, ccSubform and ccField.
Upon opening the document, I get the NotesXspDocument object which I can pass to ccForm. The custom control fetches the form definition, and uses a repeat control to generate ccField and a few ccSubform controls. These get passed a lot of parameters, e.g. the current document as a dataSource parameter. When there's a subform, data is copied from the main document and stored (differently) in a temporary document, declared inside ccSubform. The subform opens and reads a different form definition and generates its own ccField controls, using yet another repeat control.
So the NotesXspDocument is created at the top and passed to all controls using a DataSource parameter. The data related to the subform however is stored in a temporary NotesDocument.
My problems:
when the Save button is clicked, only the QuerySave event of the main XPage is triggered, and I can't find out how to trigger some sort of Save event for the temporary documents inside the subforms; I want to copy data back to the main document
is there some other event I can use to copy data back to the main document?
I have the usual compositeData issue in afterRestoreView; how can I get around it?
Awaiting your replies...
Thanks!
update
Current ccSubform, with some debugging:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom"
xmlns:xe="http://www.ibm.com/xsp/coreex" enableModifiedFlag="true" id="ccSubForm">
<xp:this.data>
<xp:dominoDocument var="docTemp" ignoreRequestParams="true">
<xp:this.querySaveDocument><![CDATA[#{javascript:dprint("subform qSD")}]]></xp:this.querySaveDocument>
<xp:this.postNewDocument><![CDATA[#{javascript:var sf= new ccAnyForm(docTemp,compositeData.formName);
viewScope.put(compositeData.formName, sf);}]]></xp:this.postNewDocument>
</xp:dominoDocument>
</xp:this.data>
<xp:this.afterRestoreView><![CDATA[#{javascript:try {
dprint("aRV: " + getClientId("ccSubForm"))
var sf= viewScope.get(compositeData.formName);
dprint("sf= " + sf);
if(!sf)
return;
var mdoc:NotesXspDocument= compositeData.dataSource;
var fields= sf.getFields();
for(var fi in fields) {
var fieldName= fields[fi].name;
var values= [];
for(var i= 0; i<rows.size(); i++) {
var tmp= docTemp.getItemValue(fieldName+"$"+i);
values.push(tmp);
dprint(fieldName + "[" + i + "]= " + tmp);
}
mdoc.replaceItemValue(fieldName, values)
}
}catch(e) {
dprint(e);
}}]]></xp:this.afterRestoreView>
<xp:this.beforePageLoad><![CDATA[#{javascript:dprint("bPL: " + getClientId("ccSubForm"))
}]]></xp:this.beforePageLoad>
<xp:tr>
<xp:td styleClass="label">
<xp:text escape="true" value="#{javascript:compositeData.label}"></xp:text>
</xp:td>
<xp:td>
<xp:panel styleClass="subform">
<xp:table style="width:100%" cellspacing="1" cellpadding="0">
<xp:tr>
<xp:repeat var="thisfield" indexVar="coli">
<xp:this.value><![CDATA[#{javascript:var sf= viewScope.get(compositeData.formName);
if(!sf)
return;
var mdoc:NotesXspDocument= compositeData.dataSource;
var fields= sf.getFields();
fields}]]></xp:this.value>
<xp:td styleClass="label">
<xp:text escape="true" value="#{javascript:thisfield.label}"></xp:text>
</xp:td>
</xp:repeat>
</xp:tr>
<xp:repeat rows="#{javascript:compositeData.rows}" disableTheme="true" var="row" indexVar="rowi">
<xp:this.value><![CDATA[#{javascript:var sf= viewScope.get(compositeData.formName);
if(!sf)
return;
var rows= [];
var fields= sf.getFields();
for(var fi in fields) {
var fieldName= fields[fi].name
var values:java.util.Vector= mdoc.getItemValue(fieldName);
if(values) {
for(var i= 0; i<values.size(); i++) {
rows[i]= i+1;
docTemp.replaceItemValue(fieldName+"$"+i, values[i])
}
}
}
return rows;}]]></xp:this.value>
<xp:tr>
<xp:repeat var="currfield" indexVar="coli">
<xp:this.value><![CDATA[#{javascript:var sf= viewScope.get(compositeData.formName);
if(!sf)
return;
var rows= [];
var fields= sf.getFields();
return fields}]]></xp:this.value>
<xp:td>
<xp:this.styleClass><![CDATA[#{javascript:compositeData.dataSource && compositeData.dataSource.isEditable && compositeData.dataSource.isEditable() && !compositeData.isEditable? "data readonly": "data"}]]></xp:this.styleClass>
<xc:ccfieldType="#{javascript:currfield.type}"
fieldLabel="#{javascript:currfield.label}" fieldTitle="#{javascript:currfield.title}"
fieldValue="#{javascript:currfield.value}" fieldIcon="#{javascript:currfield.icon}"
dataSource="#{javascript:docTemp}" formSource="#{javascript:compositeData.formSource}"
<xc:this.rendered><![CDATA[#{javascript:try {
return af? true: false;
} catch(e) {
return false;
}}]]></xc:this.rendered>
<xc:this.fieldName><![CDATA[#{javascript:currfield.name + "$" + rowi}]]></xc:this.fieldName>
<xc:this.fieldIdName><![CDATA[#{javascript:'id'+currfield.name+"$"+rowi}]]></xc:this.fieldIdName>
<xc:this.fieldUniqueName><![CDATA[#{javascript:'unique'+currfield.name+"$"+rowi}]]></xc:this.fieldUniqueName>
</xc:ccDynamicField>
</xp:td>
</xp:repeat>
</xp:tr>
</xp:repeat>
</xp:table>
</xp:panel>
</xp:td>
</xp:tr>
</xp:view>
You need to take one step back and revisit the approach. XPages doesn't limit you to Documents as data sources. It allows binding to Java beans .... and that points to the solution.
Step 1: for each 'special handling' custom control make one bean that gives you exactly what you need.
Step 2: Design one bean to be used as object data source in the main form. The object data source can be designed so you load a document. Don't try to store the document, only the field values and the unid so you can later save it back
Step 3: design properties in that object datasource that return the beans from step 1
Step 4: design your custom controls to take the bean a parmeter. Be specific with the datatype. You can use #{objDatasource.propname} to provide the parameter. Inside the custom controls use standard EL to bind the beans.
Now when you save the data source you have all data inside your main bean and a very clean layout.
Does that work for you?
I'm struggling to see the reason for creating the temp documents, copying the data across, then writing additional data back and not saving the document. I don't think you need to get that complicated because:
You can bind to and from the main document datasource within dependent custom controls. The currentDocument variable gets the nearest dominoDocument datasource, navigating back up the component tree. Alternatively, if your main datasource is always called document1, you can use that. All you need to ensure is that the relevant dominoDocument datasource is defined at a level above the custom control (if you visualise your components in a tree, with parents and children). You just won't be able to select fields in the drop-down.
If you set a value that's not in the main document, that can be computed on the defaultValue property of any controls on the custom control. Or you could set the relevant field on your main document during beforePageLoad / beforeRenderResponse etc.
Dynamic Content Control can be used to programmatically load only the relevant custom control(s).
Alternatively, if you want the temporary document approach, I'd recommend actually saving those documents with some key back to the main document. Then using a Save All Documents action, so you have them in the database, and retrieve them in postSaveDocument of the main document (personally I'd use script in the button instead) to retrieve the documents, write the values back to the main document and delete / flag so an agent can delete them. sessionAsSigner will get past any access limitations here.
If you have a reason for not wanting to clutter the database, don't forget XPages allows you to edit data from multiple databases, so you can always have a "temp store" where these sub-documents get created, edited and stored. The database can be computed, so it can be agnostic of the environment - dev, test, prod. Access can be set separately on that database, if you need to (e.g. allowing the users to delete from that temp store).
Every document data source has its own set of query type events. You could use those to get the data and then write it back to the main document.
Howard

Show new documents in view xe:pagerAddRows without full update

When user1 sees view with xe:pagerAddRows which show last document in view, another user2 adds new document. user1 clicks xe:pagerAddRows and can't see new document.
How to show new documents for user1 in view using click xe:pagerAddRows without full update page?
How to show new documents for user1 created by user2 automatically without click and full update page?
<xe:dataView
id="dataView1"
openDocAsReadonly="true"
var="viewEntry"
expandedDetail="true"
style="margin-top:20.0px"
repeatControls="true"
rows="1">
<xp:this.facets>
<xp:panel
xp:key="detail">
<xp:text
escape="true"
style="font-family:Arial;margin-right:10.0px"
id="computedField1">
<xp:this.converter>
<xp:convertDateTime
type="date">
</xp:convertDateTime>
</xp:this.converter>
<xp:this.value><![CDATA[#{javascript:
var document : NotesDocument = viewEntry.getDocument();
return #Name('[CN]',document.getItemValue('Author'))
}]]></xp:this.value>
</xp:text>
</xp:panel>
<xe:pagerAddRows
xp:key="pagerBottomLeft"
partialExecute="true"
partialRefresh="true"
refreshPage="false"
id="pagerAddRows1"
for="dataViewUtterance"
state="true"
rowCount="1"
refreshId="ShowMoreUtterance"
disabledFormat="link">
</xe:pagerAddRows>
</xp:this.facets>
<xe:this.data>
<xp:dominoView
viewName="(Documents)"
var="viewData"
ignoreRequestParams="false"
categoryFilter="#{javascript:
currentDocument.getDocument().getItemValueString('Flow')}"
dataCache="id"
scope="view">
</xp:dominoView>
</xe:this.data>
</xe:dataView>
1.
You can refresh your dataView adding an onclick event to xe:pagerAddRows.
<xe:pagerAddRows
xp:key="pagerBottomLeft"
for="dataView1"
state="true"
rowCount="2">
<xp:eventHandler
event="onclick"
refreshMode="partial"
refreshId="dataView1">
</xp:eventHandler>
</xe:pagerAddRows>
This causes a refresh of your view and will show documents created by other users since last refresh.
2.
You can refresh your view with the help of a timer function on client side. setInterval() will execute a function every x seconds. The following example is executing a partial refresh of dataView1 every 5 seconds. Just add the code to your XPage.
<xp:scriptBlock
id="scriptBlockRefresh">
<xp:this.value>
<![CDATA[
setInterval(function() {
XSP.partialRefreshGet("#{id:dataView1}", {})
}, 5 * 1000)
]]>
</xp:this.value>
</xp:scriptBlock>
3.
It seems to me you'd like to show all view entries in real time and expand view automatically. If that's the case you could change the sort order so that newest documents would appear on top. This way and with the automatic refresh, you would always see the newest entries right on your first page.

calculate the rendering of a custom control

In an xpage I would like to be able to decide which custom controls have to be rendered or loaded.
I have a custom control named 1, another 2, 3 etc
When a scoped variable has the value 1, custom control 1 should be displayed/rendered/loaded.
A value of 2 , custom control 2 has to be displayed. etc
I came up with following sollution :
I calculate if that custom control has to be loaded or not depending on the value of the scoped variable.
Since I have 8 of these custom controls on 1 page I was wondering ,since only 1 out of those 8 custom controls have to be rendered ,if there isn't a better way with less code to do the same job. Won't my sollution put a lot of load to my server ?
A better solution is to inject your custom control on the fly. This can be easily done with java:
package ch.hasselba.xpages;
import java.util.UUID;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import com.ibm.xsp.extlib.builder.ControlBuilder;
import com.ibm.xsp.extlib.builder.ControlBuilder.ControlImpl;
import com.ibm.xsp.component.UIIncludeComposite;
import com.ibm.xsp.context.FacesContextExImpl;
import com.ibm.xsp.util.FacesUtil;
public class XPagesUtil {
public void injectCC( final String ccName , final String componentName ){
FacesContextExImpl fc = (FacesContextExImpl) FacesContext.getCurrentInstance();
UIComponent cmp = FacesUtil.findChildComponent(fc.getViewRoot(), componentName );
UIIncludeComposite objCtrl = new UIIncludeComposite();
objCtrl.setPageName( ccName );
objCtrl.setId("new_" + UUID.randomUUID() );
ControlBuilder cBuilder = new ControlBuilder();
ControlImpl<UIIncludeComposite> objImplControl = new ControlImpl<UIIncludeComposite>(objCtrl);
ControlImpl<UIComponent> objImplParent = new ControlImpl<UIComponent>(cmp);
objImplParent.addChild(objImplControl);
cBuilder.buildControl(fc,objImplParent,false);
}
}
To use the code on your XPage, you now can do the following:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.afterPageLoad>
<![CDATA[#{javascript:
importPackage( ch.hasselba.xpages.extlib );
var nameOfCC = "/IncludeCC.xsp";
new ch.hasselba.xpages.XPagesUtil().injectCC(nameOfCC, "parent");
}]]>
</xp:this.afterPageLoad>
<xp:div id="parent" />
</xp:view>
You can now calculate the variable nameOfCC.
Sven's solution looks absolutely brilliant but also kinda scary at the same time. Though I'm definitely going to play with that.
There is a more "out of the box" possibility that might work for you. the Dynamic Content Control of the extension library. My understanding is that it's designed for exactly this. You still need to predefine your custom controls (unlike Sven's Java injection) but the dynamic content control is better performing then trying to control all the rendering manually. There's a demo in the ext. library demo app but heres a snippet I've used:
<xe:dynamicContent
id="dynamic">
<xp:this.facets>
<xp:panel
xp:key="cat">
<xc:cc_form_category></xc:cc_form_category>
</xp:panel>
<xp:panel
xp:key="subCategory">
<xc:cc_form_subCategory></xc:cc_form_subCategory>
</xp:panel>
<xp:panel
xp:key="rental">
<xc:cc_form_rentalID></xc:cc_form_rentalID>
</xp:panel>
<xp:panel
xp:key="default">default content</xp:panel>
</xp:this.facets>
</xe:dynamicContent>
That's basically the setup of the custom controls. then I've been using some Client Side JavaScript when I want to show one or make a change.
XSP.showContent("#{id:dynamic}","cat")
XSP.partialRefreshPost('#{id:mainPanel}', {})
I think if it kinda like a Switch Statement really.
Hope that helps.

Using view.postscript in xpages

I have a xpage that use view.postscript like this:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:dojoModule name="dojox.widget.Toaster"></xp:dojoModule>
<xp:styleSheet href="/.ibmxspres/dojoroot/dojox/widget/Toaster/Toaster.css"></xp:styleSheet>
</xp:this.resources>
<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[var Toaster = function(id, msg, type, duration, pos) {
type = (type == null) ? "message" : type;
duration = (duration == null) ? "5000" : duration;
pos = (pos == null) ? "br-up" : pos;
var obj = dijit.byId(id);
obj.positionDirection = pos;
obj.setContent(msg, type, duration);
obj.show();
}]]></xp:this.value>
</xp:scriptBlock>
<xp:div id="toaster" themeId="toaster" dojoType="dojox.widget.Toaster"></xp:div>
<xp:button value="Toaster" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:view.postScript("Toaster('"+getComponent("toaster").getClientId(facesContext)+"', 'toaster message!')");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:view>
But when I click button,the toaster can't show.Why?
And I need a way to show the toaster called by ssjs function.
Thanks a lot.
As #Frantisek Kossuth said, you're re-submitting the page. When your refreshMode is complete it will re-render the page based on the server values. If you want to make client side changes and reflect them in your UI, you can't do a refresh of complete. Doing a partial refresh of the page on an element that won't change (like button1) should update your UI without rebuilding the page.
Similarly, why do you need this to be done through SSJS? Why not just use Client actions in your button activation? If this isn't part of a larger function call that you're not including, simply change the event code from Server to Client and call the Toaster function that way. This would also allow you to not have to submit anything, leaving submit="false" in your eventHandler to avoid unnecessary functionality.

Domino 9 / Dojo 1.8 - Date Time Picker without default value

I want a Date Time Picker control WITHOUT a default value. Doesn't seem to be possible anymore :-(
To reproduce, create a blank XPage and place a Date Time Picker control. Open the XPage in the browser and you will see that it defaults to today.
I didn't found any way to set the default to an empty value. I tried setting all properties/data/default to 0, null, empty string and so on - no luck.
I tried the data-dojo-probs attribute with value:'', this sets the default to 1970-1-1, but not to blank.
Any ideas?
This is a known issue in ND9, reported as SPR DEGN966F5V.
A work around for the issue (from SPR) is to modify the widget prototype in the postCreate function to prevent the value from being reset.
require([
"dojo/_base/lang",
"ibm/xsp/widget/layout/DateTextBox",
"ibm/xsp/widget/layout/TimeTextBox",
"ibm/xsp/widget/layout/DateTimeTextBox"
], function(lang, DateTextBox, TimeTextBox, DateTimeTextBox){
var a = {};
lang.mixin(a, {
postCreate: function(){
this.inherited(arguments);
}
});
DateTextBox.extend(a);
TimeTextBox.extend(a);
DateTimeTextBox.extend(a);
});
Here is an example of it working.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script clientSide="true">
<xp:this.contents><![CDATA[
require([
"dojo/_base/lang",
"ibm/xsp/widget/layout/DateTextBox"
], function(lang, DateTextBox){
var a = {};
lang.mixin(a, {
startup: function(){
this.inherited(arguments);
this.set("value", null);
}
});
DateTextBox.extend(a);
});
]]></xp:this.contents>
</xp:script>
</xp:this.resources>
<xp:inputText id="inputText1" value="#{sessionScope.inputText1}">
<xp:this.converter>
<xp:convertDateTime type="date" />
</xp:this.converter>
<xp:dateTimeHelper />
</xp:inputText>
</xp:view>
i dont know how it works in notes 9 but you could remove it with a CSJS like:
var field= dojo.byId('#{id:field}')
field.value ="";
hope it helps..
In applications I've previously applied a dojoType on the dateTimeHelper with this code:
<xp:dateTimeHelper id="dateTimeHelper1" dojoType="dijit.form.DateTextBox">
</xp:dateTimeHelper>
That was to address a problem in previous Domino versions where the page refreshed as soon as the picker was selected in certain flavours of IE.
This code seems to work fine on R9 without needing the workaround.

Resources