I need to provide a number of web services to supply data from a Notes application to an external website.
I have created an XAgent with multiple custom REST service controls. Each control has its own "pathInfo" property and code defined in "doGet" to return a JSON object containing the relevant data required by the website.
This all works well and the correct data is returned by calls to each web service. However, having added some debug messages to each "doGet" I see that all services on the XPage are being triggered by a call to any one of them.
I have a couple of additional REST service controls on the same XPage with code defined in "doPost", used by the website to create records in the Notes database. This also works well with the desired results, but the debug messages show that when a call to one of these services is made all of the "get" services are also triggered.
Any idea what is going on here? I could create a separate XAgent for each REST service control, but it seems overkill if it's not necessary.
UPDATE
Here is a cut down version of the XPage with just two of the "get" services. It doesn't matter which of these services is called, the log messages show that getHospitals is triggered first followed by getCustomerTypes. However, the correct resultset is always returned.
<?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"
rendered="false">
<xp:this.beforePageLoad><![CDATA[#{javascript:var vwSettings:NotesView = database.getView("vwSettings");
var docSettings:NotesDocument = vwSettings.getFirstDocument();
sessionScope.put("App_DbPath", docSettings.getItemValueString("App_DbPath"));
sessionScope.put("WR_DbPath", docSettings.getItemValueString("WR_DbPath"));
sessionScope.put("logActivity", true);}]]></xp:this.beforePageLoad>
<xp:this.resources>
<xp:script src="/Utils.jss" clientSide="false"></xp:script>
<xp:script src="/OpenLogXPages.jss" clientSide="false"></xp:script>
</xp:this.resources>
<xe:restService id="restService1" pathInfo="getHospitals">
<xe:this.service>
<xe:customRestService contentType="application/json"
requestContentType="application/json" requestVar="hospital">
<xe:this.doGet><![CDATA[${javascript:try {
if (sessionScope.logActivity == true) {
log.logEvent("getHospitals");
}
var dbApp:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), sessionScope.App_DbPath);
var vwHospitals:NotesView = dbApp.getView("vhospitals.by.name");
var vecHospitals:NotesViewEntryCollection = vwHospitals.getAllEntries();
var eHospital:NotesViewEntry = vecHospitals.getFirstEntry()
var arrHospitals = new Array();
while (eHospital != null) {
var hospital = {};
hospital["Name"] = eHospital.getColumnValues()[0];
hospital["HCode"] = eHospital.getColumnValues()[1];
arrHospitals.push(hospital);
eHospital = vecHospitals.getNextEntry(eHospital);
}
log.logEvent("getHospitals - END");
return toJson(arrHospitals);
} catch(e) {
log.logError(e.toString());
}}]]></xe:this.doGet>
</xe:customRestService>
</xe:this.service>
</xe:restService>
<xe:restService id="restService2" pathInfo="getCustomerTypes">
<xe:this.service>
<xe:customRestService contentType="application/json"
requestContentType="application/json">
<xe:this.doGet><![CDATA[${javascript:try {
if (sessionScope.logActivity == true) {
log.logEvent("getCustomerTypes");
}
var dbApp:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), sessionScope.App_DbPath);
var vw:NotesView = dbApp.getView("vkeywords");
var vec:NotesViewEntryCollection = vw.getAllEntriesByKey("Customer Type");
var e:NotesViewEntry = vec.getFirstEntry()
var arrItems = new Array();
while (e != null) {
var item = {};
item["CustomerType"] = e.getColumnValues()[1];
arrItems.push(item);
e = vec.getNextEntry(e);
}
log.logEvent("getCustomerTypes - END");
return toJson(arrItems);
} catch(e) {
log.logError(e.toString());
}}]]></xe:this.doGet>
</xe:customRestService>
</xe:this.service>
</xe:restService>
And the services are called using
https://<<domain name>>/<<path name>>/ws.nsf/test.xsp/getHospitals
and
https://<<domain name>>/<<path name>>/ws.nsf/test.xsp/getCustomerTypes
The problem was that the "doGet" properties were set to "compute on page load" rather than "compute dynamically". Not sure how that crept in - probably a copy and paste error.
Related
We are batch creating Views and Dependent Views (currently only ViewPlans) via the Revit API in Revit 2019, 2020, and 2022. We are seeing the following inconsistent results in all three Revit versions.
Below is a simplified code snippet. On many but not all groups of three Duplicate Views, some Shared Parameters that are set in the View Template are present in the parent view, and child duplicate views 'B' and 'C' but not child duplicate view 'A'.
using (var transactionGroup = new TransactionGroup(document, "Create views and set parameter values"))
{
transactionGroup.Start();
var sectors = new["A", "B", "C"];
var viewLookup = new Dictionary<string, ElementId>();
using (var makeViewsTransaction = new Transaction(document, "Create views"))
{
makeViewsTransaction.Start();
ViewPlan mainPlan = ViewPlan.Create(document, viewFamilyTypeId, levelId);
mainPlan.Name = "Plan_Name_Sector";
viewLookup.Add(mainPlan.Name, mainPlan.Id);
if (mainPlan.CanViewBeDuplicated(ViewDuplicateOption.AsDependent))
{
foreach (string sector in sectors)
{
string viewName = mainPlan.Name + "_" + sector;
var childPlanId = mainPlan.Duplicate(ViewDuplicateOption.AsDependent);
var childPlan = document.GetElement(childPlanId) as ViewPlan;
childPlan.Name = viewName;
viewLookup.Add(childPlan.Name, childPlan.Id);
}
}
makeViewsTransaction.Commit();
}
using (var editViewsTransaction = new Transaction(document, "Set view parameters"))
{
editViewsTransaction.Start();
foreach (var entry in viewLookup)
{
var view = document.GetElement(entry.Value) as Autodesk.Revit.DB.View;
if (paramSet.ScopeBoxId.IntegerValue != ActionBroker.EmptyElementId.IntegerValue)
{
view.get_Parameter(BuiltInParameter.VIEWER_VOLUME_OF_INTEREST_CROP).Set(scopeBoxId);
}
}
editViewsTransaction.Commit();
}
transactionGroup.Assimilate();
}
Screenshot of a result sample showing the missing parameter values.
Has anyone else experienced this?
It seems to me like a pretty straight-forward use of the Revit API, but perhaps the transaction group is introducing problems? I'm not sure what we could/should do differently to get more consistent results. Any suggestions?
this is my sample coding for sending email with attachment. The content of word is not send correctly.
attachment is using "file upload"
// write mail
var setdoc:NotesDocument = database.getProfileDocument("System Setting", "");
var server = setdoc.getItemValueString("MailDBSvr");
var dname = setdoc.getItemValueString("MailDbPath");
var web = setdoc.getItemValueString("InternetAddress");
var maildoc:NotesDocument = database.createDocument();
maildoc.replaceItemValue("Form", "Memo");
maildoc.replaceItemValue("Subject","Test Send Mail");
session.setConvertMime(false);
var stream = session.createStream();
stream.writeText("<html><body>");
stream.writeText("<p>Dear " + "[person]" + ",</p>");
stream.writeText("<p>Attached item is an image of </p>");
stream.writeText("<p> ***THIS IS AN AUTOMATED MESSAGE - PLEASE DO NOT REPLY DIRECTLY TO THIS EMAIL***</p>");
stream.writeText("</body></html>");
var body = maildoc.createMIMEEntity("Body");
var Att= document1.getDocument(true).getFirstItem("Attachment");
maildoc.copyItem(Att,"Body") // try adding an item
body.setContentFromText(stream, "text/html;charset=UTF-8", 1725);
stream.close();
maildoc.closeMIMEEntities(true);
session.setConvertMime(true);
maildoc.replaceItemValue("SendTo","TestUser1#devsvr1.pcs.com.my");
maildoc.send();
The result come out is only the attachment field without any text value inside. I not sure which part of it is wrong.
sample Result screen:
Recommended Mime style
var HTMLMail = function() {
...
}
var mail = new HTMLMail();
mail.setTo("TestUser1#devsvr1.pcs.com.my")
//mail.addFileAttachment(result);
mail.setSubject("Test Send Mail");
mail.addHTML("<h1>Hi!</h1>");
mail.addHTML("<table><tbody><tr><td>contents in a table here</td></tr></tbody></table>");
mail.send();
IBM Notes/Domino has 2 ways to show 'pretty words and pictures'
RichText
MIME
You should only use one or the other, but here you are actually mixing both the different types.
Above, when you copy the Attachment Item, you are actually adding the first 'Body' item, you can see it's type 'RichText'.
Then we you createMimeEntity you are creating the second 'Body' item, and it's type is 'MimePart' (it is probably showing second because the Mime is not applied until CloseMimeEntities)
So now you have 2 Body items with different parts. You are seeing the 'RichText' attachment in Notes because it is first listed item.
What you actually need to do is create the correct multipart mime structure.
If you want a bit more information about mime, I a blog post on my site which explains it a little bit more, including some info about the correct mime structure.
http://camerongregor.com/2016/04/21/webmail-ui-you-must-learn-about-mime/
If you haven't seen it yet there is an XSnippet by Mark Leusink which has a demo of creating a mime email using SSJS. I don't use this myself as I don't use SSJS but it might be useful to you as it should handle most of this mime manipulation for you.
https://openntf.org/XSnippets.nsf/snippet.xsp?id=create-html-mails-in-ssjs-using-mime
My coding with reference using rich-text style :
var setdoc:NotesDocument = database.getProfileDocument("System Setting", "");
var server = setdoc.getItemValueString("MailDBSvr");
var dname = setdoc.getItemValueString("MailDbPath");
var web = setdoc.getItemValueString("InternetAddress");
var maildoc:NotesDocument = database.createDocument();
maildoc.replaceItemValue("Form", "Memo");
maildoc.replaceItemValue("Subject","Test Send Mail");
session.setConvertMime(false);
var stream = session.createStream();
stream.writeText("<html><body>");
stream.writeText("<p>Dear <b>" + "person" + "</b>,</p>");
stream.writeText("<p> ***THIS IS AN AUTOMATED MESSAGE - PLEASE DO NOT REPLY DIRECTLY TO THIS EMAIL***</p>");
stream.writeText("</body></html>");
var tmpDoc:NotesDocument = maildoc.getParentDatabase().createDocument();
var mime:NotesMIMEEntity = tmpDoc.createMIMEEntity("myBody");
var addRt:NotesMIMEEntity = maildoc.getMIMEEntity("addBody");
var Att:NotesRichTextItem = document1.getDocument(true).getFirstItem("Attachment");
if(addRt != null && #Length(addRt.getContentAsText().trim()) > 28) {
stream.writeText('<font size="2" face="sans-serif">'); // Enforce "simiilar" font type/size...
stream.writeText(addRt.getContentAsText());
stream.writeText('</font>');
}
mime.setContentFromText(stream, "text/html", NotesMIMEEntity.ENC_NONE);
var prevMime = session.isConvertMime();
session.setConvertMime(true);
tmpDoc.closeMIMEEntities(true,"myBody");
var rt:NotesRichTextItem = maildoc.getFirstItem("Body");
var body = null;
if (rt != null) {
body = rt.copyItemToDocument(tmpDoc,"Body");
rt.remove();
}
rt = maildoc.createRichTextItem("Body");
var rtMime:NotesRichTextItem = tmpDoc.getFirstItem("myBody");
rt.appendRTItem(rtMime);
if(Att != null) {
if(addRt == null) rt.addNewLine(1);
rt.appendRTItem(Att);
Att.remove();
}
if(body != null) {
rt.addNewLine(2);
rt.appendRTItem(body);
}
if(addRt != null) {
addRt.remove();
addRt.recycle();
}
stream.close();
maildoc.closeMIMEEntities(true);
session.setConvertMime(true);
maildoc.replaceItemValue("SendTo","TestUser1#devsvr1.pcs.com.my");
maildoc.send();
case : Update field after select the customer name:
setting : 1 setting view that consist of database path :
DbServer: ServerOne/pcs
Directory: office
Database name : Customer.nsf
this xpages have a datasource inside, it call "document1"
// get the database path :
var vw3:NotesView=database.getView("Setting Path");
var doc3:NotesDocument=vw3.getFirstDocument();
var server:string = doc3.getItemValueString("DbServer");
var DName:string=doc3.getItemValueString("DbName");
var Directory:string=doc3.getItemValueString("Directory");
var DBName:string= Directory+"\\" +DName;
var db:NotesDatabase = session.getDatabase(server, DBName, false);
var vw:NotesView = db.getView("All Customer");
var doc:NotesDocument=vw.getDocumentByKey(document1.getValue("Customer"),true);
if (doc !=null) {
document1.setValue("Contact", doc.getItemValueString("Contact"));
document1.setValue("Telephone", doc.getItemValueString("Phone"));
document1.setValue("Fax", doc.getItemValueString("Fax"));
document1.setValue("Email", doc.getItemValueString("Email"));
}
Problem :
The field doesn't update and get the value from "customer" database.
I see a series of problems in your code:
Bind your input field to a scope variable, not to the document itself. It is a search string in the beginning, not part of the new document.
You don't check for the case that the customer wasn't found, so you never know if that was the issue.
I would rather use an URL and resolve instead of server / path / database (but that's a little style
So something like (typed off my head, will contain typos):
var vw3:NotesView=database.getView("Setting Path");
var vwe3 = vw3.getFirstEntry();
var db = session.resolve(vwe.entries[0]);
var vw:NotesView = db.getView("All Customer");
var doc:NotesDocument=vw.getDocumentByKey(viewScope.customer,true);
if (doc !=null) {
viewScope.result = doc.getUniversalID();
document1.setValue("Contact", doc.getItemValueString("Contact"));
document1.setValue("Telephone", doc.getItemValueString("Phone"));
document1.setValue("Fax", doc.getItemValueString("Fax"));
document1.setValue("Email", doc.getItemValueString("Email"));
doc.recycle()l
} else {
viewScope.result = "Not found!";
}
// ADD recycle() calls here!!!
Bind a display only field to viewScope.result, so you have a better idea what is happening. Your view must be sorted and indexed by customer name.
Of course you could use the OpenNTF dialog list control instead.
I have a placemark in a KML that looks something like this
<Placemark>
<id>test345</id>
<name>Images from KML file</name>
<ExtendedData>
<Data name="type">
<value>images</value>
</Data>
</ExtendedData>
<Point>
<coordinates>-122.448425,37.802907,0</coordinates>
</Point>
I'm attempting to extract the ExtendedData information out of this placemarker on a click event:
google.earth.addEventListener(kmlObject, 'click', function(event) {
event.preventDefault();
var kmlPlacemark = event.getTarget();
});
An alternative solution would be to get the kmlObject from the kmlPlacemarker, any ideas?
Given the placemark the Google Earth API provides two methods to access the ExtendedData element.
getBalloonHtml()
getBalloonHtmlUnsafe()
API Reference:
https://developers.google.com/earth/documentation/reference/interface_kml_feature
You can find a working example in the Google Code Playground here:
https://code.google.com/apis/ajax/playground/?exp=earth#extended_data_in_balloons
If you wanted to get the raw KML for extended data then you could fetch the KML representation and parse it as an XML document.
var output = placemark.getKml();
Just to say I posted about just this issue on the support forum for the plug-in: https://code.google.com/p/earth-api-samples/issues/detail?id=16
Here is a method I cobbled together to provide support for getExtendedData. It takes a string of Kml as the argument via 'feature.getKml();` It returns any extended data elements that have values in a key[value] object. It expects the extended data to be in the format:
<Data name="Foo">
<value>bar</value>
</Data>
Tested in XP - FF3.0, IE7, Chrome
function getExtendedData(kmlString) {
var xmlDoc = null;
var keyValue = [];
//Parse the kml
try {
//Internet Explorer
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(kmlString);
} catch(e) {
try {
//Firefox, etc.
var parser = new DOMParser();
xmlDoc = parser.parseFromString(kmlString,"text/xml");
}
catch(e) {
//Failed to parse
alert(e.message);
return;
}
}
// Get all the named elements
var data = xmlDoc.getElementsByTagName("Data");
// Iterate through the data elements
for(var i=0; i<data.length; i++) {
if(data[i].getAttribute("name") &&
data[i].getElementsByTagName("value").length > 0) {
// Get the name and value
var name = data[i].getAttribute("name");
var value = data[i].getElementsByTagName("value")[0].firstChild.data;
// Assign them to the keyValue object
keyValue[name] = value;
}
}
return keyValue;
}
Usage
// where 'feature' is the object with the extended data
var data = getExtendedData(feature.getKml());
for (var name in data) {
var value = data[name];
alert(name + '=' + value); // e.g. type=images
}
It is actually possible to access the ExtendedData elements via the DOM APIs, although they're not particularly well-documented anywhere.
I found them while grepping around inside some of the resource (.rcc) files packaged with the Plugin.
Assuming a simple Placemark sample similar to yours:
<Placemark id="testmark">
<!-- other stuff... -->
<ExtendedData>
<Data name="someDataUrl">
<displayName>URL Representing some Data</displayName>
<value>http://example.com/#hello</value>
</Data>
</ExtendedData>
</Placemark>
Then (once it's fetched/parsed/loaded into Earth, you can access it something like:
var mark = ge.getElementById('testmark');
var extDataObj = mark.getExtendedData();
var extDataOut = Array(extDataObj.getDataCount());
for (var i = 0; i < extDataObj.getDataCount(); i++) {
var item = extDataObj.getData(i);
var details = { name: item.getName(),
displayName: item.getDisplayName(),
value: item.getValue()
};
extDataOut[i] = details;
}
console.dir(extDataOut);
Haven't tested it for performance vs the .getKml() and feed to an external parser approach, and the lack of official documentation might mean it's not fully functional or supported, but in all testing so far it seems to do ok. I haven't yet found a way to access any of the more complicated SchemaData type structures, only the simple <data name=''><value>... form.
Just checking to see if there is a very simple way to make the view header fixed so that as you page down in the view in XPages, the header stays where it is. position=Fixed is not a property of xp:viewColumnHeader.
If you want to add the attribute of position to xp:viewColumnHeader you can use the attrs property to do that (works on 8.5.3). You code would look something like this:
<xp:viewColumnHeader ......>
<xp:this.attrs>
<xp:attr name="position" value="fixed"></xp:attr>
</xp:this.attrs>
</xp:viewColumnHeader>
But I don't think that alone would do the trick. Some time back I created a CSS snippet to make floating Banner, Title Bar and Place Bar in Application Layout control of Extension Library. You can get some ideas from that.
yes, it is possible, but requires some JavaScript coding.
I solved it for a customer recently using with the following code. The basic idea is to geht the width of the columns out of the first line of TDs, then apply this with to the THs ad set the THs to fixed afterwards.
You need to run this function after a partial update, too. Good luck.
var fixTableHeaders = function() {
var thead = dojo.query("thead")[0];
if (!thead) return;
thead.style.position = "static";
var THs = dojo.query('.xspDataTable th');
var firstTDs = dojo.query('.xspDataTable tr:first-child td');
var secondTDs = null;
if (firstTDs.length < 2) {
// categorized view, first line is a category with only one cell
// -> we need the second line
secondTDs = dojo.query('.xspDataTable tr:nth-child(2) td');
}
var w = 0;
for (var i = 0; i < THs.length; i++) {
w = dojo.coords(THs[i], true).w;
// console.log(i+" w="+w);
THs[i].style.width = (w)+"px";
if (firstTDs[i]) {
//if (secondTDs && secondTDs[i]) secondTDs[i].style.width = w+"px";
//else firstTDs[i].style.width = w+"px";
firstTDs[i].style.paddingTop = "3em";
}
}
thead.style.position = "fixed";
}
dojo.addOnLoad(fixTableHeaders);
I saw some jQuery code the other day that could make a Table Header fixed. Don't remember where it was but something that can help you should be out there.