break out of the box - part 2

53
MWLUG 2014 Break out of The Box – Part 2 Integrate existing Domino Data with modern websites Karl-Henry Martinsson Demand Better Solutions, LLC

Upload: karl-henry-martinsson

Post on 10-Jan-2017

174 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Break out of The Box - Part 2

Break out of The Box – Part 2Integrate existing Domino Data with modern websites

Karl-Henry MartinssonDemand Better Solutions, LLC

Page 2: Break out of The Box - Part 2

I AM... Swede living in Dallas, TX Developer at Demand Better Solutions, LLC Programming since 1982 Web developer since 1994 Notes/Domino developer since 1996 IBM Champion since 2014

http://www.demandbettersolutions.comhttp://www.texasswede.comhttp://blog.texasswede.com

Page 3: Break out of The Box - Part 2

My Story Old infrastructure

• Domino 8.5.2 server and Notes 8.5.2 Basic client Company unwilling to upgrade

• Memory requirements for Standard client• Citrix environment – limited memory available• Upper management thinks Notes/Domino is legacy

Management requests• New web applications with modern look-and-feel• Future mobile support (phone/tablet)

Page 4: Break out of The Box - Part 2

Bootstrap and jQuery to the Rescue!

Page 5: Break out of The Box - Part 2

Why Not Xpages? Infrastructure not supporting XPages (Domino 8.5) No XPages development skills Tight deadline – no time to learn Better control of versions – not having to wait for IBM Access to frameworks not yet in Domino (e.g. jQuery) Use time-saving jQuery/Bootstrap plugins

Page 6: Break out of The Box - Part 2

Why Integrate? Limited or no migration options Desire to modernize front-end Code re-use Take advantage of the power of Domino Use existing skillset (Domino, Lotusscript) Learn valuable new skills (Javascript/jQuery/Ajax) No one will know you are using a Domino backend!

Page 7: Break out of The Box - Part 2

How does it work?

.NSF

HTML and CSS jQuery (Javascript) Choice of framework

• Bootstrap• jQuery UI / jQuery Mobile• and many other...

Process Ajax from browser Generate and return JSON

• Lotusscript agents• XPages agents• ?ReadViewEntries• ExtLib REST Service control

Ajax call

JSON data

Page 8: Break out of The Box - Part 2

Server – IBM Domino Lotusscript agents NSF databases Existing business logic Can use existing classes and script libraries

Works on Domino version 5.0 and higher

Update to recent version highly recommended

Page 9: Break out of The Box - Part 2

Where does everything live? HTML pages, CSS files and Javascript

• Notes page element• Notes resources • CDN (.js and .css)• Server file system – in Data/Domino/HTML• Another web server

Lotusscript agents• .NSF database on Domino server

Page 10: Break out of The Box - Part 2

Development Tools Domino Designer Browser with Dev Tools

• Firefox with Firebug plugin• Internet Explorer Developer Tools (built-in)• Chrome Developer Tools (built-in)

Online resources• JSONLint • Stack Overflow• Google Search

Page 11: Break out of The Box - Part 2

What is jQuery? Fast, small and feature rich Javascript library Simplify using Javascript on a web page Available as CDN and as downloaded local files Three code streams:

• jQuery 1.x Supporting all browsers

• jQuery 2.x Removed support for IE 8 and earlier Smaller and faster

• jQuery 3.x Continuation of jQuery 2.x Some breaking functionality – e.g. in Ajax calls Only version to get new functionality

Free to use (MIT license)

Page 12: Break out of The Box - Part 2

What is Bootstrap? Developed by Twitter Open source front-end framework Mainly CSS, with some jQuery Cross-browser Responsive (out-of-the-box in version 3.0+) Supports themes, color schemes and plugins Available through CDN or as local files Many 3rd party resources and plugins Current version 3.3.7, version 4 is in early testing

Page 13: Break out of The Box - Part 2

What does Bootstrap look like? Attractive look out of the box Themes used to modify colors and controls Override with your own CSS for customization Many free and premium add-ons available

Bootstrap 2 using the theme “United”, with additional CSS for page curls.

Bootstrap 3 using the default theme.

Page 14: Break out of The Box - Part 2

How do you use jQuery and Bootstrap? Link to .js and .css files in HTML header

• Download files locally• Use CDN for easy linking – my preferred approach

Start writing your code!

Tips: Use // without protocol to use HTTP or HTTPS as needed. ViewPort meta tag helps with scaling of page. Never edit the Bootstrap CSS file (even if local), always override the CSS.

<meta name="viewport" content="width=device-width, initial-scale=1"><title>Demo 1 - MWLUG 2016</title><script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script><script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script><link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><link href="demo1.css" rel="stylesheet">

Page 15: Break out of The Box - Part 2

Ajax – Asynchronous Javascript and XML Asynchronous – call is processed in background Result is passed back when done, then processed Data can be returned in different formats:

• XML – original format, rarely used anymore• JSON –now more common, easy to parse in Javascript• JSONP – JSON with Padding, when calling other servers• Can also return plain text or HTML

Using only a few lines of jQuery code Supported as data source directly in many plugins

Page 16: Break out of The Box - Part 2

Ajax call using jQuery

Arguments passed as JSON cache: false – “cache buster”

$.ajax({ url: '/websites/example.nsf/ajax_GetUserDetails?OpenAgent', data: {name: userName}, cache: false}).done(function(data) { // Process returned data here}).fail(function(e) { // Process failed call here}).always(function() { // This code will always execute});

or$.ajax({ url: '/websites/example.nsf/ajax_GetUserDetails?OpenAgent', data: {name: userName}, cache: false}).success(function(data) { // Process returned data here});

Page 17: Break out of The Box - Part 2

JSON – JavaScript Object Notation Describe data as Javascript objects Preferred to XML in web applications

• Less “chatty” (less bandwidth)• Very easy to access values directly in Javascript

Supports all Javascript data types Can contain arrays of objects Can be very complex if needed Use JSONLint to validate!

Page 18: Break out of The Box - Part 2

JSON Examples

Page 19: Break out of The Box - Part 2

Generate JSON on Domino ?ReadViewEntries&OutputFormat=JSON

• Available in Domino 7.0.2+• Can be hard to parse

Formula in Notes view• http://www.eknori.de/2011-07-23/formula-magic/

Lotusscript agent• Generate your own JSON• Test at JSONLint.com• Use JSON classes

Class.JSON (available in demo database) JSON Lotusscript Classes by Troy Reimer (on OpenNTF.org)

Page 20: Break out of The Box - Part 2

Generate JSON on Domino – Xpages Style XPages agent (SSJS XAgent)

• Domino 8.5.2 and XPages knowledge• Better performance than Lotusscript• http://www.wissel.net/blog/d6plinks/shwl-7mgfbn

REST Services control from Extension Library• Domino 8.5.2 and ExtLib on server

Page 21: Break out of The Box - Part 2

Demo 1Table using bootstrap-table plugin

Page 22: Break out of The Box - Part 2

Bootstrap-table plugin Plugin by @wenzhixin Get it at http://bootstrap-table.wenzhixin.net.cn CDN hosted version at CloudFlare.com Minimal HTML markup Javascript mainly to define columns and settings

Page 23: Break out of The Box - Part 2

Bootstrap-table – Add to the webpageHTML<div id="tableToolbar">

<div class="toolbarText">My Contacts</div></div><table id="ContactTable"></table>

jQuery (partial)$("#ContactTable").bootstrapTable({ url: 'ajax_Demo7_GetAllContacts?OpenAgent', search: true, showRefresh: true, pagination: true, pageSize: 25, classes: "table-condensed table-hover table-striped tableContent", toolbar: "#tableToolbar", columns: [{ field: 'FirstName', title: 'First Name', width: 80, sortable: true }, { field: 'LastName', title: 'Last Name', width: 90, sortable: true }, { … … …

Page 24: Break out of The Box - Part 2

Bootstrap-table – Generate JSON dataLotusscript code (partial)'*** Get all documents in view to processSet db = session.CurrentDatabaseSet view = db.GetView("(LookupContactsByLastName)")Set col = view.AllEntries'*** Start of JSON stringjsonString = “”'*** Loop through all entries and build JSON to returnSet entry = col.GetFirstEntryDo Until entry Is Nothing '*** Build JSON for each entry and add to string Set json = New JSONData() Call json.SetValue("LastName", CStr(entry.ColumnValues(0))) Call json.SetValue("FirstName", CStr(entry.ColumnValues(1))) Call json.SetValue("Company", CStr(entry.ColumnValues(2))) Call json.SetValue("Address", CStr(entry.ColumnValues(3))) Call json.SetValue("City", CStr(entry.ColumnValues(4))) Call json.SetValue("State", CStr(entry.ColumnValues(5))) Call json.SetValue("ZIP", CStr(entry.ColumnValues(6))) Call json.SetValue("DocUNID", CStr(entry.ColumnValues(9))) '*** Add new JSON to existing JSON string jsonString = jsonString + json.GetJSON() + "," + Chr$(13) Set entry = col.GetNextEntry(entry)Loop'*** Remove the trailing comma and line break if we have dataIf Len(jsonString) > 0 then jsonString = Left$(jsonString,Len(jsonString)-2)End If'*** Add brackets for arrayjsonString = "[ " + Chr$(13) + jsonString + Chr$(13) + “ ]“'*** MIME Header to tell browser what kind of data we will sendPrint "content-type: application/json"'*** Send JSON back to browserPrint jsonString

Page 25: Break out of The Box - Part 2

Bootstrap-table – Examine returned JSON

Page 26: Break out of The Box - Part 2

Bootstrap-table – The Result

Page 27: Break out of The Box - Part 2

Demo 2Contact database using Bootstrap

Page 28: Break out of The Box - Part 2

Contact database - Overview Display contacts – use Demo 1 as base Click on user in table to display details Buttons

• Edit • Save • New • Delete

Add refresh/reload of contacts when updated

Page 29: Break out of The Box - Part 2

Contact database – Add elements Lotusscript agents

• ajax_GetAllContacts• ajax_GetContactDetails• ajax_SaveContact

If DocUNID is blank, create new contact Otherwise update existing contact

• ajax_DeleteContact HTML page changes

• Add section for contact details• Detect click on row to display details• Add buttons and jQuery code to call Lotusscript agents

Page 30: Break out of The Box - Part 2

HTML – buttons<button class="btn btn-sm btn-primary" id="btnNewContact">New</button><button class="btn btn-sm btn-primary" id="btnEditContact">Edit</button><button class="btn btn-sm btn-success" id="btnSaveContact">Save</button><button class="btn btn-sm btn-danger pull-right" id="btnDeleteContact">Delete</button>

jQuery – Edit and New buttons//*** Button actions$("#btnEditContact").on("click", function(e) { setEditMode();});

$("#btnNewContact").on("click", function() { setEditMode(); // Empty all input fields $('input[data-notesfield]').each( function() { $(this).val(""); }); // Empty hidden DocUNID field $("#docUNID").attr("data-UNID",""); // Hide ‘Delete’ button $("#btnDeleteContact").hide();});

Contact database – New buttons

Page 31: Break out of The Box - Part 2

Contact database – New buttonsjQuery – Save button$("#btnSaveContact").on("click", function() { $("#btnSaveContact").hide(); var json = new Object(); // Store field values in JSON object var docunid = $("#docUNID").attr("data-UNID"); json["DocUNID"] = docunid; $('input[data-notesfield]').each( function() { var id = $(this).attr("id"); var notesfield = $(this).attr("data-notesfield"); json[notesfield] = $(this).val(); }); // Perform a call to the server to save values $.ajax({ url: "ajax_Demo8_SaveContact?OpenAgent", type: "POST", data: json }).done(function(data) { if (data.status=="error") { alert("Failure: " + data.msg); } else if (data.status=="success") { setReadMode(); // Convert INPUT back to DIV $("#contactTable").bootstrapTable("refresh", {silent: true}); }).fail( function(e) { alert("Failure!","Failed to save contact. Error: " + e.errorThrown); }); $("#btnEditContact").show(); });

Page 32: Break out of The Box - Part 2

Contact database – New buttonsjQuery – Delete button$("#btnDeleteContact").on("click", function(e) { var docunid = $("#docUNID").attr("data-UNID"); $.ajax({ url: "ajax_Demo8_DeleteContact?OpenAgent", type: "POST", data: {DocUNID: docunid } }).done(function(data) { if (data.status=="error") { alert("Failure: " + data.msg); } else if (data.status=="success") { $("#contactTable").bootstrapTable("refresh", {silent: true}); // Empty all input fields $('input[data-notesfield]').each( function() { $(this).val(""); }); // Empty all div with Notes data $('div[data-notesfield]').each( function() { $(this).html(""); }); // Empty hidden DocUNID storage $("#docUNID").attr("data-UNID","") $("#btnDeleteContact").hide(); $("#btnEditContact").hide(); } }).fail( function(e) { alert("Failure!","Failed to delete contact. Error: " + e.errorThrown); });});

Page 33: Break out of The Box - Part 2

Contact database – Detect click on rowjQuery – Detect click on table row// Detect click on row in table$("#contactTable").on('click-row.bs.table', function (e, row, $element) { // Convert INPUT fields back to DIV just in case setReadMode(); // Hide save button if visible $("#btnSaveContact").hide(); // Get DocUNID value in table and load corresponding values from server var unid = row.DocUNID; displayDetails(unid);});

Page 34: Break out of The Box - Part 2

Contact database – Load detailsjQuery – Load contact details from server and display on page// Get contact details from Domino server and populate fields// using the DocUIND value as lookup keyfunction displayDetails(docunid) { $.ajax({ url: 'ajax_Demo8_GetContactDetails?OpenAgent', data: {DocUNID: docunid}, cache: false }).success(function(data) { if(data.status=="success") { // For each element with data-notesfield attribute $('div[data-notesfield]').each( function() { notesfield = $(this).attr("data-notesfield"); if (data[notesfield]!=null) { fieldvalue = data[notesfield]; $(this).html(fieldvalue); } }); // Store DocUNID in enmpty div for later use $("#docUNID").attr("data-UNID",data.DocUNID); // Display previously hidden editand delete buttons $("#btnEditContact").show(); $("#btnDeleteContact").show(); } });}

Page 35: Break out of The Box - Part 2

Contact database – Fancy stuffjQuery – Switch between DIV (plan text) and INPUT (editable)// Put contact details into edit mode by changing DIV to INPUTfunction setEditMode() { $("#btnEditContact").hide(); // Change all div with Notes data to input $('div[data-notesfield]').each( function() { var id = $(this).attr("id"); var notesfield = $(this).attr("data-notesfield"); var input = "<input class='jsonData inputNotesField form-control input-sm' id='" + id input = input + "' data-notesfield='" + notesfield + "' value='" + $(this).html() + "'></input>"; $(this).replaceWith(input) }); $("#btnSaveContact").show(); $("#btnEditContact").hide(); }

// Put contact details into read mode by changing INPUT to DIVfunction setReadMode() { $('input[data-notesfield]').each( function() { var id = $(this).attr("id"); var notesfield = $(this).attr("data-notesfield"); var div = "<div class='jsonData displayNotesField' id='" + id div = div + "' data-notesfield='" + notesfield + "'>" + $(this).val() + "</div>"; $(this).replaceWith(div) });}

Page 36: Break out of The Box - Part 2

Contact database

Page 37: Break out of The Box - Part 2

Demo 3Contact database using jQuery Mobile

Page 38: Break out of The Box - Part 2

jQuery UI and jQuery Mobile UI Frameworks jQuery UI – alternative to Bootstrap

• Set of components Widgets Effects Interactions

• Built on top of jQuery jQuery Mobile – emulates native mobile app look

• Header/footer• Grid layout• Buttons• Listview widget• Forms• …and much more!

Page 39: Break out of The Box - Part 2

Contact database – jQuery Mobile Reuse agents from previous demo New HTML code

• Multi page application with two pages Contact list using Listview widget Contact details using form elements

New Javascript/jQuery• Some code can be reused!

Notification overlay when saved• Using notifIt! plugin• Can use other plugins, many available!

Page 40: Break out of The Box - Part 2

Contact database – Android, iOS, WinMobile Create native apps using PhoneGap One source, multiple targets Create folder structure on disk

• .html, .css and .js• config.xml in root folder

Create ZIP file Upload to PhoneGap Build Download finished apps

• Android• iOS• Windows Mobile

Page 41: Break out of The Box - Part 2

Demo 4Calendar using FullCalendar plugin

Page 42: Break out of The Box - Part 2

FullCalendar plugin Get it at http://fullcalendar.io Lotusscript agents

• ajax_Calendar_GetAllEvents Returning events between specific days Calendar automatically sends start and end date

• ajax_Calendar_GetEventDetails• ajax_Calendar_UpdateEvent

Triggered when moving event or changing duration Arguments: DocUNID, start date and end date

Page 43: Break out of The Box - Part 2

FullCalendar plugin Calendar points to JSON of event data Automatically sends start and end dates Agent returns only events within date range

jQuery – Display calendar and load JSON of event data from server var eventSource = 'ajax_Calendar_GetAllEvents?OpenAgent';$("#notesCalendar").fullCalendar({ events: eventSource});

Page 44: Break out of The Box - Part 2

FullCalendar pluginLotusscript agent ajax_Calendar_GetAllEvents'*** Local variables to hold arguments passed from URLDim startdate As StringDim enddate As String'*** Other local variablesDim jsontext As String

'*** Create new URLData objectSet url = New URLData()'*** Create new JSONData objectSet json = New JSONData()

'*** Check start date and convert from ISO to US date formatIf url.IsValue("start") Then startdate = ISOtoUS(url.GetValue("start"))Else startdate = "01/01/1980"End If

'*** Check end date and convert to US date formatIf url.IsValue("end") Then enddate = ISOtoUS(url.GetValue("end"))Else enddate = "12/31/2199"End If

Page 45: Break out of The Box - Part 2

FullCalendar pluginLotusscript agent ajax_Calendar_GetAllEvents (continued)'*** Send MIME header to browserPrint "content-type: application/json"jsontext = ""Set db = session.CurrentDatabaseSet view = db.GetView("Events")Set col = view.AllEntriesSet entry = col.GetFirstEntry()Do Until entry Is Nothing If CDat(entry.ColumnValues(0))>=CDat(startdate) Then If CDat(entry.ColumnValues(0))<=CDat(enddate) Then Call json.SetValue("id", CStr(entry.ColumnValues(5))) Call json.SetValue("title",CStr(entry.ColumnValues(3))) Call json.SetValue("start", Format$(CDat(entry.ColumnValues(0)),"mm/dd/yyyy hh:nn ampm")) Call json.SetValue("end", Format$(entry.ColumnValues(1),"mm/dd/yyyy hh:nn ampm")) '*** Make the entry editable in calendar (allow changing date/time) Call json.SetBoolean("editable", True) End If End If jsontext = jsontext + json.GetJSON() + "," + Chr$(13) Set entry = col.GetNextEntry(entry)LoopIf Len(jsontext)>4 Then jsontext = Left$(jsontext,Len(jsontext)-2)End IfPrint "[ " + jsontext + " ]"

Page 46: Break out of The Box - Part 2

FullCalendar pluginFullCalendar plugin – Events triggered on click, resize and drop/move…eventClick: function(calEvent, jsEvent, view) { var unid = calEvent.id; displayEventDetails(unid);},

eventResize: function(event, delta, revertFunc) { if (!confirm(event.title + " will now end at " + event.end.format("h:mm a") + "\nAre you sure?")) { revertFunc(); } else { var unid = event.id; updateEvent(unid,event.start.format("MM/DD/YYYY hh:mm a"),event.end.format("MM/DD/YYYY hh:mm a")); displayEventDetails(unid) }},

eventDrop: function(event, delta, revertFunc) { var prompt = event.title + "<br>was moved to " + event.start.format("MM/DD/YYYY") prompt = prompt + " at " + event.start.format("h:mm a"); bootbox.confirm(prompt + "<br>Are you sure you want to do that?", function(result) { if(result==true) { var unid = event.id; updateEvent(unid,event.start.format("MM/DD/YYYY hh:mm a"),event.end.format("MM/DD/YYYY hh:mm a")); displayEventDetails(unid) } else { revertFunc(); } }); }…

Page 47: Break out of The Box - Part 2

FullCalendar pluginJavascript code – Send event update to serverfunction updateEvent(docunid,startDT,endDT) { var json = new Object(); json["DocUNID"] = docunid; json["EventStart"] = startDT; json["EventEnd"] = endDT; // Perform a call to the server to save new event date/time $.ajax({ url: "ajax_Calendar_UpdateEvent?OpenAgent", type: "POST", data: json }).done(function(data) { if (data.status=="error") { bootstrapAlert(data.msg,"danger"); } else if (data.status=="success") { bootstrapAlert(data.msg,"success"); } }).fail( function(e) { bootstrapAlert("Failed to create progress note. Error: " + e.errorThrown,"danger"); });}

Page 48: Break out of The Box - Part 2

FullCalendar pluginLotusscript agent – ajax_Calendar_UpdateEvent'--- Local variablesDim startDate As StringDim endDate As String

'*** Get documentSet db = session.CurrentDatabaseIf url.GetValue("DocUNID")<>"" Then Set doc = db.GetDocumentByUNID(url.GetValue("DocUNID"))End If'*** Check that we found a document, otherwise exitIf doc Is Nothing Then Set json = New JSONData() json.success = False json.SetErrorMsg("Failed to locate document '" & url.GetValue("DocUNID")) Call json.SendToBrowser() Exit Sub End IfCall doc.ReplaceItemValue("EventStart",CDat(url.GetValue("EventStart")))Call doc.ReplaceItemValue("EventEnd",CDat(url.GetValue("EventEnd")))Call doc.Save(True,False)Set json = New JSONData()json.success = Truejson.SetMsg("Updated '" & doc.GetItemValue("EventTitle")(0) & "' with new date/time")Call json.SendToBrowser()

Page 49: Break out of The Box - Part 2

FullCalendar – End Result

Page 50: Break out of The Box - Part 2

Summary

Page 51: Break out of The Box - Part 2

What have we seen? Ajax/JSON – efficient way to access Domino data CRUD using server based agents (Lotusscript) jQuery and Bootstrap to speed up development

Why learn this? Quickly create attractive and modern web applications Still access existing Domino data with ease Acquire some new and easy-to-learn skills Those skills are beneficial on other platforms as well Many plugins available for free – less work for you! Your customers and/or boss will love you!

Page 52: Break out of The Box - Part 2

Questions?

Page 53: Break out of The Box - Part 2

Thank you!

Karl-Henry MartinssonEmail: [email protected], Skype: @texasswedehttp://www.linkedin.com/in/texasswedehttp://blog.texasswede.com