Posts Tagged With: Repeat Control

Simple dialog returning a value to your #XPages

Since I’m still new to XPages, I’m always finding things that are a challenge. Most of the time, I don’t know how big a challenge they are because doing things in XPages isn’t the same as doing them in Old Notes.

Business case

On the payment request in our procurement module, the user needs to enter the exchange rate between the local currency and the base currency for approval routing. Sometimes, the payment has already been made in local currency while the exchange rate has changed. They might only know the amounts in the two currencies, but didn’t record the exchange rate at the time.

PaymentRequest

Solution

In discussions, I suggested that it might not be difficult to simply have a popup that allowed the user to enter the amounts and return the value to the payment request in the UI.

Surprisingly to me, I was right. It’s not that hard. My challenge was that I decide to put this exchange rate computer into a custom control so that I’d be able to re-use it. That meant having to figure out how to have the dialog do a partial refresh on the payment request, in order to recompute the total amount in the base currency (USD).

Existing design considerations

Our procurement module was originally designed by the brilliant minds over at Teamwork Solutions, led by Scott Good. It’s very nice, but this wasn’t included in the original requirements gathering. (What percentage of actual requirements do get into the original requirements gathering?)

Each of those line items displayed in the payment request is a separate Notes document, so, in Old Notes terms, this would be like an embedded view, but since we’re in XPages, it’s a repeat. (I am learning to love repeats!) The total payable amounts are ‘computed fields’ in XPages, which is like a computed for display field in Old Notes. As such, they wouldn’t actually save anything to the payment request document. So, we have some non-displayed computed fields with some server-side Javsacript (SSJS) that aggregates all the local amounts from those view entries and puts it into the control on the XPage. As such, when the exchange rate changes, there is a partial refresh.

So, on our exchange rate control, we had a partial refresh for onchange. Simple control, bound to a field on our payment request document.

<xp:inputText id="exchangeRate" value="#{payDoc.Exchange_Rate}" style="text-align:right;width:70.0px;">
	<xp:this.converter>
		<xp:convertNumber type="number"></xp:convertNumber>
	</xp:this.converter>
	<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="paymentCostInfoPanel">
	</xp:eventHandler>
</xp:inputText>

Design

So, I created my exchangeRateComputer custom control. I decided to make the link part of the custom control so that implementing it on any other XPage would require the least work possible.  I decided to always set the control on the XPage to be named “exchangeRate”, so I don’t have to pass a string with the control name. As an added bonus, I wanted to fill out the local currency field for the user and just let them enter the final base currency amount they’d like to see.

ExchangeRateCalculator

I ran into one problem. I couldn’t get it to do the partial refresh from the exchangeRateComputer custom control. I’d tried some XSP.partialRefreshGet commands but was having problems and realized…. I might want the name of the element to be refreshed to be different on different XPages. Since passing the compositeData value into the XSP.getElementById seemed beyond my capabilities, I decided to simply defer all refreshes to the exchangeRate control itself. So, I ended up adding an onblur partial refresh. When the user clicks on the ‘Apply to payment request button’, it puts focus onto the exchangeRate control, then, closes the dialog. Closing the dialog blurs focus from the exchangeRate control and…. with an added onblur event, performs a partial refresh for me. (The onblur event is identical to the onchange, except for the name.)

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
	<xp:link escape="true" text="Compute Exchange Rate" id="computeLink" 
		style="margin-left:5px;">
		<xp:eventHandler event="onclick" submit="false">
			<xp:this.script><![CDATA[XSP.openDialog('#{id:computeDialog}');]]></xp:this.script>
		</xp:eventHandler>
	</xp:link>

	<xe:dialog id="computeDialog" title="Exchange rate calculator">
		<xp:table>
			<xp:tr>
				<xp:td>
				</xp:td>
				<xp:td>
					<xp:label id="instructions" value="Enter the amounts below to calculate an exchange rate">
					</xp:label>
				</xp:td>
			</xp:tr>
			<xp:tr>
				<xp:td style="width:150px;text-align:right;">
					<xp:label id="total_baseCurrencyLabel" value="Total Base Currency">
					</xp:label>
				</xp:td>
				<xp:td>
					<xp:inputText id="total_baseCurrency" style="width:100px;text-align:right;">
						<xp:this.converter>
							<xp:convertNumber type="number"></xp:convertNumber>
						</xp:this.converter>
						<xp:eventHandler event="onchange" submit="true"
							refreshMode="complete">
							<xp:this.action><![CDATA[#{javascript:var base = getComponent("total_baseCurrency").getValue();
var local = getComponent("total_localCurrency").getValue();
rate = local / base;
getComponent("rate").setValue(rate);}]]></xp:this.action>
						</xp:eventHandler>
					</xp:inputText>
				</xp:td>
			</xp:tr>
			<xp:tr>
				<xp:td style="text-align:right;">
					<xp:label id="total_localCurrencyLabel" value="Total Local Currency">
					</xp:label>
				</xp:td>
				<xp:td>
					<xp:inputText id="total_localCurrency"
						defaultValue="#{javascript:compositeData.localCurrency}"
						style="width:100px;text-align:right;">
						<xp:this.converter>
							<xp:convertNumber type="number"></xp:convertNumber>
						</xp:this.converter>
						<xp:this.validators>
							<xp:validateLongRange minimum="1"></xp:validateLongRange>
						</xp:this.validators>
						<xp:eventHandler event="onchange" submit="true"
							refreshMode="complete">
							<xp:this.action><![CDATA[#{javascript:var base = getComponent("total_baseCurrency").getValue();
var local = getComponent("total_localCurrency").getValue();
rate = local / base;
getComponent("rate").setValue(rate);}]]></xp:this.action>
						</xp:eventHandler>
					</xp:inputText>
				</xp:td>
			</xp:tr>
			<xp:tr>
				<xp:td style="text-align:right;">
					<xp:label id="rateLabel" value="Exchange Rate"></xp:label>
				</xp:td>
				<xp:td>
					<xp:text escape="true" id="rate"></xp:text>
				</xp:td>
			</xp:tr>
			<xp:tr>
				<xp:td>
				</xp:td>
				<xp:td>
					<xp:button value="Apply to payment request"
						id="copyButton">
						<xp:eventHandler event="onclick" submit="true"
							refreshMode="complete">
							<xp:this.script><![CDATA[var rate = XSP.getElementById("#{id:rate}"); 
XSP.getElementById("#{id:exchangeRate}").value = rate.innerHTML;
XSP.getElementById("#{id:exchangeRate}").focus();
XSP.closeDialog('#{id:computeDialog}');]]></xp:this.script>
						</xp:eventHandler>
					</xp:button>
					<xp:button value="Cancel" id="cancelButton">
						<xp:eventHandler event="onclick"
							submit="false">
							<xp:this.script><![CDATA[XSP.closeDialog('#{id:computeDialog}');]]></xp:this.script>
						</xp:eventHandler>
					</xp:button>
				</xp:td>
			</xp:tr>
		</xp:table>
	</xe:dialog>
</xp:view>

The last tweak to it is that my computed exchange rate in the dialog is just a computed field. I struggled a little until a dogpile search revealed that I needed to get that value as innerHTML. If you try getValue() on a computed field, you get bupkis, but if you grab the innerHTML, you’ve got the world in your hands.

It feels like that would be easier in Old Notes, but I hardly care any more. I have a new micro-solution in my toolbox and I expect to re-use this not only for other exchange rate computations, but to re-use the dialog and value-passing in many places. It’s all about building up your toolkit, right?

Categories: Client-Side Javascript, Old Notes, Server-Side Javascript, Xpages, XSP Functions | Tags: , , , , , , , , , , | 1 Comment

Nesting repeats to provide links to attachments in #XPages

Another of the challenges in my assessment XPage problem is that the evaluators will need to open the attachments that were submitted with each grant request. There are two issues here – first, that the attachments are uploaded to multiple separate documents from the grant request and, second, that there might be multiple attachments on each of those documents.

We chose to have the attachments on separate documents to reduce the size of the request document and lower the likelihood of truncation or replication issues. The users requesting these grants are all located in Africa and may well be in remote locations. So, bandwidth issues, even for the browser side, are of paramount concern for us. We’ve had some issues in the past on the Notes side with some truncations or with replication issues and found it better to keep documents smaller as a precaution.

So, for each request, there can be up to 4 documents with an unknown number of attachments associated with each request. As such, I stumbled upon the idea of using a repeat-within-a-repeat to display the links for each attachment.

First, I made sure to get only the attachment documents that I want by filtering my source view (as discussed previously):

<xp:dominoView viewName="AnnexLinks" var="linksView">
    <xp:this.keys><![CDATA[#{javascript:appDoc.getItemValueString("appUniqueID");}]]></xp:this.keys>
</xp:dominoView>

Then, I needed to build my repeat control. The code itself is actually very brief. My AnnexLinks view is very simple, with only three columns: AppUniqueID (my index, which is not the document unique ID, by the way), the AttachmentType and the URLs for the attachments.

The URLs column builds a unique URL for each attachment:

unid := @Text ( @DocumentUniqueID );
base := "0/" + unid + "/$file/";
base + @AttachmentNames

Since there can be multiple values in the URLs column for a single document, I have the link embedded inside a repeat control, using that value as an array to populate my link. I’m computing the filename back from the URL to use as the link label, but using the AttachmentType for the document in the outer repeat to display a label for the attachments.

<xp:repeat id="repeat3" rows="30" value="#{linksView}" var="rowData" indexVar="rowIndex">
    <xp:text escape="true" id="computedField1" value="#{rowData.AttachmentType}" style="font-weight:bold">
    </xp:text>
    <xp:label value=" files:" id="label2" style="font-weight:bold">
    </xp:label>
    <xp:repeat id="repeat4" rows="30" value="#{rowData.URLs}" var="url">
        <xp:link escape="true" id="link1" value="#{url}">
            <xp:this.text><![CDATA[#{javascript:@RightBack(url,"/") + " ";}]]></xp:this.text>
        </xp:link>
        <br />
    </xp:repeat>
</xp:repeat>

It’s shocking to me that something that ends up so small can accomplish what seemed like a huge tasks initially. On my notepad, I’ve got all kinds of SSJS written to get a handle to the attachment documents, then to try building an array of URLs, synchronized with a list of filenames to be used as labels. Fortunately, by poking around for URL info (h/t to Stephan Wissel – this will have to be updated for XPiNC and upgradeability) and referring back to my copy of Mastering XPages, I was able to sort out how to do it in just a few lines of simple, elegant code.

Categories: Xpages | Tags: , , , , | 1 Comment

Repeats and dynamic field bindings #xpages

In our purchase order module, we need to ask certain questions depending on the source selection method, competition type and total cost of the PO. These questions are likely to change over time and in between different instances of the database. So I have a view containing the questions, so that I can add questions dynamically to my XPage without needing to change the code.

To be honest, doing this in a regular Notes form would have required either putting every question on the form or putting a bunch of fields on the form for all the answer fields. Either method would be sloppy and wasteful. There could have been dozens of unnecessary answer fields hidden by hide-whens and constant revisions between various instances of the database leading to a configuration management/version control nightmare.

Doing it in a repeat control, with the source view filtered by values passed in an array, I can get dynamically selected questions with only the necessary fields being stored on the document.

In order to do this, I had to use all of my minimal XPages knowledge, ask several questions on Stack Overflow, re-read portions of Mastering Xpages and scour the help documentation. You, however, can simply follow my guidance and have this solution for your own.

The Question FormCompetition Question Form

I created a relatively simple Notes form, based on the ones supplied to us for keywords by Teamwork Solutions. The layout is clean and very readable, plus re-using it allows more consistency within our application. A number of the fields are used to determine when and where the question will display (Category, Competition type, Source selection method and Dollar ranges). The checkbox values are derived from keyword documents, to allow maximum flexibility. I’m allowing five field types for answers: Yes/No, Dialog Box, Text Box, Date and Date Range. I had tried Rich Text, but was having problems with losing the handle to the document when saving, and since it wasn’t really required in this implementation, dropped that for later review. Sometimes, the question has a related question to extract more details, so I allowed two questions to be placed on one question document to ensure they display together. Down at the bottom, I’ve got the fields to which the answer fields will be bound on the Notes document. One hazard to allowing the field to be named in this configuration document is that there might be duplicate use of a field name, by the risk level strikes me as low.

Setting up the Repeat

The way that I started to understand repeats was actually when I set up jQuery mobile access to some data after viewing Richard Sharpe’s webinar for TLCC on Building Your First Mobile Application Using XPages I was able to take the knowledge I acquired doing that and use it to link from one Requisition to many Purchase Orders as demonstrated on this blog.

The repeat itself is not that interesting, but, one of the technical tricks in the setup is. As I’ve started to learn XPages, I’ve gotten comfortable with variables stored in applicationScope (available to everyone), sessionScope (available throughout the current user’s session) and viewScope (available on the current page), but had no idea when one would ever use requestScope variables. It turns out that they can be very useful to us here, though we’re going to access them using a dataContext rather than labelling them as requestScope variables.

Since I want to take a value from each Question document to determine the field to bind to on the Purchase Order document, the syntax and re-use will be far easier if I can establish a variable for that field name used within each row of the repeat. This way, we can use Expression Language to refer to the field on the Purchase Order document, binding to it dynamically.

<xp:repeat id="repeat2" rows="30" var="rowData" style="width:700px" value="#{competitionQuestionView}">
    <xp:panel>
        <xp:this.dataContexts>
            <xp:dataContext var="fieldName">
                <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue ("FieldName");}]]></xp:this.value>
            </xp:dataContext>
        </xp:this.dataContexts>
        <--- insert code here --->
    </xp:panel>
</xp:repeat>

First, we want to display our question, with the layout handled by a table. We just grab the column value and display it.

<xp:table>
    <xp:tr>
       <xp:td style="width:200.0px;">
          <xp:label id="label1">
             <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("Question");}]]></xp:this.value>
          </xp:label>
       </xp:td>

Then, in the right-hand cell, we have the various possible answer fields. Note that since only one is rendered (the rendering formulas being mutually exclusive), it doesn’t matter that we bind to the field multiple times, since only one binding actually appears on the page. Tim Tripcony guided me to this solution in his answer on Stack Overflow. By using array syntax, we bind to the field on the poDoc using the value of our requestScope variable, fieldName. Remember, as a requestScope variable, it is only “alive” for the duration of this data retrieval to create the field binding. The binding formula ends up being very simple: #{poDoc[fieldName]}

Here’s a portion of the answer fields to give you the flavor…

<xp:td>
   <xp:inputText id="inputText1" style="padding-top:2px;width:400px">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Text Box"; }]]></xp:this.rendered>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:inputText>
   <xp:comboBox id="comboBox1" style="padding-top:2px;">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Dialog Box"; }]]></xp:this.rendered>
      <xp:selectItems>
         <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("DialogChoices")}]]></xp:this.value>
      </xp:selectItems>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:comboBox>
   <xp:inputText id="inputText2" style="padding-top:2px;text-align:left">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Date"; }]]></xp:this.rendered>
      <xp:dateTimeHelper id="dateTimeHelper1"></xp:dateTimeHelper>
      <xp:this.converter>
         <xp:convertDateTime type="date"></xp:convertDateTime>
      </xp:this.converter>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:inputText>
   <xp:radioGroup id="radioGroup1" style="padding-top:2px;width:86.0px">
      <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("FieldType") == "Yes/No"; }]]></xp:this.rendered>
      <xp:selectItem itemLabel="Yes" itemValue="Yes"></xp:selectItem>
      <xp:selectItem itemLabel="No" itemValue="No"></xp:selectItem>
      <xp:this.value><![CDATA[#{poDoc[fieldName]}]]></xp:this.value>
   </xp:radioGroup>
</xp:td>

Since my Question document also allows for the addition of a comment question after the non-TextBox answers, I have another line on the XPage to display those (Note that the dataContext for the commentFieldName requestScope variable has to be added to that panel inside the repeat control as well).

<xp:tr id="commentRow">
   <xp:this.rendered><![CDATA[#{javascript:rowData.getColumnValue("DisplayComment") == "Yes"; }]]></xp:this.rendered>
   <xp:td style="border-bottom:grey solid 0px;padding-top:2px;">
      <xp:label id="label3">
         <xp:this.value><![CDATA[#{javascript:rowData.getColumnValue("CommentLabel");}]]></xp:this.value>
      </xp:label>
   </xp:td>
   <xp:td id="commentCell" style="border-bottom:grey solid 0px;padding-top:2px;">
      <xp:inputText id="inputText3">
         <xp:this.value><![CDATA[#{poDoc[commentFieldName]}]]></xp:this.value>
      </xp:inputText>
   </xp:td>
</xp:tr>

In my next blog post, we’ll look at how we filtered the view data source in order to only display the questions we want on a particular purchase order.

If you don’t already look at XPages questions on Stack Overflow, you really ought to, and if you haven’t been keeping up with TLCC’s webinars, check out the series.

Categories: Server-Side Javascript, Xpages | Tags: , , , , , , , | 1 Comment

Creating one-to-many linking in Xpages

As I noted in my prior post, our new XPages application will need links from Requisitions to all Purchase Orders that are created from that Requisition. Now, in regular old Notes, I’d handle this just by plopping down a single-category embedded view, using the Requisition’s ID to find just the right Purchase Orders. Since embedded views already allow you to open the document underneath, my work would be done.

Fortunately, having found that Server Side Javascript is not frightfully different from LotusScript. You already have the basic understanding of the document object model from doing LotusScript, so it’s just a matter of figuring out the syntax, right?

Since I want to display a set of links to the associated Purchase Orders, I decided the cleanest way to do that was using a Repeat Control. Unfortunately, knowing the tool I needed didn’t resolve the problem by itself.

First, I had to build the values that would go into my Repeat Control. I thought about binding the view here, but our data doesn’t reside in the same database as the XPage designs. That means I’d have to compute which database I needed and, so far, that felt beyond my talents. Fortunately, the wonderful folks over at Teamwork Solutions provided us with a quick way to get a handle to the database, a getDb function. So, rather than trying to figure out how to squeeze that into a binding, I stuck to my LotusScript roots and got a NotesDocumentCollection from the view.

	<xp:this.value><![CDATA[#{javascript:
 		var db:NotesDatabase = getDb("tamisDb");
 		var poView = db.getView("PObyProcReqDocID");
 		var docCollection = poView.getAllDocumentsByKey(getComponent("docID1").getValue());
 		return docCollection;}]]>
 	</xp:this.value>

So, then, in my repeat, I can just refer to each document, pulling the values I want.

I always try to give my objects names that identify them clearly. My very first paid programming assignment was when I was pursuing my degree in Political Science, studying the Soviet Union. I worked at the National Superconducting Cyclotron Lab. It was a great place to work. Now, they don’t normally hire social studies majors to write code, but I hired in as a receptionist, then started working for the guys in Operations, walking around recording numbers on dials and checking on things. My boss knew I’d been a computer science major, so he guided me into re-writing their help document database. Every module in the database was named after a woman in his life. They were suitable names for him, because he knew the woman and the module names fit their personalities. I didn’t and I knew the next guy wouldn’t either, so I gave them all nice, functional names.

Why do I digress? Well, XPages is so eager to write code for you, that it assigns object names with object type and a number. I think a lot of people leave it that way, but when I’m in there, I need to assign names that fit. Sadly, I always end up with rowData as the object name for my repeats. I don’t know if changing from that had doomed my early repeats or if it’s just superstitious, but I leave it as rowData.

So, back to the completed code. Our ‘value’ section is there, identifying the NotesDocumentCollection we need, of all Purchase Orders documents that belong to the Requisition. For each one, we establish a link, labelling it by grabbing a field value, then using a Simple Action to open the pro_purchaseOrder XPage that displays Purchase Orders and using the UNID from the document selected as our rowData.

	<xp:repeat id="purchaseOrderLinkRepeat" rows="30" var="rowData" repeatControls="false" removeRepeat="true">
		<xp:this.value><![CDATA[#{javascript:
			var db:NotesDatabase = getDb("tamisDb");
			var poView = db.getView("PObyProcReqDocID")
			var docCollection = poView.getAllDocumentsByKey(getComponent("docID1").getValue());
			return docCollection;}]]>
		</xp:this.value>
		<xp:link id="poLink">
			<xp:this.text><![CDATA[#{javascript:"Purchase record: " + rowData.getItemValueString("TSWFNumber");}]]>
			</xp:this.text>
			<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
				<xp:this.action>
					<xp:openPage name="/pro_purchaseOrder.xsp" target="openDocument"
						documentId="#{javascript:rowData.getUniversalID();}">
					</xp:openPage>
				</xp:this.action>
			</xp:eventHandler>
		</xp:link>
	</xp:repeat>

Honestly, that little slice of code took me several days to get my head around. I wasn’t staring at the screen the whole time, but the stops-and-starts of trying one approach and then another were tremendous. Those hurdles truly point out that a seasoned developer might well accomplish in an hour what it takes a new developer (or one new to the language) an entire week to do. Now that I have it under my belt, my team and I will be reusing this all over the place. I hope you can as well.

Categories: Server-Side Javascript | Tags: , , , , | 1 Comment

Blog at WordPress.com.