THANK YOU NOTES IN 9 !!!!!!!!!!!!!

Once upon a time there was a guy who worked for a trucking company and he decided that he needed to learn this new XPages thing – and he figured why not share what I am doing? That was over 7 years ago and it grew into the largest single video repository in the history of IBM Domino. Dave Leedy is coming to the end of his XPages-casting career and is hanging up his cleats in this arena. Not only did Notesin9 lead to more opportunities for Dave, it also helped many many other people.

Dave will not admit it, but http://www.Notesin9.com site has helped more people be successful that he could have ever imagined,

Dave and Notesin9 are a huge reason for my personal success in the last few years and I know I am not alone. Dave’s encouragement and the hosting costs he provided out of his own pocket provided a platform for my success unmatched in my professional career. I owe him a huge debt of thanks personally and many others do too.

So a few friends (44 people!!) and I got together and decided it would be cool to create something memorable that Dave could relate to and I really hope it gives you some idea of the love……

So today we are releasing the unofficial-official #thanksnotesin9 video. I hope you enjoy it. Share it, trend it, it’s worth it to everyone in the IBM Domino community.

I know there are many others who would like to contribute but we had to keep this on the quiet and had to be completed eventually. If you have a thanks video you want to make then link it in the comments below. The more the merrier🙂

Cheers,

Marky

 

PS

make sure you watch it to the end😉

Saving a word document directly from an Office Add-In into Salesforce.com

In this article I will demonstrate and discuss how to programmatically save a Word document directly into Salesforce.

Introduction

In previous articles we have looked at how to programmatically interact with a Word document from within an Office Add-In. In this case we will use the Office JavaScript API to access the file as a binary string and then convert it into a format compatible with Salesforce.com.

Turning your word document into a binary

This article in the Office dev center details how to Get the whole document from an add-in for PowerPoint or Word. We will use the bulk of that article up to the point where we send the slice. We are going to look at a modified sendSlice() function. The dev.office.com article details how you can access the binary contents of the word document, but falls short when it comes to getting the base64 encoded version of the file to be submitted to an external site. This is the complicated part !

Getting the right format for Salesforce

Unfortunately the salesforce documentation is not based around using JavaScript to access the REST APIs. It is based around using curl commands in UNIX. The sample page for Inserting or updating Blob Data explains how to upload a multipart message which includes both the meta-data values and the binary data for the file. For the record they also neglect to provide any guidance on how to get the binary of the file….!

Using the code posted on this stackoverflow post  we are able to turn a binary string into the necessary format to send the file to salesforce.

The final sendSlice() function is shown below. The significant thing to note is that the initial file must be sent to the ContentVersion sobject. Because this is a new document a parentId is not necessary and a ContentDocumentId is created. In a future article we will look at how to then associate this new file attachment is an existing object.

function sendSlice(slice, state) {
  updateStatus("In sendSlice");
  var data = slice.data;

  // If the slice contains data, create an HTTP request.
  if (data) {

    // Encode the slice data, a byte array, as a Base64 string.
    // NOTE: The implementation of myEncodeBase64(input) function isn't
    // included with this example. For information about Base64 encoding with
    // JavaScript, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding.

    var u8 = new Uint8Array(data);
    var b64encoded = btoa(String.fromCharCode.apply(null, u8));

    var objectData = {
      "Description": "Demo Upload Directly from Word",
      "Title": "sample.docx",
      "ReasonForChange": "Initial Upload",
      "PathOnClient": "sample.docx"
    }

    var boundary = 'boundary_string_' + Date.now().toString();
    var attachmentContentType = 'application/octet-stream';

    // Serialize the object, excluding the body, which will be placed in the second partition of the multipart/form-data request
    var serializedObject = JSON.stringify(objectData);

    var requestBodyBeginning = '--' + boundary
        + '\r\n'
        + 'Content-Disposition: form-data; name="entity_attachment";'
        + '\r\n'
        + 'Content-Type: application/json'
        + '\r\n\r\n'
        + serializedObject
        + '\r\n\r\n' +
        '--' + boundary
        + '\r\n'
        + 'Content-Type: ' + attachmentContentType
        + '\r\n'
        + 'Content-Disposition: form-data; name="VersionData"; filename="filler"'
        + '\r\n\r\n';

    var requestBodyEnd =
        '\r\n\r\n'
        + '--' + boundary + '--';

    // The atob function will decode a base64-encoded string into a new string with a character for each byte of the binary data.
    var byteCharacters = window.atob(b64encoded);

    // Each character's code point (charCode) will be the value of the byte.
    // We can create an array of byte values by applying .charCodeAt for each character in the string.
    var byteNumbers = new Array(byteCharacters.length);

    for (var i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i);
    }

    // Convert into a real typed byte array. (Represents an array of 8-bit unsigned integers)
    var byteArray = new Uint8Array(byteNumbers);

    var totalRequestSize = requestBodyBeginning.length + byteArray.byteLength + requestBodyEnd.length;

    var uint8array = new Uint8Array(totalRequestSize);
    var i;

    // Append the beginning of the request
    for (i = 0; i < requestBodyBeginning.length; i++) {
      uint8array[i] = requestBodyBeginning.charCodeAt(i) & 0xff;
    }

    // Append the binary attachment
    for (var j = 0; j < byteArray.byteLength; i++, j++) {
      uint8array[i] = byteArray[j];
    }

    // Append the end of the request
    for (var j = 0; j < requestBodyEnd.length; i++, j++) {
      uint8array[i] = requestBodyEnd.charCodeAt(j) & 0xff;
    }

    //Create the new file in Salesforce
    var url = "https://yourdomain.my.salesforce.com/services/data/v37.0/sobjects/ContentVersion/"

    return $.ajax({
      method: "POST",
      url: url,
      processData: false,
      data: uint8array.buffer,
      headers: {
        "Content-Type": 'multipart/form-data' + "; boundary=\"" + boundary + "\"",
        "Authorization": "Bearer " + app.getCookie(app.addinName)
      }
    }).error(function (data) {
      updateStatus("FAIL: " + JSON.stringify(data))
    }).success(function (data) {
      updateStatus("Success creating ContentVersion: " + data.id)
      closeFile(state)
    });

  }
}

 

Conclusion

In this article we have seen an example of how we can extend the functionality of an Office Add-In to access the binary data of a word document and then save it to a cloud provider (in this case Salesforce).

 

Adding an attachment to an opportunity using Salesforce REST APIs

In this article I will show the simple and straight forward method for adding an attachment to a Opportunity using the Salesforce REST APIs.

Introduction

This article is written as a summation of a frustrating couple of days trying to discern the Salesforce REST API documentation. I started with the Insert or update Blob page. With all the examples based on UNIX commands it made it very difficult to figure out how to do this using client side JavaScript.

Eventually I realized there was a link to the SObject Basic Information help page and SObject Rows help page. Using a combination of all three pages of information as a reference I was able to figure out how to do what I needed to. I needed to write it up as I will likely forget about it😉

Adding attachments

To add an attachment into Salesforce you have to get the base64 encoded string thereof and submit it as an Attachment Object, referencing the parentId of the Opportunity.

In my example I have an opportunity Id = 00658000004kGVRAA2. note that there are no attachments.

sa1

Using the combination of the three help documents I was able to figure out that sending the base64 encoded attachment to the opportunity is nothing more complicated that constructing the following

var root = "https://yourpath.my.salesforce.com/"

var url = root+"services/data/v37.0/sobjects/Attachment"

var data = {
  "Name" : "demoAttachment.pdf",
  "Body": "Base64Encoded Attachment",
  "parentId": "00658000004kGVRAA2" //the id of the opportunity
}

$.ajax({
  url: url,
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer "+OAuthToken
  },
  data: JSON.stringify(data)
})

When the POST is successful you will receive the new Id of the Attached file

sa2

The attachment appears on the opportunity

sa3

and we can download and open the file

sa4

Conclusion

The important part of this example is to understand that we are not creating an attachment from the opportunity. We are creating the attachment and associating itself with the opportunity. Trying to go at the opportunity and throw the attachment at it directly was a complete failure.

Using an Office Add-In to search and replace data in a Word Document

In this article I will demonstrate how we can use an Office Add-In to perform simple search and replace function within a Word document. This is particularly useful when you want to use an external cloud source to insert data into your documents.

Introduction 

This example comes directly from the Microsoft GitHub example on Word Add-In Document Assembly. (https://github.com/OfficeDev/Word-Add-in-DocumentAssembly). The reason I am blogging about it is that it did not appear in any searches for me and I stumbled across it purely by accident.

Filling a document template

Setup

I grabbed a sample word document template from Word Online (https://templates.office.com/en-sg/Formal%20business%20letter-TM00002133) and we are going to replace the items in this document programmatically.

r1

I created a blank Word Add-In locally and then inserted my local FirebugLite capability just to create a quick and easy demo without having to go through the trouble of hosting the code anywhere.

r2

Basic search and replace

The basic code for search and replace is as follows:

function handleSuccess() {
	app.showNotification("Replacement successful", "Success");
}

function handleError(result) {
	app.showNotification("Error", "ErrorCode = " + result.code + ", ErrorMessage = " + result.message);
}
	
Word.run(function (ctx) {

	// Queue a command to search the document for the string "Contoso".
	// Create a proxy search results collection object.
	var results = ctx.document.body.search("[Recipient Name]");      //Search for the text to replace

	// Queue a command to load all of the properties on the search results collection object.
	ctx.load(results);

	// Synchronize the document state by executing the queued commands,
	// and returning a promise to indicate task completion.
	return ctx.sync().then(function () {

	  // Once we have the results, we iterate through each result and set some properties on
	  // each search result proxy object. Then we queue a command to wrap each search result
	  // with a content control and set the tag and title property on the content control.
	  for (var i = 0; i < results.items.length; i++) {
		results.items[i].insertHtml("Marky The Receiver", "replace");     //Replace the text HERE
	  }
	})
	// Synchronize the document state by executing the queued commands.
	.then(ctx.sync)
	.then(function () {
	  handleSuccess();
	})
	.catch(function (error) {
	  handleError(error);
	})
});

Running this example we can see the replacement was successful in both places

r3

r4
So to complete this as an example for the whole document I use a sample data object as it would be returned from a cloud REST provider and cycle through all the elements to be replaced.

function handleSuccess() {
	app.showNotification("Replacement successful", "Success");
}

function handleError(result) {
	app.showNotification("Error", "ErrorCode = " + result.code + ", ErrorMessage = " + result.message);
}

var data = {

	date: "22 Aug 2016",
	sender: "Someone really important",
	company1: "The Boss | Company 1 | Somewhere | Here | There | 12345",
	company2: "The Bigger Boss | Company 2 | Somewhere else | Near | Canada | 98765"
}
	
Word.run(function (ctx) {

	var results = ctx.document.body.search("[Recipient Name]");      //Search for the text to replace
	ctx.load(results);

	return ctx.sync().then(function () {
	  for (var i = 0; i < results.items.length; i++) {
		results.items[i].insertHtml("Marky The Receiver", "replace");     //Replace the text HERE
	  }
	})
	.then(ctx.sync)
	.then(function () {
		var results = ctx.document.body.search("[Date]");      //Search for the text to replace
		ctx.load(results);

		return ctx.sync().then(function () {
		  for (var i = 0; i < results.items.length; i++) {
			results.items[i].insertHtml(data.date, "replace");     //Replace the text HERE
		  }
		})
		.then(ctx.sync)
		.then(function () {
			var results = ctx.document.body.search("[Title | Company | Address | City | State | Zip]");      //Search for the text to replace
			ctx.load(results);

			return ctx.sync().then(function () {
			  
				results.items[0].insertHtml(data.company1, "replace");     //Replace the text HERE
				results.items[1].insertHtml(data.company2, "replace");     //Replace the text HERE
			  
			})
			.then(ctx.sync)
			.then(function () {
				var results = ctx.document.body.search("[Sender Name]");      //Search for the text to replace
				ctx.load(results);

				return ctx.sync().then(function () {
				  for (var i = 0; i < results.items.length; i++) {
					results.items[i].insertHtml(data.sender, "replace");     //Replace the text HERE
				  }
				})
				.then(ctx.sync)
				.then(function () {
				  handleSuccess();
				})
			})
		})
	})
	.catch(function (error) {
	  handleError(error);
	})
});

And here’s the final output

r5

r6

Conclusion

Using the Word JavaScript API through an Office Add-In we are able to use a search and replace technique to take external data and complete a template. This is especially powerful when you consider it in the context of a cloud service integration like O365, or CRM clouds like MS Dynamics or Salesforce.

PSC and LDC Via – The technical reasons for our strategic partnership

I’ve never been the guy who sees the sky as falling, more so the guy who wants to see what is on the other side of the cloud and if it is fun to play with. PSC’s new partnership with LDC Via is most definitely fun to play with. Over the past 5 years PSC has demonstrated time and again what a strong team of developers we have and have been able to consistently deliver client excellence. We have worked on projects from the World’s Largest XPages application to most recently an entirely responsive boostrapped application written on nothing but a notes form and WQS/WQO agents. PSC is still committed to Domino and XPages as a solution to application modernization for clients who are still committed to the IBM Domino platform. We are busy, and confident that there is no end to that in the near future.

As well as six IBM ICS Champions and the largest XPages development consultancy team in North America PSC also has extensive experience in working with SharePoint, .NET, Enterprise Java servers, Office 365, Dynamics CRM, Salesforce and multiple open source systems like Node.js, Mongo, Docker, and Angular.js. As well as on premises solutions we have a large number of cloud based projects in Azure, AWS, Google and Bluemix. Because of this broad understanding of multiple technology vendors, platforms and stacks we are able to offer our clients an honest and strategic vision of what is really best for their unique situation and environment.
So how does a non-Domino solution like LDCVia fit into that?  Beautifully!

While working with our clients to modernize their applications, we have focused on providing flexible solutions, for today and for the future. I started to evangelize the separation of interface from data and logic using Angular.js in April 2014 not only because it felt right, but because our clients were asking for it.

At Connect 2015 I spoke with Mark Leusink on the advantages of modernizing your applications on the Domino platform using this technique because “The application does not need to change when you are ready to move the data and security model”. LDC Via is that “data and security model” which allows me to easily move my modernized Domino applications off platform.

As John Head mentioned in his non-technical reasons to partner with LDC Via, LDC Via fits beautifully into our modernization strategy.

M4

For the presentation at MWLUG last week, it took me a small number of *hours* to transform my demo angular application from using Domino Data Services to using the migrated data on the LDC Via platform. Take a look at http://api.ldcvia.com and you will be able to see how Matt White and the rest of the team have created a fully functioning REST API model which seems very “familiar” to the notes domino development community.

Here is the demo used in last week’s conference presentation.

The end of Domino ?

Not at all. This is not our call to arms to get off the platform, far from it. We still have 8 full time developers working on Domino and XPages for our clients and I don’t see that changing in the near future. Our partnership with LDC Via demonstrates how clients committed to the platform today can partner with PSC and we can deliver solutions on a modern architecture knowing that future options exist. Listening to client concerns and partnering with them to pro-actively prepare for the future is what we do best.

LDC Via is now a first class option when it comes to our modernization efforts and we are very proud to be able to partner with their great team of developers.

This is going to be fun🙂

 

Speaking at SharePointFest Chicago 2016

I am very excited to announce that I have been accepted to speak at SharePointFest, December 8th 2016 in Chicago.

http://www.sharepointfest.com/~spfadmin/Chicago/index.php/sessions/38-sharepoint-development/118-dev103-office-365-add-ins-a-web-developer-s-playground

Title 
DEV 103 – Office 365 Add-Ins: A web developer’s playground

Abstract
Like most office workers, we all spend a significant amount of time in our “Microsoft Office” productivity tools. Even email is still a productivity tool. Productivity starts to diminish though if we have to move outside of our Office environment and hunt for information and/or complete business workflow processes.

With the creation of Office 365 Add-Ins, Microsoft has presented web developers with a new opportunity to create rich, engaging and integrated user experiences without having to leave the “experience” of our Office applications. Developers have the ability to create Add-Ins using HTML/JS/CSS and these run in the Windows Client, on the web, on our phones and even on the OS X desktop client.

In this presentation Mark will provide lots of demonstrations of how to get started with Office Add-Ins. These will include: creating your first Add-In in under 2 minutes, how to simplify workflow approval without having to leave your email client, how to pull report and analytics data into your Office product suite applications, integrating SharePoint as a Service, integration with Salesforce and how to integrate your content with cognitive analytics.

Come to the presentation and find out why Office 365 Add-Ins are a modern web developers playground.