Wednesday, September 22, 2021

Checkbox: allow entries not on list [client]

 I love working in the Notes client. So much is handled right off. As part of a personal research project, I have been categorizing references from a variety of sources. The easiest way is a checkbox, of course. This does leave something out- the ability to add in a new category on the fly. Dialog Lists have an option for new entries that is very handy. There is not an out of the box way to do this with checkbox fields. And that is what I needed. 

So I created a text field I could put new categories in, naming it "newCat". In the Terminate event, I wrote the value to the field. As I populated the checkbox with a category view, as of then, my new category is available. Below is my code

Sub Terminate
Dim workspace As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Dim doc As NotesDocument
Set uidoc = workspace.CurrentDocument

If Len(uidoc.FieldGetText("newCat")) > 1 Then
Call uidoc.Save
Set doc = uidoc.Document

Dim newCatVar As Variant
Dim arrApp As Variant

newCatVar = Split(uidoc.FieldGetText("newCat"), ",")

Dim getArray As Variant
Dim itemarray As NotesItem
Set itemarray = doc.GetFirstItem("Categories")
getArray = itemarray.Values
arrApp = Arrayappend(getArray, newCatVar)
Call doc.ReplaceItemValue("Categories", arrApp)
Call doc.RemoveItem("newCat")
Call doc.Save(True, False)
End If
End Sub

Tuesday, November 13, 2018

IndexedDB from XPages

Browsers have a few ways of storing data. Most of us have used cookies at one time or another. Local Storage is another. For a project I'm working on, I may need a bit more and this is were IndexedDb come in. It is persistent over different sessions, and can store quite a bit. This is for a mobile web app, so this will be very valuable. This post will give you an idea of how much it will hold.

I found a tutorial, I got it to work, but I needed it to work with the XPage application I am working on. And I got it to. You can see in my code that I am adjusting this tutorial, so the initial credit goes to Matt West. If you go over that tutorial, this post will make a bit more sense. I'm concentrating on integrating it to XPages, not really explaining the ins-and-outs of IndexedDb.

For the IndexedDb, you have to open it. Basically, this is initializing it. In the "indexDBLibDAC" script library, we create a blank datastore. I call it "dacs", and has individual records called "dac". It has a keyPath of "timestamp". This allows the JavaScript object to find and retrieve it. Here is a picture of how it stores data in Firefox, you can find this under "Storage" in the Developer tools.


Versioning is important, so you set a version. If you increment the version, it replaces the datastore, and I have not tested what happens if you move up a version, but it may remove all the data in place.

It has to be opened each time. I do this in a control I have on all the pages called "UtilPollConnection". On window.onload, it opens the IndexedDb, as you can see below, taking from the tutorial. I have the work in the script library. 

window.onload = function() {
  // Display the items.
dacDB.open(refreshDacs);
};

So this creates a blank IndexedDb object/database.  

Now, I need to get the values into the IndexedDB. I have a control called "ccTmpDAC". I wanted to show moving it from XPage fields to the IndexedDB, and I wanted to show it in a simple manner. You can fill out the fields, and click the "Move Cookies" button. This puts the values from classic XPages to something easily reachable by Client Side JavaScript. Once the values are in the cookies, click the "Add to IndexedDb" button. That moves it to the IndexedDb. This is not a super-effecient method, but it's how I build this in steps, and I think helps illustrate the process. In my working app, I'm using a similar process. 

Adding a record to the IndexedDb basically makes a JSON element, and you can see how I map the values from the cookies to the dac entry and store it - again based on the tutorial. You can put in a number of values and get the to the IndexedDb.

My process in this is to store the data for a while, when the device is out of data range and upload when a connection is re-established. The basic framework for that is in place. I'm using some timeouts, and for this version they are not set up correctly. But you can test this with the "Sent to RPC" button. This calls a function that gets the first record and calls a rest with it's parameters. You can do other things, but I think this shows how you can get that to a Java Bean or some other SSJS. Meaning we have made a round trip. From traditional XPages input to getting it back.

The one XPage in the attached database does the entire process.

One note on the JavaScript. When I brought the tutorial over to start making it work in XPages, I got some errors on certain keywords. They would just not compile in whatever version of JavaScript Domino 9 uses. I found this post  which explains that to do. You put the words in brackets and single quotes. So when you have result.continue(); Give you a problem, make it result['continue']();, so that it will compile.

Here is a link to the database I have for IndexedDb in XPages.

Cheers,
Brian


Wednesday, October 25, 2017

HCL and IBM Partner on Domino 10

I was excited to see the announcement about HCL and IBM working on Domino 10 in 2018. The thing I want most out of this is a focus on Sales, even more than I want most of the changes that could come from new elements to work with. 

Domino has a very mature NoSQL datastore that has a lot of built-in functionality. I think if an emphasis was put on that and whatever sales teams exist pushed the product there would be an upturn in the user base. If they would make it worth the financial while of salespeople to sign a deal with new or existing customers, that will make the platform successful. 

We've all seen how a better product is relegated to obscurity when an inferior product just has a better salesforce. It's not the lack of features: systems are sold every day that can't deliver on things. It's not needing trained people: there are new systems created all the time that people have to go learn. It's getting a customer to sign a contract. 

IBM has a lot of money walking out the door not because they are lacking in good, solid products, but because they don't make it profitable for their sales team to sell everything they offer. Yes, they make a lot of money selling services to support competitors products. But IBM should look to make more by supporting products and selling the products they will support. 

Friday, August 11, 2017

Importing CSV via Java to a Notes Database

It's been longer than I intended since my last post, but here it is.

One of the things I've posted has been importing to a Notes database. My first was using LotusScript to import from an Excel file, I've moved to using CSV which does not require an external program. This one uses a Java bean and HashMap to map the field names.

As with the others, this takes two files. First is a simple where the you have the data. The column titles will match up with the same row in a title spreadsheet. The title spreadsheet has two rows. The first has the column titles form the first and the second is the field name you want to import to.

Here is a sample of the import title:

Alpha,Beta,Gamma,Delta,Epsilon
AlphaField,BetaField,GammaField,DeltaField,EpsilonField

Here is a sample of the data file:

Alpha,Beta,Gamma,Delta,Epsilon
First1,Second1,Third1,Forth1,Fifth1
First2,Second2,Third2,Forth2,Fifth2

Here is the bean:

package com.something;

import java.io.*;
import java.util.*;
import javax.faces.context.*;
import lotus.domino.*;
import org.apache.commons.lang.*;

public class ImportMap implements Serializable {

private static final long serialVersionUID = 1L;

public ImportMap() {

}

public void ImportMappedData(String dataFile, String fieldMapFile, String formName) {
try {

BufferedReader fieldMapReader = new BufferedReader(new FileReader(fieldMapFile));
String fieldMapTitleLine = fieldMapReader.readLine();
String fieldMapFieldLine = fieldMapReader.readLine();
// will need to account for different numbers of columns
HashMap hm = new LinkedHashMap();

String[] splitTitle = fieldMapTitleLine.split(",");
String[] splitField = fieldMapFieldLine.split(",");
for (int i = 0; i < splitTitle.length; i++) {
hm.put(splitTitle[i], splitField[i]);
// System.out.println("Title: " + splitTitle[i]);
}
// Get a set of the entries
Set set = hm.entrySet();
// Get an iterator
Iterator i = set.iterator();
// Display elements
while (i.hasNext()) {
Map.Entry me = (Map.Entry) i.next();
}

BufferedReader dataMapReader = new BufferedReader(new FileReader(dataFile));
String dataMapTitleLine = dataMapReader.readLine();
String dataMapDataLine = dataMapReader.readLine();
String[] splitDataTitle = dataMapTitleLine.split(",");

Session session = (Session) getVariableValue("session");
Database db = session.getCurrentDatabase();
lotus.domino.Document importDoc = null;

while (dataMapDataLine != null) {
boolean saveDoc = false;
importDoc = db.createDocument();
importDoc.replaceItemValue("form", formName);
String[] dataMapData = dataMapDataLine.split(",");
for (int j = 0; j < splitDataTitle.length; j++) {
if (hm.containsKey(splitDataTitle[j])) {
try {
saveDoc = true;
importDoc.replaceItemValue(StringUtils.trimToEmpty(hm.get(splitDataTitle[j]).toString()), StringUtils.trimToEmpty(dataMapData[j]));

} catch (Exception Ae) {
// System.out.println("no value to write for " + splitDataTitle[j] );
}

}
}

if (saveDoc) {
importDoc.save();
}

importDoc.recycle();
dataMapDataLine = dataMapReader.readLine();
}

incinerate(importDoc, db);
} catch (Exception e) {
e.printStackTrace();
}
}

private void incinerate(Object... dominoObjects) {
for (Object dominoObject : dominoObjects) {
if (null != dominoObject) {
if (dominoObject instanceof Base) {
try {
((Base) dominoObject).recycle();
} catch (NotesException recycleSucks) {
// optionally log exception
}
}
}
}
}

public static Object getVariableValue(String varName) {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().getVariableResolver().resolveVariable(context, varName);
}
}

This one uses the Apache String Utils to strip out the extra spaces that might be in, and leave an empty string if nothing is found. You can take that out if you don't have the Jar or don't want to use it. My import process doesn't account for commas within a quoted string. I know that should be part of the process but it wasn't needed for my project. I'd like to find a way to address it. Currently I'm just using Split to break each row apart based on commas, but at some point I'd like to create something (possibly using RegEx) that accounts for the commas in a quoted string. I would suggest running something to remove extra line breaks. I had to write a script to remove them from Numbers. Otherwise you may get partial lines trying to import.

Cheers,
Brian

Tuesday, June 21, 2016

HTML5 Canvas to PNG via RPC

Declan Lynch provided a Signature Capture Control on OpenNTF some time ago. I had downloaded and played with it a bit, but hadn't had a production use for it, but it worked just like it said on the tin - drop it in and use it.

Recently I was asked to come up with a way to let people sign into an event using tablets, so a perfect opportunity to pull it out. It was a breeze to add it to the sign-in portion, we display a page on a mobile device and the user can sign in on the canvas. That was the "Wow" part of my initial presentation and his work let it go off not only without a hitch, but with next to no work on my part.

The next phase to come up is to capture the signatures as images so they can be exported and stored. Declan's control saves the co-ordinates in a text field. Here I discovered that the HTML5 canvas (which is what the control uses) has a method, toDataURL, that translates into a base64 string that can then be converted to an image. PNG is the default, but JPG also seems an option (I left it as PNG). I have put that in a CSJS button that calls a RPC that has a function that takes the string and converts it to an image, attaching that image to the document.

A few notes:

  1. I'm "cheating" on using CSJS to get the element in my sample. It's a simple page so the element is always generating the same ID. You will probably want to change that so you can use it anywhere. 
  2. The returned string starts with "data:image/png;base64," so my SSJS function strips that out.
  3. My PRC returns an alert that it is done, you can easily comment that out. 
I've not decided how I'm going to implement this yet, button clicking won't do for my workflow, but I can change that to some other event to trigger the process.

Here is a link to a document with the full XPage and the function I call.  

Cheers,
Brian



Monday, May 23, 2016

Eternal fustrations with IBM "Help" - - this time trying to give them money

So I find I need to purchase a Domino license again, this happens for independent developers. I make my selection and get taken to what IBM is now calling the "Marketplace" to check out. However the option to enter a credit card to actually pay for my purchase is greyed out. So I call in. There is a wait and a lady answers. She asks the typical questions and then for me to send them an email with a screen shot. I ask for a ticket number so I can track this request (my reopened ticket for Bluemix is still sitting there unanswered after several days). She tells me they can't open tickets. I tell her I need one to follow this issue. Three times so far she's put me on hold to come back with the same thing - send in an email. I still want a ticket number.

Four times now....(I'm writing this while being on hold)....Five times. Finally I got a ticket number. It's a bit different from the ones I'm used to from IBM, but here we go.

The lady did actually give me her full name (which is rare), but you have to wonder what customer service expert came up with the idea that people want to be forever told to go elsewhere.

Arrgh,
Brian

Tuesday, May 3, 2016

Simple Example: Bootstrap

The Bootstrap library is a great way to do responsive design, and it's been incorporated into the Extension Library so you can use it "out of the box". The problem I've found is that the samples provided are pretty complex. Not too helpful if you are starting out since you have to try to figure out callbacks and a lot of other stuff to get to the points you want.

I think overly complex starter examples are a waste. They let the creator think they have provided something without actually helping the new person find their way. If you wanted the BootStrap Navbar, for example, it's hard to find that element in a way you can just use it and figure out how to get fancy later.

I've been taking a course that includes Bootstrap, and it's helped me figure out how it works. So I decided to so create a database showing some of the basics so someone moving to Bootstrap (especially from traditional Notes or non-Bootstrap XPage work). It's not designed to show everything, but to help out in showing some of the basics. I think once someone gets these under their belt, the rest will come more easily.

Here is what is in this example, all in separate XPages so you can see just that.

  1. NavBar: the useful top. This one includes some links that collapse to the burger menu when on a mobile device. Also I've included a glyph in the upper right which is common and looks good. 
  2. Jumbotron: The big top on a lot of websites, and with a button to go somewhere. It also shows how to use the Bootstrap styling of a button "btn btn-primary", you can put in "btn btn-success" for the green one, for example. 
  3. Well/InputForm: This is my most complex entry here. This gives the nice appearance where there is  grey box (the "well") with labels and fields and a nice button. 
    1. Put everything in a container, there there is a div for the well, then a div for the form group. These combine for the appearance desired.
    2. I use the a "Display Errors" control and style it with the "alert alert-danger" for the expected Bootstrap validation.
    3. I put in a combobox so you can see how it styles as well
  4. ContexturalBackgrounds: these show the colors behind a paragraph or other elements. I have a variety of them. 
  5. Lists: Also showing different colors for list items, list groups, and divs where you can put other elements.
  6. Offset columns: Bootstrap uses a grid system, and I have a page sampling the width and offset. Offset lets you specify the number of columns on either side of the 'populated' column. These allow the resizing needed going from desktops to mobile devices. 
 In the database, change theme to one of the Bootstrap ones provided, like Bootstrap3.2.0.

If you need to use Themes in your application, you can incorporate this one like this:

<theme extends="Bootstrap3.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd">
   
        <!-- jQuery -->
    <resource>
        <content-type>application/x-javascript</content-type>
        <href>bower_components/DataTables/media/js/jquery-2.2.0.min.js</href>
    </resource>
....(other resources)
</theme>


It's the first node, "theme extends" that does it, incorporating the Bootstrap theme from the Extension library into the theme you need.

It is my hope that this will be easier for someone to see how Bootstrap works in XPages

Here is the database.

Cheers,
Brian

Thursday, March 24, 2016

Import CSVs into a Notes/XPage database

We have not had a direct way to import into Notes since it became impossible to save a file in .123 format (or .wk4). I loved being able to import from a view. To work around this, some years ago I created an agent that used MSExcel and two files. That worked when I had MSExcel on my machines, but I don't any more, especially for my personal machines where I won't pay for it. So I needed to change to to import CSV files, which I can do via LibreOffice. So I updated what I had posted in 2009.
 
 The "data" file was the data to be imported, with the first row being (as is common) some description of what the column contains (e.g. "Name" "Telephone number", etc.). This first row is copied to the other spreadsheet, and in the second row, below each column is the Notes field name that column should be mapped to.

Here is a sample of data, note the first column has titles.


And here is how I would set up the mapping:



Then we just need to import, and here is the agent:
Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim doc As NotesDocument
    Dim fileName As String
    Dim lastColumn As Integer
    Dim index As Variant
    Dim lastRow As Integer
    Dim row As Integer
    Set db = session.CurrentDatabase
    Dim fileNum As Integer, cells As Integer, k As Integer
    Dim InputStr As String, delimiter As String
    fileNum% = FreeFile()
    Dim titleFileName As String
    Dim parseSize As Double
    Dim fileDataNum As Integer   
    Const titles = "c:\dxl\ImportTitles.csv"
    Const data = "c:\dxl\ImportData.csv"
    Const formName = "Import Form"       
    Dim q As Double
   
    titleFileName = titles
    'Column titles on first row
    'Notes field names on row 2
    delimiter = "," ' Delimiter of your file
    Dim parseArray As Variant
   
    Dim fieldArray() As String
    ReDim Preserve fieldArray(1, index)
    Open titleFileName For Input As fileNum%
    k = 0
    Do While Not EOF(fileNum%)
        Line Input #1,  InputStr$
        parseArray = Split(InputStr$, ",")
        parseSize = UBound(parseArray)
        If(k = 0) Then
            ReDim Preserve fieldArray(1, parseSize)
            q = 0
            Do Until q = parseSize + 1
                fieldArray(0, q) = parseArray(q)               
                q = q + 1
            loop
        Else
            q = 0
            Do Until q = parseSize + 1
                fieldArray(1, q) = parseArray(q)
                q = q + 1               
            Loop
        End If
        k = k + 1
    Loop
    Close fileNum%
           
    fileDataNum% = FreeFile()
    Dim dataFileName As String
    dataFileName = data
   
    Open data For Input As fileDataNum%
    k = 0
    Do While Not EOF(fileDataNum%)
        Line Input #1, InputStr$
        parseArray = Split(InputStr$, ",")
       
        If(k = 0) Then
            'first row, so the titles
            lastColumn = UBound(parseArray)       
            parseSize = UBound(parseArray)   
            Dim x As Integer, y As Integer
            y = 0
            x = 0
            Do While x < (Ubound(fieldArray, 2) + 1)
                Do While y < lastColumn + 1
                    'this determines what column as what title, therefore needs to be mapped To what Field
                    If (fieldArray(0, x) = parseArray(y)) Then
                        fieldArray(0, x) = y
                        GoTo jump
                    Else
                        y = y + 1
                    End If
                Loop
            jump:
                x = x + 1
                y = 1
            Loop   
            k = k + 1        
        Else
            'we are importing data
            x = 0
            Print k
            Set doc = db.CreateDocument
            doc.Form = formName
            Do While x < (UBound(fieldArray, 2) + 1)
                'for each column in array
                If Not (fieldArray(0,x)) = "" Then
                    y = CInt(fieldArray(0,x))
                    'Below will bring in each column value as mapped to the Field (above)
                    Call doc.ReplaceItemValue(fieldArray(1,x), parseArray(y))
                End If
                x = x + 1
            Loop
            Call doc.Save(True, False)
            k = k + 1
        End If
    Loop
    Close fileNum%
End Sub

(I might need to do a little clean up on it, I think I have a few spare Dims)

The column names on the two spreadsheets do not have to be in the same order, as you can see in the pictures, but the column titles to have to be the same. I have not made any attempt to cast case or anything, so they need to be the same case.

I've not done a lot of testing on this yet, I'm relaying on the fact the process worked great in it's previous incarnation. Hopefully this will help someone else. I have discovered that the CSV needs to be clean.

Hopefully this will help someone.

Cheers,
Brian

Tuesday, February 16, 2016

REST via Service Bean


This is based on Custom REST service in Xpages using a service bean by Stephan Wissel. But it doesn't show actually getting prints from the method sent. My work partner Brian Hester  and I both tried at the same time and ended up getting it at the same time – we actually started IM-ing each other that we had it. And this is the day before Bernd Hort posted his presentation.

So to intercept the REST methods, so you can get prints like this:

02/11/2016 09:06:26 AM HTTP JVM: renderService
02/11/2016 09:06:26 AM HTTP JVM: rType: GET
02/11/2016 09:06:40 AM HTTP JVM: renderService
02/11/2016 09:06:40 AM HTTP JVM: rType: POST

Once you get the method, you can have the bean do whatever you would like. It's nice to have it all in one place.

Here is the bean:
package com.companyname;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ibm.domino.services.ServiceException;
import com.ibm.domino.services.rest.RestServiceEngine;
import com.ibm.xsp.extlib.component.rest.CustomService;
import com.ibm.xsp.extlib.component.rest.CustomServiceBean;

public class DynamicViewService extends CustomServiceBean {

public void DynamicViewService() {

System.out.println("init...");

}

@Override
public void renderService(CustomService service, RestServiceEngine engine) throws ServiceException {
System.out.println("renderService");
HttpServletRequest request = engine.getHttpRequest();
HttpServletResponse response = engine.getHttpResponse();

response.setHeader("Content-Type", "application/json; charset=UTF-8");

// Here goes your code, get the response writer or stream
String rType = request.getMethod();
System.out.println("rType: " + rType);
try {
response.getWriter().write("<html><body>" + rType + "</body></html>");
response.getWriter().close();
return;
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println(e.toString());
return;
}
}

}


And here is the full Xpage:

<?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">
<xe:restService id="JSONSearch" pathInfo="json" state="false">
<xe:this.service>
<xe:customRestService
contentType="application/json"
serviceBean="com.randstadusa.DynamicViewService">
</xe:customRestService>
</xe:this.service>
</xe:restService>
<xp:br></xp:br>
<xp:br></xp:br>
</xp:view>

Wednesday, January 13, 2016

Reversing the display order of a Multi-value field (XPages)

Today I needed to reverse the display of a multi-value field. It's a log of actions and we needed to show the most recent on top rather then the first added. JavaScript arrays have a reverse() function but when I took the vector I got back it was coming in as an object not an array. I didn't want to spend more time on it, so I decided to reverse the elements in the vector.  I'm showing the results in a repeat.

So what I decided to do was reverse the vector. Below is my code to do it. It takes one vector and puts all the elements into a new vector and returns that:

  var iVector = new java.util.Vector();
    iVector = SSJSgetItemValueSet(doc, "lastresult", iVector);
    var oVector = new java.util.Vector();
    for(var nV=(iVector.size()-1); nV >= 0; nV--){
        oVector.addElement(iVector.elementAt(nV));
    } 

return oVector;
 
SSJSgetItemValueSet is a function I have in a library to assure that I get a vector from a NotesItem. Here is that function:

   function SSJSgetItemValueSet(iDoc:NotesDocument, iItemName:String, iVector:java.util.Vector) {
    //this is designed to see if there is any value in the field, and if so, to get all of it.
    //if there is only one value, still put it in a vector
    //if null, put null in as the value
    //java.util.Vector.size() is the # of elements in the vector
    //call as: iVector = SSJSgetItemValueSet(nDoc, approvedField, iVector);
    //this overloaded method is for when we want to do this from an XPage, and we can't pass a Notes object (like a Doc) into a bean,
    iVector = null; // always set to null
        try {
            if (iDoc.hasItem(iItemName)) {               
                var iItem:NotesItem = iDoc.getFirstItem(iItemName);
                var passObj = getValueAsVector(iItem.getValues());
                iVector = passObj;
            } else {
                iVector = null;
            }
        } catch (e) {
            e.toString();
        }   
    return iVector;
}
Hopefully this will be useful for someone.

Cheers,
Brian


Monday, December 7, 2015

nhttp preview won't "switch ID"

With XPages I use the nhttp preview a lot. Today I wrapped up work using one ID and switched to another one. I had made a simple change and was checking on it and it wouldn't load. The error was that it couldn't open a database - both were on my local, and working for months. It turns out that the nhttp preview was the problem. I shut it down and relaunched the clients and it worked.

I wonder if nhttp has a reload command...

Cheers,
Brian

Wednesday, November 4, 2015

DirectoryNavigator via Java

So looking into something, I discovered there has been an addition called a "DirectoryNavigator", it's supposed to make it easier to get to person docs in the NAB. I needed something like this, but in Java - the examples are in LotusScript, and I'm working in a bean. Those examples are "TBD". I did get it working, so I'm sharing. It looks like this came out in R8.

What you do is create a vector with the field names you want to retrieve, and you create a vector with what you want to look up (names). You check to see if there is a match, and if so, iterate through the resultset and get the items retrieved. Here is a snippet of the code (dir is the NAB):

            Vector<String> itV = new Vector<String>();
            itV.addElement("ShortName");
            itV.addElement("HTTPPassword");
            itV.addElement("FullName");

            Vector<String> nameV = new Vector<String>();
            nameV.addElement("Smith");

            System.out.println("before dirNav");
            // DirectoryNavigator dirNav = dir.l
            DirectoryNavigator dirNav = dir.lookupNames("$Users", nameV, itV, true);

            while (dirNav.isMatchLocated()) {
                System.out.println("found");
                // dirNav.findFirstName();
                System.out.println("The first value is: " + dirNav.getFirstItemValue());
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("Next value is: " + dirNav.getNextItemValue());
                    } catch (Exception divE) {
                        System.out.println("in catch");

                    }
                }
                dirNav.findNextMatch();
            }

 
Here is the print results. I've obscured the names, but we get the hierarchical names, and the common names.

10/28/2015 08:45:00 AM  HTTP JVM: starting
10/28/2015 08:45:00 AM  HTTP JVM: new start
10/28/2015 08:45:00 AM  HTTP JVM: before dirNav
10/28/2015 08:45:00 AM  HTTP JVM: found
10/28/2015 08:45:00 AM  HTTP JVM: The first value is: [lsmith]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: [(390ACEC884A01BFDF5FA36AE5E6B29B1)]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: [CN=RRRRRRRRR]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []
10/28/2015 08:45:00 AM  HTTP JVM: found
10/28/2015 08:45:00 AM  HTTP JVM: The first value is: [RSmith, smithr]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: [(CBA717BC74064A8F7EC075DA95ACB8F6)]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: [CN=TTTTTTTTTTTT]
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []
10/28/2015 08:45:00 AM  HTTP JVM: Next value is: []


 You can see I cheated and did hard coded 5 cycles - so when there is nothing to display, you get an empty array. 

This may be useful for someone.

Cheers,
Brian


PS: If whoever does the Notes help wants to use this, or have me do the rest of the methods, I'll volunteer. BM

Wednesday, August 12, 2015

Bootstrap Progress Bars - sample database

I've been digging into Bootstrap for a bit now. One of the useful components is the progress bar. David Leedy did a great NotesIn9 on them and I followed it, and it was greatly valuable. I took his work and made a sample database. This is all his work, I just transcribed it (I changed the viewScope variable names however). Hopefully having this in a sample database will be useful.

Download the file

Cheers,
Brian




Tuesday, July 14, 2015

dataTable column width

In my last post, I'd say I'd look into controlling the column width. It's a setting:

<xp:column
  id="column4"

 style="width:5.0%">

so that was easy.

Cheers,
Brian

dataTable with Categories

I've been using DataTables for a number of things lately, where I need a bit more flexibility than a viewPanel, but I don't want to build it from scratch with a Repeat.

I'm currently working on process where I'm making a nsf to replace a MS Access database. So I'm having to adjust from the relational method. This means I need to use a key to look things up in a view to display the human friendly words rather than the code. So traditional Notes views don't do that. DataTables give me the flexibility I need. For part of this, I have 'events', and these will have one to several classes at each of these events. I want to hide the classes for each event until I click something to make it look neater and to save space.

DataTables don't have a native way to show categories, so I found one. I put a repeat in a column, and populate that repeat with a NotesViewEntryCollection from a value in a row of the DataTable. I tried to use the plus/minus icons like shown here in a great post by Ulrich Krause. But when I paged the icons disappeared. Both of them. So I used the basic idea, and put a link in that was always there to show the classes, and another link to hide them if desired. It works like I desire, so fine.

So this does what I wanted. I'm putting the code below. I have a few things to adjust. First, the classes should be in a table so they display better (I'm not putting it in to make the code a little shorter). Also, the columns resize when the repeat is expanded. Something I'll look into fixing after I post this.

Cheers,
Brian

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.data>
        <xp:dominoView
            var="view1"
            viewName="Event\Name">
            <xp:this.databaseName><![CDATA[#{javascript:var curServer = @Subset(@DbName(),1);
if(@Left(curServer.toLowerCase(), "/") == "cn=svrname") {
    session.getDatabase('
svrname/OU', 'foldername\\Data.nsf');
} else {
    session.getDatabase('', '
foldername\\Data.nsf');
}}]]></xp:this.databaseName>
        </xp:dominoView>
    </xp:this.data>
    <xp:dataTable
        id="dataTable1"
        rows="30"
        var="rowData"
        value="#{view1}">
        <xp:this.facets>
            <xp:pager
                partialRefresh="true"
                layout="Previous Group Next"
                xp:key="header"
                id="pager1">
            </xp:pager>
            <xp:pager
                partialRefresh="true"
                layout="Previous Group Next"
                xp:key="footer"
                id="pager2">
            </xp:pager>
        </xp:this.facets>
        <xp:column id="column1">
            <xp:this.facets>
                <xp:label
                    value="Event Name"
                    id="label1"
                    xp:key="header">
                </xp:label>
            </xp:this.facets>
            <xp:link
                escape="true"
                id="link1">               
            </xp:link>
            <xp:text
                escape="true"
                id="computedField4">
            <xp:this.value><![CDATA[#{javascript:rowData.getColumnValues()[0];}]]></xp:this.value></xp:text></xp:column>
        <xp:column id="column2">
            <xp:text
                escape="true"
                id="computedField5">
            <xp:this.value><![CDATA[#{javascript:rowData.getColumnValues()[4];}]]></xp:this.value></xp:text>
           
            <xp:this.facets>
                <xp:label
                    value="Date"
                    id="label2"
                    xp:key="header">
                </xp:label>
            </xp:this.facets>
        </xp:column>
        <xp:column id="column3">
        
            <xp:this.facets>
                <xp:label
                    value="Group"
                    id="label3"
                    xp:key="header">
                </xp:label>
            </xp:this.facets>
            <xp:text
                escape="true"
                id="computedField6">
            <xp:this.value><![CDATA[#{javascript:rowData.getColumnValues()[3];}]]></xp:this.value></xp:text></xp:column>
        <xp:column id="column4">           
            <xp:this.facets>
                <xp:label
                    value="Number"
                    id="label4"
                    xp:key="header">
                </xp:label>
            </xp:this.facets>
            <xp:text
                escape="true"
                id="computedField7">
                <xp:this.value><![CDATA[#{javascript:rowData.getColumnValues()[2];}]]></xp:this.value>
                <xp:this.converter>
                    <xp:convertNumber
                        type="number"
                        integerOnly="true">
                    </xp:convertNumber>
                </xp:this.converter>
            </xp:text></xp:column>
        <xp:column id="column6">
        <xp:panel id="mainpanel">   
            <xp:link
                escape="true"
                text="Classes"
                id="link6">
            <xp:eventHandler
                event="onclick"
                submit="false">
                <xp:this.script><![CDATA[var visibility = 'show';
XSP.partialRefreshGet("#{id:mainpanel}", {
params: {'$$xspsubmitvalue': visibility},
onComplete: function () {
    XSP.partialRefreshGet("#{id:secondpanel}", {
        params: {'$$xspsubmitvalue': visibility}});}
});]]></xp:this.script>
            </xp:eventHandler></xp:link>
        </xp:panel></xp:column>
        <xp:column id="column5">
            <xp:this.facets>
                <xp:label
                    value="Courses"
                    id="label5"
                    xp:key="header">
                </xp:label>
            </xp:this.facets>
            <xp:panel id="secondpanel">
            <xp:repeat
                id="repeat1"
                rows="30"
                rendered="#{javascript:context.getSubmittedValue()== 'show'}"
                var="rptRowData">
                <xp:this.value><![CDATA[#{javascript:var curServer = @Subset(@DbName(),1);
if(@Left(curServer.toLowerCase(), "/") == "cn=svrname") {
    var dataDb:NotesDatabase=session.getDatabase('
svrname/ou', 'foldername\\Data.nsf');
} else {
    var dataDb:NotesDatabase=session.getDatabase('', '
foldername\\Data.nsf');
}

var v:NotesView=dataDb.getView('Class Instance\\Event-Course');
var nvec:NotesViewEntryCollection=v.getAllEntriesByKey(rowData.getColumnValues()[2] + '', true);
return nvec;}]]></xp:this.value>
                <xp:text
                    escape="true"
                    id="computedField1">
                    <xp:this.value><![CDATA[#{javascript:rptRowData.getColumnValues()[1];}]]></xp:this.value>
                </xp:text>
                &#160;&#160;&#160;
                <xp:text
                    escape="true"
                    id="computedField2">
                    <xp:this.value><![CDATA[#{javascript:rptRowData.getColumnValues()[2];}]]></xp:this.value>
                    <xp:this.converter>
                        <xp:convertNumber
                            type="number"
                            integerOnly="true">
                        </xp:convertNumber>
                    </xp:this.converter>
                </xp:text>
                &#160;&#160;&#160;&#160; &#160;
                <xp:text
                    escape="true"
                    id="computedField3">
                    <xp:this.value><![CDATA[#{javascript:var cNumV:NotesView=database.getView('Course Number');
var cNVE:NotesViewEntry=cNumV.getEntryByKey(rptRowData.getColumnValues()[1], true);
if(cNVE != null){
return cNVE.getColumnValues()[1];
}
return "Course Name not found";}]]></xp:this.value>
                </xp:text>
                &#160;&#160;&#160;&#160;&#160;&#160;
            </xp:repeat>
            <xp:link
                escape="true"
                text="Close"
                rendered="#{javascript:context.getSubmittedValue()== 'show'}"
                id="link5">
                <xp:eventHandler
                    event="onclick"
                    submit="false">
                    <xp:this.script><![CDATA[var visibility = 'hide';
XSP.partialRefreshGet("#{id:mainpanel}", {
    params: {'$$xspsubmitvalue': visibility},
    onComplete: function () {
    XSP.partialRefreshGet("#{id:secondpanel}", {
    params: {'$$xspsubmitvalue': visibility}});}
});]]></xp:this.script>
                </xp:eventHandler>
            </xp:link>
            </xp:panel>
        </xp:column>
    </xp:dataTable>
    <xp:br></xp:br>
    </xp:view>