I am trying to use the extension library component Remote Service (xe:jsonRpcService). I got some hints from here and here. Basically I am trying to save a document using RPC. The problem is that the document gets saved but it does not save any fields present on the XPage. Below is the sample XPage code:
<?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:this.data>
<xp:dominoDocument var="document1" formName="Test"></xp:dominoDocument>
</xp:this.data>
<xe:jsonRpcService id="jsonRpcService1" serviceName="service">
<xe:this.methods>
<xe:remoteMethod name="saveDoc">
<xe:this.script><![CDATA[print(">> " + getComponent("inputText1").getValue());
document1.save();
return true;]]></xe:this.script>
</xe:remoteMethod>
</xe:this.methods>
</xe:jsonRpcService>
<xp:br></xp:br>
<xp:inputText id="inputText1" defaultValue="testValue" value="#{document1.testField}"></xp:inputText>
<xp:br></xp:br>
<xp:button value="Save" id="button1">
<xp:eventHandler event="onclick" submit="false">
<xp:this.script><![CDATA[var deferred = service.saveDoc();
deferred.addCallback(
function(result) {
alert(result);
}
);]]></xp:this.script>
</xp:eventHandler>
</xp:button>
</xp:view>
What I have done here is that, I created Remote Service (service) where I am saving the current document (document1). It saves the document but does not save the value in inputText1. Also, when I try to print the value of inputText1 it shows on console but it is not getting saved.
Is this the right way to do it? Or am I missing something here. Also what would be some scenarios where usage of xe:jsonRpcService would be justified?
There are (at least) two reasons for avoiding the use of a JSON-RPC for this type of operation:
This service component is designed to be as lightweight as possible, so, unlike standard events in XPages (which automatically post the entire HTML form), it sends only the data required to call the method, and receives only the data returned by the method. In your example, the method takes no arguments, so it's literally just sending enough information for the server to know what method to call; similarly, you're just returning a boolean value, so that's literally all that will be sent back. If you use your browser's development tools (i.e. Firebug or the built-in tools in Chrome) to inspect the network traffic, you'll see this reflected in the JSON packets that are sent in each direction. As a result, the server doesn't "know" anything that you don't explicitly "tell" it. So it's faster than a typical event because you're not posting anything that isn't explicitly related to the method you're calling... but you have to intentionally send everything that the method does need in order to run.
Another side effect of the component's focus on performance is that it skips the component tree serialization at the end of the JSF lifecycle. If you're keeping the current page in memory, that shouldn't be an issue, but if you're using the default option (save all pages to disk), the server will "forget" any changes that are made to the page during the method invocation. You can explicitly override this behavior on a case-by-case basis by directly telling the view root to serialize its state, but it's easy to forget that you have to do this manually, which typically causes a lot of frustration when you see indications server-side that it's doing what it's supposed to but the actual web page doesn't reflect that. It's best to just treat any RPC method as a "read-only" operation unless you're certain you'll always remember this odd side effect and understand how to circumvent it.
My advice is to think of JSON-RPC as "SOAP minus the stupid". Phrased more politely, it's conceptually identical to Web Services, but without all the complexity of Web Services. As such, these types of services are ideal for data operations that are useful within the context of the current page without being explicitly tied to the state of the current page.
Here are some examples of operations where a JSON-RPC method might be useful:
Informing a new user whether the user name they're choosing for their new account is already taken. Instead of binding a standard keyup event which would post the entire form, send only the value of the one field to the service, query it against the site registration records, and send back a boolean.
Live polling of related data that is prone to frequent updates. Suppose you're developing a CRM, and you want to display the stock price of the company whose account you're viewing. Using setInterval to periodically ask the RPC for the updated stock price, then doing manual client-side DOM manipulation if the price changes, again allows you to perform a somewhat complex operation with a minimum of network overhead.
This doesn't mean that you can't use an RPC for write operations... but for any operation that needs a complete, up-to-date context (i.e. current value of every field on the current page) to run correctly, a standard event handler is nearly always the better approach.
Related
I'm having a problem where I can't Open/Create documents using a specific XPage in my application.
For simplicity sake, I have 2 XPages, one called XSP1.xsp and one called XSP2.xsp.
Each, at the top of the page, has a declared dominoDocument:
<xp:this.data>
<xp:dominoDocument var="XSP1Doc" formName="XSP1">
</xp:dominoDocument>
</xp:this.data>
Each page has, amongst other things on it, buttons that I'm hiding using the following code:
<xp:button styleClass="btn btn-primary" value="Label" id="myButton">
<xp:this.rendered><![CDATA[#{javascript:XSP1Doc.getItemValueString("Status") == "myStatus"}]]></xp:this.rendered>
</xp:button>
When I open XSP1.xsp either with or without a ?documentId parameter in the querystring, it opens. When I open XSP2.xsp, it does not open, but rather tells me that:
Script interpreter error, line=1, col=9: 'XSP2Doc' is null
I cannot for the life of me determine what is different between the two pages. Even further and stranger, I can open documents created with XSP1.xsp in XSP2.xsp, and documents created in XSP2.xsp in XSP1.xsp, but I cannot open XSP2 with no document, or with a document created with the form used in XSP2.
I realize this may be confusing, please ask for clarification if necessary.
Edit:
I removed the buttons and the page loaded, and it loaded the back-end variables (assuming I had a documentId in the querystring). If I create a document using this page, then I can in fact open it. I still can't open the page if the buttons exist (which, if I have to test for null isn't a big deal, but I don't get why it would be different between two different pages.)
One thing I noticed is that the way I'm creating the XSP2 document, it doesn't have a $Revised field for some reason. Could this be causing this problem?
Edit 2: Well, I just copied XSP1, deleted everything in it, and recreated XSP2, and it works fine now. Still no idea what's different but at least I'm past it.
Interested in an answer so I'll leave this up.
Edit 3: Turns out I was doing something in a field that was recycling an object with the name XSP2 due to some shenanigans. I don't think this is a real issue. Not sure what do do here. Delete this question?
I have a simple button with the eventHandler:
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:openPage target="newDocument" name="/doc.xsp"></xp:openPage>
</xp:this.action>
</xp:eventHandler>
In lotusScript I would add a value to a field, before composing the form, using:
Call doc_new.ReplaceItemValue("txt_codformularmain","01")
....
....
Set uidoc = w.EditDocument ( True, doc_new )
I tried in the postNewDocument event of the doc.xsp
<xp:this.data>
<xp:dominoDocument var="Contr" formName="(fmFormularCIP)">
<xp:this.postNewDocument><![CDATA[#{javascript:Contr.replaceItemValue("txt_codformularmain", "01")}]]></xp:this.postNewDocument></xp:dominoDocument>
</xp:this.data>
I don't want every time the doc is created that value to be, let say, 01, BUT only when the doc. is composed by a specific button. Some other button will have the chance to add the 02 value for that field, and so on.
How can I achieve this in xpages development? Thanks for your time.
This is a little bit tricky in Web application development, because what you did in classical Notes development cannot be applied here. I had a lot of issues in this.
The classical scenario is that you have a Page X with some value (say txt_codformularmain will be 01). How do you decide this value "01"?
In some cases, this value is something you have in the page X. So you want to pass a specific value to the target page. One option could be using a query parameter (doc.xsp?myValue=01) and consume it on doc.xsp with param.myValue. Or, you might put a sessionScope variable before going to the target page and consume it by sessionScope.myValue.
It depends on what you need. Query parameter is somewhat safer, because the user might use the same button twice and sessionScope variable causes inconsistent behaviour. On the other hand, query parameter can be changed by the user, so you might have a breach in your application.
Some cases, you want to populate some values at the second page. Simple values (e.g. creation date, user names, etc.) can be populated with forms (i.e. compute value on load). Sometimes it would be more complicated (e.g. the department of the user where you have to lookup some views). For such cases, you need to use postNewDocument event at the target page.
After you have passed the value to the doc.xsp page, you will consume in the way you need. If it's not going to be edited on the page, you may again use postNewDocument event and set value by dataSourceName.replaceItemValue('itemName', param.myValue). If the value can be edited, you would use it as a default value on your field component.
One problem I have experienced: Once you set a value in postNewDocument, you may have problems to change it later, until the document data source saved. This is happening on specific cases. If you experience such a problem, you might use some editable field with readonly attribute. Just keep in mind :)
I am basically trying to understand xsp properties for XPages. I have referred multiple websites and found a small problem to understand the xsp.session.transient property.
This Blog( http://www.mindoo.com/web/blog.nsf/dx/17.07.2011101855KLEBRW.htm?opendocument&comments) states that:
"this flag means that unique session objects will be created per request to the server and discarded right after the request ended"
When I look into this blog (http://www.itwu-demo.net/web/itwublog.nsf/default.xsp?documentId=E42CD391498BDE9CC1257A770040B2EB) it says that setting it to true may result in loss of objects. I observed this side effect when I used tabbed navigation on my page. The code mentioned below would just refresh for the second hierarchical tab and would never go to the third hierarchy. If I set the property to false it works well. However, I would like to understand the reason of this behavior? Also, how exactly the property could be used for our benefit? Can anyone please throw some light here. Thanks.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:tabbedPanel id="tabbedPanel1">
<xp:tabPanel label="New Tab" id="tabPanel1">a</xp:tabPanel>
<xp:tabPanel label="New Tab2" id="tabPanel2">b<xp:br></xp:br>
<xp:tabbedPanel id="tabbedPanel2">
<xp:tabPanel label="New Tab" id="tabPanel3">c</xp:tabPanel>
<xp:tabPanel label="New Tab2" id="tabPanel4">d<xp:br></xp:br>
<xp:tabbedPanel id="tabbedPanel3">
<xp:tabPanel label="New Tab" id="tabPanel5">e</xp:tabPanel>
<xp:tabPanel label="New Tab2" id="tabPanel6">f</xp:tabPanel>
</xp:tabbedPanel>
</xp:tabPanel>
</xp:tabbedPanel>
</xp:tabPanel>
</xp:tabbedPanel>
</xp:view>
The xsp.session.transient property is documented in the XPages Portable Command Guide book.
The following is a summary of the property taken from the book:
By default, the XPages runtime is a stateful web application framework. A request for an XPage results in a degree of server-side processing that begins with creation or retrieval of a user session and ultimately ends with a rendering process that builds up the content for a response. During this server-side processing, a user session configuration object, along with all the controls on a requested XPage, have their respective properties and values serialized to disk and/or deserialized from disk. This is due to the inbuilt serialization mechanism of XPages that manages and provides the stateful characteristics of the XPages runtime. Based on application requirements, it might be beneficial from a performance and scalability perspective for an application not to participate in this serialization process, to optimize its level of participation. This aim of the xsp.session.transient property is to provide a way to control how user session objects are serialized between requests.
By default, the XPages runtime sets this property to false. Therefore, the serialization process includes all user session objects, but not the sessionScope object. This means that any XPages a given user requests are serialized/deserialized in association with the user session object over the life of that user session object. They are discarded along with the user session object when the overall user session timeout duration passes.
Alternatively, if this property is set to true, the XPages runtime automatically avoids serializing user session objects between XPage requests. It is important to note that
a user session object still is instantiated for a request, but it simply is not serialized between requests. This also means that properties and values of controls within requested XPages still participate in the serialization process—this ensures that an XPage can still provide a rich user experience for the scoped variables and partial execution of actions, for example. However, when a user navigates to another XPage, the associated stateful data for that XPage is discarded because the user session object is not serialized between requests. This feature is made available for use cases that require an extremely optimal level of performance tuning where server memory must be finely managed. Note that such use cases are those in which partial updates are applied against only the current page; full page refreshes cause the state to be discarded between requests. Therefore, the design and intent of the page require careful consideration to benefit from this feature.
As you read from the documentation, the state of objects are not kept between requests if xsp.session.transient is set to true.
I can highly recommend buying the XPages Portable Command Guide.
I ask this just to be clear here: I accidently set the createForm property to false. I then expected a link event that should open another page only not to function any more.
Is this the intended behavior of SSJS events e.g. in links when you disable form creation?
As Per mentions, all events require a form: if they are full refresh, then the page needs a form to post to in order to trigger the redirect; if they are partial refresh, the form determines the contents of the AJAX POST.
The XPages runtime includes support for a form component, but it is not included in the component palette (and it cannot be added via Designer Preferences), so the only way to add it to a page is by editing the source XML directly. For example:
<xp:form>
<xp:link id="exampleLink" text="Example Text">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:// event code}]]></xp:this.action>
</xp:eventHandler>
</xp:link>
</xp:form>
There are (at least) two reasons why this component is useful:
It can improve performance. If a page contains different areas that are functionally distinct -- in other words, an event in one portion of the page does not need to be aware of data in any other portions of the page -- then wrapping each section in a separate form component causes each event to only post data that is contained inside the same form as the component that triggers the event. Your question indicates that the link that broke when you set createForm to false should navigate the user to another page; it's likely, therefore, that this event doesn't need to know about any field values, because the user is leaving the current page anyway. If that's true, wrap this link in its own form, and any fields inside a separate form, and the link will perform marginally faster because the browser doesn't have to post any field data... just the identifier for the link itself.
It can provide style flexibility. It's common for a developer to receive, separately from the actual end user requirements, pre-determined CSS from a designer unfamiliar with Domino -- for instance, if the site design is outsourced, or must adhere to corporate style guidelines. This often becomes a source of tension when the designer has made certain assumptions that, at first glance, are incompatible with the markup that Domino generates. One of the most common examples of this is when the site contains search features, because most web developers will have one form for search and a separate sibling form for any other fields. This can increase the cost of a project when either the designer or the developer has to revise a stylesheet that the designer already developed to account for a single form tag surrounding all the content. In short, suppressing the default form element and explicitly specifying form components where needed makes it easier to adhere to externally imposed style guidelines.
So there are many use cases where it's actually preferable to use one or more form components on an XPage... just remember that all data and events must be inside a form -- whether the default form that usually surrounds all the content, or a manually included form component -- and that forms cannot be nested. You can add as many form components as you want, but they must be siblings. No form can contain another form.
Yes, because you are doing HTTP POST requests and they require a form.
You can convert your POST request link to a GET request link instead be removing the SSJS event and creating a "basic" link instead:
<xp:link escape="true" text="Link" id="link2" value="/somepage.xsp"></xp:link>
But if you need the SSJS logic, then you also need to have a form.
I hope someone can help me solve a very serious problem we face at the moment with a business critical application losing data when a user works in it.
This happens randomly - I have never reproduced this but the users are in the system a lot more than me.
A document is created with a load of fields on it, and there are 2 rich text fields. We're using Domino 8.5.3 - there are no extension lib controls in use. The document has workflow built in, and all validation is done by a SSJS function called from the data query save event. There is an insane amount of logging to the sessionscope.log and also this is (now) captured for each user in a notes document so I can review what they are doing.
Sometimes, a user gets to a workflow step where they have to fill in a Rich Text field and make a choice in a dropdown field, then they submit the document with a workflow button. When the workflow button is pressed (does a Full Update) some client side JS runs first
// Process any autogenerated submit listeners
if( XSP._processListeners ){ // Not sure if this is valid in all versions of XPages
XSP._processListeners( XSP.querySubmitListeners, document.forms[0].id );
}
(I added this to try and prevent the RTF fields losing their values after reading a blog but so far it's not working)
then the Server-side event runs and calls view.save() to trigger QS code (for validation) and PS code to run the workflow agent on the server.
95% of the time, this works fine.
5% of the time however, the page refreshes all the changes made, both to the RFT field (CKEditor) and the dropdown field are reloaded as they were previously, with no content. It's like the save hasn't happened, and the Full Update button has decided to work like a page refresh instead of a submit.
Under normal circumstances, the log shows that when a workflow button is pressed, the QuerySave code starts and returns True. Then the ID of the workflow button pressed is logged (so I can see which ones are being used when I am reviewing problems), then the PostSave code starts and finally returns true.
When there is a problem, The QuerySave event runs, returns true if the validation has passed, or false if it's failed, and then it stops. The ID of the workflow button is also logged. But the code should continue by calling the PostSave function if the QuerySave returns true - it doesn't even log that it's starting the PostSave function.
And to make matters worse, after the failure to call the PostSave code, the next thing that is logged is the beforePageLoad event running and this apparently reloads the page, which hasn't got the recent edits on it, and so the users loses all the information they have typed!
This has to be the most annoying problem I've ever encountered with XPages as I can find no reason why a successful QuerySave (or even a failure because mandatory fields weren't filled in) would cause the page to refresh like this and lose the content. Please please can someone help point me in the right direction??
It sounds as if in the 5% use cases, the document open for > 30mins and the XSP session is timing out - the submit causes the component tree to be re-created, and the now empty page returned back to the user. Try increasing the time out for the application to see if the issue goes away.
I would design the flow slightly different. In JSF/XPages validation belongs into validators, not into a QuerySave event. Also I'd rather use a submit for the buttons, so you don't need to trigger a view.save() in code. This does not interfere with JSF's sequence of things - but that's style not necessarily source of your problem.... idea about that:
As Jeremy I would as a first stop suspect a timeout, then the next stop is a fatal issue in your QuerySave event, that derails the runtime (for whatever reason). You can try something like this:
var qsResult = false;
// your code goes here, no return statements
// please and if you are happy
qsResult = true;
return qsResult;
The pessimistic approach would eventually tell you if something is wrong. Also: if there is an abort and your querySave just returns, then you might run in this trap
function noReturn() {return; } //nothing comes back!
noReturn() == true; --> false
noReturn() == false; --> false
noReturn() != false; --> true!!!!
What you need to check: what is your performance setting: serialize to disk, keep in memory or keep latest in memory? It could be you running foul of the way JavaScript libraries work.
A SSJS library is loaded whenever it is needed. Variables inside are initialized. A library is unloaded when memory conditions require it and all related variables are discarded. so if you rely on any variable in a JS Function that sits inside a SSJS library between calls you might or might not get the value back, which could describe your error condition. Stuff you want to keep should go into a scope (viewScope seems right here).
To make it a little more trickier:
When you use closures and first class functions these functions have access to the variables from the parent function, unless the library had been unloaded. Also functions (you could park them in a scope too) don't serialize (open flaw) so you need to be careful when putting them into a scope.
If your stuff is really complex you might be better off with a backing bean.
Did that help?
To create a managed bean (or more) check Per's article. Your validator would sit in a application bean:
<faces-config>
<managed-bean>
<managed-bean-name>workflowvalidator</managed-bean-name>
<managed-bean-class>com.company.WfValidator</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
</faces-config>
Inside you would use a map for the error messages
public Map<String,String> getErrorMessages() {
if (this.errorStrings == null) { // errorStrings implements the MAP interface
this.loadErrorDefinitions(); //Private method, loads from Domino
}
return this.errorStrings;
}
then you can use EL in the Error message string of your validators:
workflowvalidator.errorMessage("some-id");
this allows XPages to pick the right one directly in EL, which is faster than SSJS. You could then go and implement your own custom Java validator that talks to that bean (this would allow you bypass SSJS here). Other than the example I wouldn't put the notes code in it, but talk to your WfValidator class. To do that you need to get a handle to it in Java:
private WfValidator getValidatorBean() {
FacesContext fc = FacesContext.getCurrentInstance();
return (WfValidator) fc.getApplication()
.getVariableResolver()
.resolveVariable(fc, "workflowvalidator");
}
Using the resolver you get access to the loaded bean. Hope that helps!
My experience is that this problem is due to keeping page in memory. Sometimes for some reason the page gets wiped out of memory. I'm seeing this when there is a lot of partial refreshes with rather complex backend Java processing. This processing somehow seems to take the space from memory that is used by the XPage.
The problem might have been fixed in later releases but I'm seeing it at least in 8.5.2.
In your case I would figure out some other workaround for the CKEditor bug and use "Keep pages on disk" option. Or if you can upgrade to 9.0.1 it might fix both problems.