1

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>
Eric McCormick
  • 2,716
  • 2
  • 19
  • 37
D.Bugger
  • 2,300
  • 15
  • 19
  • Can you post the code of your page. – Patrick Sawyer Oct 16 '14 at 14:00
  • Very difficult. In fact, there are the XPage, then ccForm, that contains ccFlatForm, which contains ccFields and ccSubforms. I also use quite a lot of library functions. I can see if I can extract some of the more relevant XML parts of these elements. – D.Bugger Oct 16 '14 at 14:09
  • I'm sorry, it's too complex. And building a working example will take too much time. The heart of the matter, in short: XPage with one main document, one button that triggers saving the document, and several subforms with each their own temporary NotesDocument. These subform documents are populated upon creation from the main document. When the user modifies the data in a subform, this data should be moved back to the main document before the main document is saved. How? – D.Bugger Oct 16 '14 at 14:35
  • It sounds overly complex. Why use a temporary document to pass values between custom controls? You could use viewScope vars or even the data source itself. – Per Henrik Lausten Oct 16 '14 at 15:01
  • It's complex indeed, but how would you know if it's overly complex? ;-) I could indeed use the main document, and then I'd have to keep track of which fields are used in the subform, for they need special treatment which I would have liked to happen in the ccSubform control itself. I'm very reluctant to use viewScope vars, with lots of 'independent' custom controls flying around... I'd love to have persistent controlScope vars, by the way... – D.Bugger Oct 16 '14 at 15:12
  • Fair point wrt. "overly" :-D – Per Henrik Lausten Oct 16 '14 at 15:37
  • I think I missed the point (once again, I'm afraid) with XPages: one is supposed to bind fields to NotesDocument fields when the page is loading, so that all data can be saved automatically in later stages. I suppose that's the reason why I "lose" the compositeData and the dataSource during the querySaveDocument. – D.Bugger Oct 18 '14 at 09:52
  • Edit not appreciated, the result is crap, very distracting and completely besides the point. Please undo. – D.Bugger Mar 30 '16 at 09:17

3 Answers3

2

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?

stwissel
  • 20,110
  • 6
  • 54
  • 101
  • Hm, well, until now I managed to keep myself bean-free *shame on me*... I'll finally have to read up on it, so it seems. Thanks! – D.Bugger Oct 18 '14 at 09:46
  • Rofl. You make missing out on the power of your platform to be a good thing. All that temp documents and the copy back and forth sounds rather messy. You probably could muddle through with one datasource and parameters, but a backing bean looks like the cleanest shot you have – stwissel Oct 18 '14 at 10:19
  • Sigh... Please bear with me... ;-) – D.Bugger Oct 18 '14 at 16:13
  • It is easier than it sounds – stwissel Oct 19 '14 at 03:09
  • Bean planted! I had some initial implementation issues, like how to bind with it from Expression Language, but another question solved that one as well (clue: "implements DataObject"). Thanks!! – D.Bugger Oct 29 '14 at 17:46
  • Binding: have a getBla() and setBla(String blub) method. Then binding is yourBean.bla all case sensitive – stwissel Oct 30 '14 at 01:15
1

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).

Paul Stephen Withers
  • 15,699
  • 1
  • 15
  • 33
  • The reason data is copied from the main doc to a temp doc is that several multi-value fields are copied to many single value fields. Example: there are some invoice fields in the main doc, say NItems, Article, and Price. Suppose there are 2 invlice lines, then these fields are copied to the temp doc as NItems$1, NItems$2, Article$1, Article$2, etc. Each field is displayed on the screen using our own ccField control, with many more options than the standard ones. To simplify things, I'd prefer to reuse ccField which is (currently) designed to use a document datasource. Running out of space...:P – D.Bugger Oct 17 '14 at 16:17
  • Bind to the main doc's fields... Hm, so create a binding dynamically in some hidden control at load time to a field in the main doc. Hmmm, got to think about that one... For I suppose that's the reason why I don't have compositeData.dataSource vailable during the querySaveDocument of a temp doc. – D.Bugger Oct 17 '14 at 16:58
0

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

Howard
  • 1,503
  • 7
  • 16
  • I'd like to know how I could do that, indeed, but what I found out is that the QuerySave event of a temp. document isn't triggered when I trigger the main document. The Save button explicitly saves the main document (a Simple Action is used). By the way, those temp.documents shouldn't be saved anyway, but I need the data. I prefer the temp. documents to do the moving of the data, initiating the move from the main document requires a lot of additional administration. I'll post (part of) the ccSubform control. – D.Bugger Oct 16 '14 at 14:55
  • Not sure a simple action will do it, try doing a save() which saves all data sources. In your querysave you can return false to not save each temp. doc. For that matter, why use temp docs? Just setup controls and grab the data from the controls to write to whatever doc. you want to save. – Howard Oct 16 '14 at 18:09