Ben Langhinrichs

Photograph of Ben Langhinrichs

E-mail address - Ben Langhinrichs






April, 2020
SMTWTFS
   01 02 03 04
05 06 07 08 09 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

Search the weblog





























Genii Weblog


Civility in critiquing the ideas of others is no vice. Rudeness in defending your own ideas is no virtue.


Wed 29 Apr 2020, 03:03 PM
Inline GIF image
St Augustine famously said, though probably in Latin, "When in Rome, do as the Romans do." Yet my previous post, Down to Business - PDF invoices from Notes data with Node, violates that spirit in a fairly major way. Now, the post is still well worth a read and contains a demo that is absolutely relevant, but the script is a bit like reading a sex scene in a book by a priest. You know what is happening, but you're not convinced the author really does.
 
JavaScript isn't written that way. JavaScript doesn't generally operate that way. Sure, it works, but not in a way that would speak to anybody coming from the modern world of JavaScript or Node.js development. It does look a lot like LotusScript, but if we want to widen the range of developers who work with Notes/Domino, we need to reach beyond the relatively few LotusScript developers still out there and appeal to the vastly wider ocean of JavaScript developers. My earlier post, A bigger boat: meeting developers where they are, adds one element of that by allowing parameters to be passed as an object. But it is still fairly old-fashioned synchronous blocking code. Do this. Then do that. While this, do that other. The main JavaScript loop runs in a single thread, so unless you spin off functions, while it is off doing a function, everything waits. That is not good when you should also be processing mouse movements, etc.
 
In short, we need to go asynchronous. We need Promises. Not ordinary promises, like "I will remember to take the trash out on trash day, but JavaScript Promises, which are more like, "I will come back with a result that either resolves this function or rejects it due to error."
 
So, back to the drawing board with Exciton Power. Here is the same basic script I showed in  Down to Business - PDF invoices from Notes data with Node, generating the exact same invoices, but now using the asynchronous (and default) mode for Exciton Power. It some a bit more error trapping and uses slightly different class names (e.g., ExcitonCollection rather than GCollection). All methods are asynchronous and return promises. It is still mostly imperative code and still pseudo-synchronous in that it uses an async / await model familiar to JavaScript developers. It should be fairly clear to hardcore LotusScript developers as well, but let's face it, we old dogs do have to try and learn a few new tricks. Mostly, it does the job without blocking the main event loop, so Node.js can get on with its business, though a more truly asynchronous model will be coming for times when you might handle multiple documents at the same time by spinning off different threads. But for now, at least we can pass as Roman wannabes if not full-fledged Romans. Che buon'idea!
 
Note: I am not a JavaScript expert by any means, and welcome any suggestions about how to improve this and make it feel more natural to JavaScript developers.
 
// *** Initiate a session
const session = require('./build/debug/ExcitonPower');
const { createInvoice } = require("./createInvoice.js");
 
// *** Create a collection and add a view to it
const coll = session.useCollection();
 
let invoiceStarted = false;
 
// *** Cycle through the view, looking for invoices and their line item response documents
coll.addByView({server: "", filePath: "AcroBatsCRM.nsf", viewname: "Customers"}).then(async doccount => {
  console.log("Added "+doccount+" documents from Customers view");
  try {
    let doc = await coll.getFirstDoc({items: "CompanyName,InvNo,Contact,Address,City,State"})
    let docobj;
    if (doc != null) docobj = JSON.parse(doc);
 
    let count = 0;
    let total = 0.0
    let invoiceStarted = false;
    while (doc !== null) {
      if (docobj'@form' == "Invoice") {
        invoice = {InvNo: docobj.InvNo, filename: docobj.CompanyName+" - "+docobj.InvNo+".pdf", shipping: docobj, items: []};
        invoice.subtotal = 0.0;
        invoice.paid = 0.0;
 
        invoiceStarted = true;
        total = 0.0;
        count = 0;
        } 
      else if (docobj'@form' == "Line Item" && invoiceStarted) {
        count++;
        doc = await coll.getDocByUNID({unid: doc, items: "ItemNo,Qty,Price,Total,@DbLookup(\"\":\"\"; \"\":\"AcroBatsPRD.nsf\"; \"Products\"; ItemNo; \"ItemDesc\")=ItemDesc"});
        docobj = JSON.parse(doc);
        invoice.items.push(docobj);
        total += docobj.Total;
        invoice.subtotal = total;
        invoice.paid = 0.0;
        }
 
      if ((doc = await coll.getNextDoc({unid: doc, items: "CompanyName,InvNo,Contact,Address,City,State"})) != null)
        docobj = JSON.parse(doc);
 
      if (doc == null || (invoiceStarted && docobj'@form' != "Line Item")) {
        invoice.subtotal = total;
        invoice.paid = 0.0;
        createInvoice(invoice);
        console.log("Created invoice as "+invoice.filename+" for $"+total); 
        invoiceStarted = false;
        }
      }
    }
  catch(err) {
    console.log("Error: "+err.message);
    }
  }).catch(function(err){console.log("Error: "+err.message)});
 
console.log("Completed!");
 

Copyright 2020 Genii Software Ltd.

Tags:

Mon 27 Apr 2020, 08:44 AM
Inline JPEG image
 
I've had various customers ask what exactly is in Notes 11, especially for those who are just gradually getting back on maintenance after being lapsed for a while. Mat Newman gives a rundown.
 
 
 

Copyright 2020 Genii Software Ltd.

Tags:

Thu 23 Apr 2020, 09:57 PM
Inline JPEG image
 
I've been in this business a while, and seen a lot of world events translate—or not—into changes in the way people look at their business systems. From I.T.-specific events such as the dot com crash or Y2K to general events such as the Great Recession, when the world shifts on its access, it encourages people to examine their systems and fix what failed or preemptively make changes to prevent failure in a future event. Sometimes they do, and sometimes the event recedes and they don't.
 
The global pandemic is no different, and yet it is very different. A virus that doesn't infect computers has nonetheless forced tremendous upheaval in the computer world. The first wave of changes was almost immediate as lockdowns in country after country forced workers who could to work from home. Online meetings, video conferencing, and email/chat/etc. all found themselves used more heavily and in more ways than ever before.
 
But the second wave has started, and it is more widespread and deep. Every software system a company or organization uses has been pushed and pulled in ways that may not have been planned for. There are big, public examples such as the state unemployment software systems that were never built for surges such as this... and are built in COBOL. A scramble for aging developers who know COBOL is on right now, but also an awareness that these systems have chugged along and are no longer sustainable. In most states (that function well), the systems will be replaced and modernized as this crisis calms down, because the weakness is now visible.
 
Among my customers, both existing and new, I am seeing a growing awareness that relying on those Notes applications that keeps chugging along may have risks. Some applications are only designed for a Notes client, but workers who were never expected to work from home may not be equipped with laptops to run Notes, or are much harder to support remotely. Other apps designed to be run inside the corporate network on a Domino-based Intranet may not be secured properly for external use over less secure WiFi in homes. While some problems can be solved with VPNs and other technical solutions, closer scrutiny is leading people to examine whether to move forward or move away. Perhaps Nomad is the answer for some apps, allowing them to be run on devices that workers already have. For others, a hastily spun-up Domino interface to a Notes app may be the short term answer.
 
But no matter what choices are made, change is in the air. I am currently juggling migrations away from older applications with some customers and upgrades to more recent Domino versions for others. I am working with a couple of customers on whether they can use a combination of Domino Access Services,. a custom DSAPI, and Exciton to move data into a more accessible environment and still be able to keep their Domino data secure and intact. Almost every day, I hear from a new customer or an older customer with new ideas, and most are on the cusp of deciding what direction to take.
 
I want to be able to steer them toward Notes/Domino 11, Nomad, etc. Sometimes, I can. Part of the reason I am working so hard on Exciton is to give pathways forward for their apps where the HCL development picture is still too limited. When I can't steer them forward, I am helping them migrate off with the highest degree of accuracy, fidelity, and security possible. But they are moving for the first time in a long time. The pandemic has exposed weaknesses, and it is up to us and HCL to either help fix those weaknesses or help them move on. There is opportunity, but only if we seize it.
 
Be proactive. Look at your clients and see how well the software applications you know about are likely to perform in this new world. Perhaps we will "get back to normal" in a few months, but the cat is out of the bag, and systems not designed to accommodate this pandemic world will be upgraded, scrapped, or replaced with systems what are designed for it. While some applications are used every day, others may be less prominent. If you can go to your client with a proactive vision for those systems, you can win that business, and perhaps steer the solution in a direction you would like.
 
If you have applications that aren't passing muster in this new reality, request an evaluation of our Midas LSX with Midas Exports to get the data out, or contact me to discuss how we can assist. If you would be interested in moving forward, contact me and ask to participate in our Exciton beta. You may reach me by e-mail at 
 
 

Copyright 2020 Genii Software Ltd.

Tags:

Mon 20 Apr 2020, 10:30 AM
Inline JPEG image
 
Because the business of the world is business, I'm starting an occasional series of posts on how our Exciton Power product can drive practical business goals. One frequently requested feature is the ability to create PDFs programmatically, not simply printing a Notes form but actually creating the PDFs. I asked on Facebook for suggestions about real-life ways that customers use PDFs, and John Head suggested invoices from a classic main doc/response doc roll-up.
 
On Friday morning, I had time to pull together a demo. I emphasize the time because it only took about an hour to figure out how to create the invoice logic using PDFKit (a free Node.js module) and Exciton Power (our product in beta). It took more time to create a simple pair of databases with dummy data. The power in Exciton Power and Node.js is not about invoices or PDFs, it is about the ability that Notes developers will recognize to be presented with a business problem and be able to code it and put it into action in a matter of hows, not days and weeks.
 
Here is a short video showing the process. For those interested in how it is coded, the JavaScript I used will follow the video. [Note: I added subtitles to make this more accessible.]
 
 
If you would be interested in participating in the Exciton beta, or even if you just have questions, contact me by e-mail at 
 
Source Code for demo:
 
The code I use that is specific to Exciton Power is 54 lines and probably fairly familiar stuff. I walk a view using a GetFirstDoc/GetNextDoc combination. One quirk of the Exciton code is that instead of returning something like a NotesViewEntry, we return meta-data about the document and the specific items requested, bundled as a JSON string. In this case, one of the items is even an @DBLookup to a separate database to retrieve a product description.
 
[Note: Once we release Exciton Power out of beta, I will post all sample code on github, (as I has been gently recommended).. At the moment, syntax is open to change, so I don't want to put up code that may not work with the Gold release.]
 
demo2.js
const useExcitonPower = require('./build/debug/ExcitonPower32');
const { createInvoice } = require("./createInvoice.js");
 
// *** Initiate a session
const gs = useExcitonPower();
 
// *** Create a collection and add a view to it
const coll = gs.useGCollection();
console.log(coll.addByView("", "AcroBatsCRM.nsf", "Customers"));
 
// *** Cycle through the view, looking for invoices and their line item response documents
let doc = coll.getFirstDoc("CompanyName,InvNo,Contact,Address,City,State");
let count = 0;
let total = 0.0
let invoiceStarted = false;
while (doc !== null && count < 10)
  {
  let docobj = JSON.parse(doc);
  if (invoiceStarted && docobj['@form'] !== "Line Item")
    {
    invoice.subtotal = total;
    invoice.paid = 0.0;
    createInvoice(invoice);
    console.log("Created invoice as "+invoice.filename+" for $"+total); 
    invoiceStarted = false;
    }
  if (docobj['@form'] == "Invoice") 
    {
    invoice = {InvNo: docobj.InvNo, filename: docobj.CompanyName+" - "+docobj.InvNo+".pdf", shipping: docobj, items: []};
 
    invoiceStarted = true;
    total = 0.0;
    count = 0;
    }
  else if (docobj['@form'] == "Line Item" && invoiceStarted)
    {
    count++;
    doc = coll.getDocByUNID(doc, "ItemNo,Qty,Price,Total,@DbLookup(\"\":\"\"; \"\":\"AcroBatsPRD.nsf\"; \"Products\"; ItemNo; \"ItemDesc\")=ItemDesc");
    docobj = JSON.parse(doc);
    invoice.items.push(docobj);
    total += docobj.Total;
    }
 
  doc = coll.getNextDoc(doc, "CompanyName,InvNo,Contact,Address,City,State");
  }
if (invoiceStarted)
  {
  invoice.subtotal = total;
  invoice.paid = 0.0;
  createInvoice(invoice);
  console.log("Created invoice as "+invoice.filename+" for $"+total); 
  }
 
The createInvoice.js script I used is based fairly directly on the example in Generate Invoices with PDFKit on Node.js. Again, I want to emphasize that the value to a Notes/Domino developer of being able to work in Node.js is the power to quickly and easily use resources like PDFKit. Many are free, some cost something, but all extend the edges of development far, far beyond what we currently have in Notes.
 

Copyright 2020 Genii Software Ltd.

Tags:

Wed 8 Apr 2020, 11:25 AM
Inline JPEG image
 
In my previous post, A bigger boat: growing the vision for Domino development, I introduced the idea (with a demo!) of an extensive set of Notes/Domino functionality exposed in different ways, in this case LotusScript and JavaScript with Node.js. The idea is that we need Domino API functionality available where projects are. If a company is using Node.js extensively, we need that functionality there, not in LotusScript. But if a company has invested a lot in LotusScript, we need the functionality there. Likewise, while we might wish everybody would rush to Domino 11, there are companies on 8.5.3, 9.0.1, 10, and 11. We need to reach them where they are if we want widespread adoption,
 
In this post, I wanted to talk about lower level ways in which we reach the developer where she or he is. Some developers are used to LotusScript, so we'd like to make it easy for them to translate their skills even if they have to work in JavaScript. Other developers are coming to this with extensive JavaScript skills, and we'd like to make it easy for them as well. Additionally, there are strengths in JavaScript we don't have in LotusScript, and we'd like to make those available.
 
Let's look at an example:
 
Parameters
rtchunk.appendText("Duck season!", 12, "Bold 12pt");
rtchunk.appendNewLines(1, false);
rtchunk.appendText("Rabbit season!", 14, "Bold 12pt");
rtchunk.appendNewLines(1, true);
 
In LotusScript, methods are called with a series of defined parameters. Some parameters are required, while others are optional. One of the design decisions we made with Exciton Power is to provide more options rather than fewer. It is a risk, as people could get confused or wonder which is "correct", though under a very thin layer, the choices do exactly the same thing. So, we provide the option of using LotusScript-style parameters and validating them the way we would in LotusScript.
 
Objects
But JavaScript developers are often more comfortable with objects, both unnamed and relatively unstructured objects and objects which are part of defined classes. This could could be written as objects using:
rtchunk.appendText({text: "Duck season!", textLen: 12, fontSpecs: "Bold 12pt"});
rtchunk.appendNewLines({count: 1, newParagraph: true});
rtchunk.appendText({text: "Rabbit season!", textLen: 14, fontSpecs: "Bold 12pt"});
rtchunk.appendNewLines({count: 1, newParagraph: true});
 
Slightly more self-documenting, but not terribly different. But let's name those objects and see some differences:
 
var txtObj = {fontSpecs: "Bold 12pt"};
var nlObj = {count: 1, newParagraph: false};
 
txtObj.text = "Duck season!";
txtObj.textLen = txtObj.text.length;
rtchunk.appendText(txtObj);
 
nlObj.newParagraph = 
rtchunk.appendNewLines(nlObj);
 
txtObj.text = "Rabbit season!";
txtObj.textLen = txtObj.text.length;
rtchunk.appendText(txtObj);
 
nlObj.newParagraph = true
rtchunk.appendNewLines(nlObj);
 
At first glance, this is even longer and less clear, but we have separated out how we set the font specs so that can be handled elsewhere according to some other rule, and changed the logic so the textLen property can be calculated instead of hard-coded. We could then take this another step further, and make the txtObj a class where the txtLen was calculated automatically when the text was set, and you could separate out setting point size from setting other attributes, for exampe. 
 
class TextObj {
  constructor() {
    this.ptSize = "10pt";
    this.attribs = "Plain";
    this.color = "Black";
    this.face = "sans-serif";
    this.fontSpecs = this.ptSize+" "+this.attribs+" "+this.color+" "+this.face;
  }
  setText(t) {
    this.text = t;
    this.textLen = this.text.length;
  }
  setPtSize(pt) {
    this.ptSize = pt;
    this.fontSpecs = this.ptSize+" "+this.attribs+" "+this.color+" "+this.face;
  }
}
 
var txtObj = new TextObj();
var nlObj = {count: 1, newParagraph: false};
 
txtObj.setText("Duck season!");
rtchunk.appendText(txtObj);
 
nlObj.newParagraph = 
rtchunk.appendNewLines(nlObj);
 
txtObj.setText("Rabbit season!");
rtchunk.appendText(txtObj);
 
nlObj.newParagraph = true
rtchunk.appendNewLines(nlObj);
 
 
With classes, you can implement rules so that certain things are and aren't allowed. Obviously, all of this can be done in LotusScript in a more cumbersome way, but we wanted to meet JavaScript developers where they are and also take advantage of individual language strengths. With the option of either explicit parameters or a JavaScript object, we go to war with the army we have, whatever army we happen to have.
 
 
If you would be interested in participating in the Exciton beta, or even if you just have questions, contact me by e-mail at 
 

Copyright 2020 Genii Software Ltd.

Tags:

Thu 2 Apr 2020, 04:34 PM
Inline GIF image
 
In this classic scene from Jaws, police chief Martin Brody already knows he is facing a dangerous creature, but when the great white shark finally appears, he realizes has been thinking on the wrong scale. Over the years, the phrase, "You're gonna need a bigger boat" has become a popular way of saying that you have underestimated the problem.
 
HCL is doing wonderful things with Domino, building an infrastructure made for today's world, and especially today's web. But the folks at Iris and later Lotus realized early on that you could build the best platform in the world, but it wouldn't do you any good if people couldn't build their business apps on it. They went all in on APIs to make it possible to do fantastic things, well ahead of the competition. But the competition has had a lot of time to improve. Look around at Microsoft and Google and Amazon and a hundred other smaller tech companies, and you realize that if Domino is going to carve out a place in this world, we need a bigger boat.
 
We at Genii Software have a vision for a comprehensive set of functionality that transcends the traditional Domino development framework. We are not competing with HCL, and won't hesitate to piggyback off of their technology where we can. The goal is not to beat HCL, but to enhance the development landscape along with them for all our sakes. Basically, we want to build a bigger boat together with HCL and other partners.
 
This very brief demo shows a first look at our Exciton Power offering, in beta now. This is a Node.js module with (currently) six classes, over two hundred methods, and over 150 properties, all allowing you to build kick butt business apps combining the wealth of Node.js modules with the power and security of Notes/Domino. If you've seen my recent demos, you'll recognize the functionality shown here, but in JavaScript instead of LotusScript. The code below the demo should look very familiar to those who know and love the Midas LSX, but this is more than a Midas port to JavaScript, and it goes far beyond rich text, including form/view/etc. design, data mining, high fidelity rendering, and more. All available to work with both local and server-based databases using the traditional Notes security model, whether your Notes ID is local or in the ID Vault. Oh yes, and you'll notice the demo is in Notes 9.0.1. Exciton Power is built to work with Notes/Domino 8.5.x/9.x/10.x/11.x on both 32-bit and 64-bit Windows and 32-bit and 64-bit Linux. If you need to build a Node.js app for a Domino customer, you don't want to worry what version or OS they are on. You want to meet them where they are, and then take them where you are going.
 
 
If you would be interested in participating in the Exciton beta, or even if you just have questions, contact me by e-mail at 
 
Source Code for demo:
Let's take a quick look at the code. I am using the really basic approach to fulfilling the HTTP request, but you obviously can grab the results of the GET however you like. Just make sure it is a JSON text stream when it is passed to the methods. (This is a first look of the beta, so don't count on everything being exactly like this.) Not bad for 47 lines of code, eh?
 
const https = require('https');
const useExcitonPower = require('./build/debug/ExcitonPower32');

// *** Initiate a session
const gs = useExcitonPower();

// *** Connect to a rich text item
const rtitem = gs.useGRTItem();
rtitem.connect('', 'UseCases.nsf', '2E92311B5BB4CB168525853D0016F8E5', 'Body', true);
const rtchunk = rtitem.useGRTChunk(rtitem);
rtchunk.defineChunk(rtitem, "Everything");

// *** Define the properties to access the data
const props = "AsTable='horizontal' AsObjectArray='yes' TitleFont='Bold #004080' TableWidth='Fit with margins'";
const col1props = "Text='' TextFont='Bold #004080'";
const col2props = "Text='' TextFont='-Bold #084000'";

// Add the first tab with data about how many tests have been run
https.get('https://covid19-server.chrismichael.now.sh/api/v1/TestsinUS', (resp) => {
  let data = '';
  // A chunk of data has been recieved.
  resp.on('data', (chunk) => {
    data += chunk;
  });

 // The whole response has been received. Add the internal table to tab 1
 resp.on('end', () => {

  // *** Add the title, and make everything Verdana to look better
  rtchunk.setFont("Plain 10pt Default Sans Serif");
  console.log("Adding tabbed table with two tabs.");
  rtchunk.appendTable(1, 1, "RowDisplay='Tabbed' TabFont='14pt Bold' BorderEffects='Drop_Shadow'",
          "CellColor='RGB227,234,240' TabLabel='COVID-19 Testing' Width=4");
  rtchunk.zoomIn("Table 1");
  rtchunk.appendRow(false, 1, "CellColor='RGB227,234,240' TabLabel='World Stats'");
  rtchunk.zoomOut();
  
  // *** Appends the fields to the form and save
  rtchunk.zoomIn("Table 1; Row 1; Inside column 1");
  console.log("Adding nested table inside first tab with Testing stats from one REST API call.");
  rtchunk.appendFieldsWithJSON(data, props+" ChildOf='tests.table'", col1props, col2props);
  rtchunk.zoomOut();

  // Add the second tab with data about how many tests have been run in the U.S.
  https.get('https://covid19-server.chrismichael.now.sh/api/v1/AllCasesInAmerica', (resp) => {
    data = '';
  
    // A chunk of data has been recieved.
    resp.on('data', (chunk) => {
      data += chunk;
    });
  
    // The whole response has been received. Add the internal table to tab 1
    resp.on('end', () => {
    
    // *** Appends the fields to the form and save
    rtchunk.zoomIn("Table 1; Row 2; Inside column 1");
    console.log("Adding nested table inside second tab with World stats from a different REST API call.");
    rtchunk.appendFieldsWithJSON(data, props+" ChildOf='data.table'", col1props, col2props);
    rtchunk.zoomOut();
  
    rtitem.save();
    console.log("Document saved");
  });

  }).on("error", (err) => {
    console.log("Error: " + err.message);
  });
  
});

}).on("error", (err) => {
  console.log("Error: " + err.message);
});

 
 

Copyright 2020 Genii Software Ltd.

Tags: