JSON-RPC and handle to current document - xpages

Good day folks!
I have a call (CSJS) to a JSON-RPC service in my XPage to get the value of a viewScope variable. I use this call to cycle through my field names (dynamically) and validate them.
I'm having trouble figuring out how to get a handle to the datasource (document1 in my case) to which these fields are bound, from the RPC service.
Here's the call:
// Use the JSON-RPC Service to get the number of asset item rows
// from the viewScope variable
var deferred = myRPCService.getScopeVar();
deferred.addCallback(function(result){
alert(result); // <-- viewScope variable value
// get the dynamic field names for the asset items based on row #
var itemname = '';
for (var i = 1; i < result; i++) {
var itemname = 'replace'+(i < 10? '0':'')+ i
if (document1.getItemValueString(itemname) == ""){
// do this
} else{
// do that
}
}
});
I do get back the value of the viewScope variable from the RPC call but I can't get beyond that. Any pointers/examples would be appreciated.
Thanks,
Dan
PS: See my comment below ...

You don't say what calls the JSON-RPC event, so I will guess and say it is a button. You will have to have an additional serverside event set the viewScope variable to the value of document1.getItemValueString(itemname) which you can then retrieve in the RPC.
Your SSJS event code will look something like:
viewScope.result = document1.getItemValueString(itemname);
The additional event can be onmouseover (not recommended) or onkeypress. I assume you are using onclick clientside for this code, you can't use the onclick serverside because this code would need to run prior to your clientside. You will want the viewScope to contain fresh data, hence this suggestion. If the data doesn't need to be fresh, then set it on page load.
Another idea would be to call two RPC's. The first one can set the viewScope variable and the second can use it. The first one won't need a callback, and just calls a java or SSJS function that sets the viewScope. Personally I like this better than my first suggestion.

Related

Value in xp:confirm is not being updated after partial refresh. Why?

On an xpage I calculate the message for an xp:confirm control:
var arr = viewScope.get("attachmentsAll");
if(arr.length>0){
return "";
}else{
return arr.length + " Are you sure want to upload the file?";
}
the viewScope is being updated after the event has been executed. I check this via a xp:text and I notice that this assumption is true.
<xp:text escape="true" id="computedField1"><xp:this.value><![CDATA[#{javascript:var arr = viewScope.get("attachmentsAll")
return arr.length + " number?"}]]></xp:this.value></xp:text>
The xp:confirm and xp:text reside in the same panel that is being partially updated after the event.
Can anyone explain me why the value for the viewScope variable is updated in the xp:text control and not in the xp:confirm control?
The main idea of my answer to your previous question was to put hidden input with computed value. What if you try to use <xp:this.script> instead of xp:confirm, and get the confirmation message from that hidden input the same way?
Update
The reason and the alternative solution that does not require to make any changes to existing xpage
It came out that the back-end instance of xp:confirm evaluates the new message correctly. The new value is even being sent to the browser with the response to ajax request. But one of the functions of XSP client module is built so that it won't update the querySubmit listener function if there already exists one with the same name. So, we are stuck with the old confirmation function that contains the old message. There is a way to override this behavior without breaking any other features. I have tried this and it works for me.
Create a new JavaScript library (client-side). Add the code:
if (!XSP._pushListenerTuned) {
XSP.__pushListener = XSP._pushListener;
XSP._pushListener = function x__pl(listeners, formId, clientId, scriptId, listener) {
if (scriptId && scriptId.endsWith("_confirm")) {
for (var i = 0; i < listeners.length; i++) {
if (scriptId == listeners[i].scriptId) {
listeners.splice(i, 1);
}
}
listeners.push(new this._SubmitListener(formId, listener, clientId, scriptId));
} else {
XSP.__pushListener(listeners, formId, clientId, scriptId, listener);
}
}
XSP._pushListenerTuned = true;
}
Attach your new library as resource globally via theme or as page resource on required page. I guess placing the above code as scriptBlock on a required page should also work. Now, any xp:confirm component on any page (if you used theme resource), or on particular page and everywhere after visiting this page (if you used page resource or scriptBlock), will work as naturally expected.

NetSuite SuiteScript Client Side drop down validation

I have a custom form where, in a subtab, I have a dropdown that I need to find out the selected value on the client side after the user selects to perform some validation. I created the script and tied it to the on change event of the dropdown. I cannot seem to find the code to get the selected value on the client side. I have found code to read the value on the server side from a submit event. I need this on the client side on change. I am going to use the ID to look up a record and check a value on that record and if applicable popup a warning to the user. Either SS1 or SS2 is good, whatever would be better I have both available. Any help with this would be great. thanks
In a client script, you can use nlapiGetFieldValue() to retrieve the results.
function fieldchanged(type, name, linenum) {
if(name == 'dropdownid') {
var value = nlapiGetFieldValue('dropdownid');
alert(value);
}
}
OK the nlapiGetFieldValue, did not do the trick, what did was the following
function ValidateField( type, field, linenum ) {
if ( field === 'recordid' ) {
var vendorid = nlapiGetCurrentLineItemValue(type,field,linenum);
var vendorRecord = nlapiLoadRecord('vendor',vendorid);
}
return true;
}
thanks for your help

Docuemt postopen event not operating on profile document

I need to save serial number of the document in a profile document and here is a code of action Execute Script:
if (document1.isNewNote()){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","")
var lnm=pdoc.getItemValue("lastNumber")[0];
var inputText6:com.ibm.xsp.component.xp.XspInputText = getComponent("inputText6");
inputText6.setValue(lnm);
pdoc.replaceItemValue("lastNumber",lnm);
pdoc.save();
}
This code is not opening profile document at all. Any thing wrong in the code?
"LastNumber" is the name of the form used to create Profile Document ?
this profile document already exist ?
there are no reader fields in this profile document ?
you have an error on this line : var pdoc:NotesDocument=database.getProfileDocument("LastNumber","") ?
or you have debug it and see that pdoc is null ?
instead of pdoc.getItemValue("lastNumber")[0] you can use pdoc.getItemValueInteger("lastNumber") to get a typed result
I supposed that this field contains a number and you want to increment it
instead of using inputText field you can set value directly with document1.setValue("NumberField", lnm);
I second the caution Per is suggesting. Profile documents can be a beast. You should abstract access to the "next number" into a SSJS function call. Btw. in your code snippet you don't actually increment the last number. Also: if your input text control is bound, go after the data source, not the UI.
A crude way (I would use a managed application bean for better isolation) for a better function could be this:
if(document1.isNewNote() {
document1.setValue("DocumentNumber",applicationTools.getNextNumber());
}
Then in a SSJS library you would have:
var applicationTools = {
"getNextNumber" : function() {
synchronized(applicationScope){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","");
if (!applicationScope.lastNumber) {
applicationScope.lastNumber = pdoc.getItemValueInteger("lastNumber");
}
applicationScope.lastNumber++;
pdoc.replaceItemValue("lastNumber",applicationScope.lastNumber);
pdoc.save(); //Make sure pdoc is writeable by ALL!!!!
pdoc.recycle();
return applicationScope.lastNumber;
}
},
"someOtherUtility" : function(nameToLookup, departments) {
// more stuff here
}
}
Which, in some way has been asked before, but not for a profile field. Someone still could simply go after the applicationScope.lastNumber variable, which is one of the reasons why I rather use a bean. The other: you could do the saving asynchronously, so it would be faster.
Note: in any case the number generation only works when you have a non-replicating database. But abstracting the function opens the possibility to replace fetching the number from the profile with a call to a central number generator ... or any other mechanism ... without changing your form again.

Is it better to reuse or recreate a reactive source in Meteor

I have a template called 'contacts'. Inside is an #each which renders the template 'contact'. The user can press the 'edit' button which sets a session variable with the mongo id of the edited row. The row then reactively re-renders into "edit" mode.
Template.contact.viewEditing = function() {
return Session.get("contactViewEditingId") === this._id;
}
The html uses the viewEditing helper a few times, for instance:
{{#if viewEditing}}
<div class="panel-heading">EDITING!</div>
{{/if}}
I need to bind some javascript in the .rendered(). I would like to check again if we are editing. I can think of 2 options:
Should I call Template.content.viewEditing() inside my template.rendered() ? Does this save on reactivity calculations?
Or should I just copy pasta the if statement. This option seems to violate DRY.
Option 1:
Template.contact.rendered = function() {
if( Template.contact.viewEditing.call(this.data) ) {
// Bind some fancy jQuery
bindEditInPlace(this.data);
}
}
Option 2:
Template.contact.rendered = function() {
if( Session.get("contactViewEditingId") === this._id ) {
// Bind some fancy jQuery
bindEditInPlace(this.data);
}
}
I think that putting {{viewEditing}} multiple times in your template doesn't "cost" anything extra. So logically I would think that using this helper elsewhere is better. Maybe I need more help understanding reactivity calculations. Thanks!
Helpers are run inside a Deps.Computation, which means that every time a reactive variable is referenced and modified in a helper, it will re-run.
Template.rendered is a callback that runs each time the template is re-rendered (which usually happens when a helper in the template is re-run reactively), but it is not itself a reactive computation.
So it doesn't matter using either the template helper or copy-pasting its code inside your rendered callback : both ways won't trigger a reactive computation invalidation because we are not inside one.
As far as DRY is concerned, you could refactor your code like this :
function isContactViewEditing(contactView){
return Session.equals("contactViewEditingId",contactView._id);
}
Template.contact.helpers({
viewEditing:isContactViewEditing
});
Template.contact.rendered=function(){
if(isContactViewEditing(this.data)){
//
}
};
I think saimeunt's answer is correct, especially if you have more complex logic in the function which you don't want to replicate.
Create a local function which you can re-use in both the helper and the .rendered callback.
If you had a case where you wanted to use a reactive source minus the reactivity you could make it non-reactive by wrapping it in a Deps.nonreactive function likes so:
Deps.nonreactive(function(){
//Reactive stuff here
});
Regarding reactivity concerns, pay attention to his change from using Session.get to Session.equals. Session.get will cause any reactive computation it is used in to re-calculate on every change of the session variable. So if you use this helper in multiple places with different ids, and you change the session variable, every single one will re-calculate and re-render the templates they are used in. Session.equals only invalidates a computation when the equality changes. So changing the session variable from one non-equal id to another non-equal id will not cause the computation/template to re-run when you use Session.equals.
For your specific example where the helper is only returning the result of a Session.equals you might consider creating a global handlebars helper that can do this for you with any session variable and any value. Like the following.
Handlebars.registerHelper('sessionEquals', function (key, value) {
return Session.equals(key, value);
});
Then in the template use it like so:
{{#if sessionEquals 'contactViewEditingId' _id}}
<div class="panel-heading">EDITING!</div>
{{/if}}
In the template when rendering an item that is editable add a unique class name to mark the item as editable. Then in your Template.rendered callback when binding the javascript use a selector which looks for that class and only binds to elements with that special class.

When no data is returned from database

I am intiating a loading panel in init method and hiding it in ReturnDataPayload event.This is working perfectly when data Table has got some values in it.But when there is no data returned from database , the control is not going to returnDataPayLoad event.Please help me in finding an event which will be fired even when the response doesn't have any data or tell me a way to hide the loading panel.
If you want a custom behavior, use DataSource's sendRequest method of the dataTable's dataSource
(function() {
var YdataTable = YAHOO.widget.DataTable,
YdataSource = YAHOO.util.DataSource;
var settings = {
container:"<DATATABLE_CONTAINER_GOES_HERE>",
source:"<URL_TO_RETRIEVE_YOUR_DATA>",
columnSettings:[
{key:"id", label:"Id"}
],
dataSourceSettings:{
responseType:YdataSource.TYPE_JSON,
responseSchema:{
resultsList:"rs",
fields:[
{key:"id"}
]
}
},
dataTableSettings:{
initialLoad:false
}
}
var dataTable = new YdataTable(
settings.container,
settings.columnSettings,
new YdataSource(
settings.source,
settings.dataSourceSettings),
settings.dataTableSettings);
})();
keep in mind No matter which source is your data: XML, JSON, JavaScript object, TEXT, you always will get your data in a unified way through DataSource's sendRequest method. So when you want to retrieve your data and, at the same time, add custom behavior, use it
dataTable.getDataSource().sendRequest(null, {
success:function(request, response, payload) {
if(response.results.length == 0) {
// No data returned
// Do what you want right here
// You can, for instance, hide the dataTable by calling this.setStyle("display", "none");
} else {
// Some data returned
// If you want to use default the DataTable behavior, just call
this.onDataReturnInitializeTable(request, response, payload);
}
},
scope:dataTable,
argument:dataTable.getState()
});
The properties of the response are
results (Array): Your source of data in a unified way. For each object in the results Array, There is a property according to responseSchema's fields property. Notice i use response.results.length to verify if some data has been returned
error (Boolean): Indicates data error
cached (Boolean): Indicates cached response
meta (Object): Schema-parsed meta data
On the YUI dataTable page, look for Loading data at runtime to see some built-in functions provided by YUI dataTable
I hope it can be useful and feel free to ask for help for anything else you want about YUI. See a demo page of nice features of YUI dataTable

Resources