Friday 21 February 2014

Writing a Book

(This post is based on my personal experience writing for Packt - your mileage may vary!)

0808EN MockupCover Cookbook

Since I wrote the Visualforce Development Cookbook I’ve had a number of people,  who are either thinking about writing a book or have been approached by a publisher, ask me what it was like.  Rather than continuing to drip feed this information to individuals, I’ve decided to write a blog post about it. 

There’s lots of information on writing for Packt at the Packt Author Website - I’d recommend spending some time there if you think there’s a book in you.

How It Started

I was asked by Packt to act as a Technical Reviewer for the Salesforce CRM Admin Cookbook. As I was carrying out my tech review duties I started wondering whether I could write one myself, but didn’t take it any further than that.  A month or two after this finished, I was contacted by Packt and asked if I’d be interested in authoring a new book on Visualforce that they were planning to publish later in the year.  After some email exchanges where I found out more about what was involved, I took the plunge and signed up to write the book.

Its a Lot of Work!

Writing a book is rather different to writing occasional blogs and articles.   Once you factor in a full-time day job, it means giving up a large amount of your spare time. I found the best way was to try to write some content every day, so I averaged 1-2 hours every evening and around 10 hours every weekend. I also had a weeks vacation about half-way through the first drafts, and I spent most of the week breaking the back of a chapter. You definitely need an understanding family to take on a project of this nature as you are looking at several hundred hours work. 

There’s a Delivery Schedule

and you have to stick to it.  Most of the time this is fine, but there are times when fitting in the work gets pretty tricky.  Around the time that the first drafts were coming back for corrections, Salesforce hosted the MVP Summit in San Francisco, which was a pretty full couple of days.  The schedule was still there though, so after the summit dinner I found myself writing second drafts until 3am prior to flying back first thing in the morning and producing updated images while boarding was delayed. 

Get Organised!

Each chapter required images, Visualforce pages, Apex classes and sometimes static resources and custom Visualforce components. When I started out I had a single directory that I put everything into and broke up into zip files when I had to submit a chapter.  This became harder and harder to control as the volume of content grew, until it got to the point where I had to spend an afternoon moving everything into dedicated chapter directories, with subdirectories for different drafts, images and the various source items.  This is something I wish I’d thought of at the beginning, as I would have much preferred to spend the time producing new content rather than filing and refiling the existing items.

Be Grateful for Technical Reviewers

Tech Reviewers  provide invaluable feedback - typically they are more experienced than the target audience for the book and they will go through each of the chapters with a fine tooth comb, not only looking for problems but also how to make things better - additions to recipes to make them more reusable for example. Having carried out this task before, I’m well aware of how much effort goes into this.

At times you’ll get annoyed with the comments from your technical reviewers.  Most of the time this comes down to “they don’t understand!”. When you start feeling this way its important to take a step back - if they don’t understand its because you as the author haven’t explained yourself correctly. All the reviewer has to go on are the words that you have written - if an experienced individual can’t grasp the point you are trying to make, what chance does the less experienced reader stand?  

All that said, its important to remember that its your book that goes out with your name on it, so just because a reviewer suggests a change doesn’t mean you have to make it. 

The Book is Published but You’re not Done

Once the book is written and published, you might think that you are done. You aren’t, as now you are into the marketing phase. This is where you’ll need to be publicising the book on Twitter, Facebook, LinkedIn, blogs etc.  The good news is that you don’t have to figure all this out yourself, rather you’ll get a specialist assigned to you by the publisher.

Its Rewarding

Nothing beats the feeling of:

  • seeing your book on sites like Amazon, Safari and Barnes and Noble
  • getting your first print copy with your name on the spine

While its a lot of hard work, I really enjoyed it and I’m hoping to write more in the future.

Any Questions?

If you have any specific questions, please post them in the comments section for this post - if I can answer them I’ll bring them up into a Q&A section.

 

Thursday 13 February 2014

Cross Category Group Knowledge Article Visibility

When implementing Salesforce knowledge, articles can be associated with data categories, which are contained inside data category groups.  You can define up to three active data category groups, and an article can be associated with up to 8 categories per category group.

What might come as a surprise, is the requirement for an article to be visible if it is associated with categories in more than one group.

Consider the following setup of a knowledge base for Bob Buzzard Inc, providing information on Salesforce1 for my developers:

Screen Shot 2014 01 18 at 11 42 39

I have an article on adding a Visualforce mobile card to a page layout that is associated with the Mobile Cards data category, which my developers have access to through their profile, which includes all categories in the group:

Screen Shot 2014 01 18 at 11 47 18

A developer can then see the article via the Knowledge One tab:

Screen Shot 2014 01 18 at 12 08 31

I then set up another data category group for Administrators:

Screen Shot 2014 01 18 at 12 04 47

as my developers and administrators are quite protective of their roles and responsibilities (will they ever get along :), so developers have no access to the admin data category:

Screen Shot 2014 01 18 at 12 06 04

It then strikes me that the article about configuring a Visualforce mobile card would also be of interest to admins, so I add it to the Mobile Cards category in the admins group:

Screen Shot 2014 01 18 at 12 10 16

Once I’ve published this, the developers are straight on the phone complaining that they can no longer see the article:

Screen Shot 2014 01 18 at 12 14 13

all they can see is the test article.

Poring over the knowledge implementation guide presents the following explanation:

"A user can see an article if he or she can see at least one category per category group on the article."

In this case, as my developers can’t see a category in the admins category group, they can’t see the article any more.

Unfortunately, there will be an uprising if I give the developers access to the admin Mobile Cards category, so that isn’t an option for me.  The workaround that I came up with was to add a fake “shared” category to the admins category group:

Screen Shot 2014 01 18 at 12 28 18

and associate the article with this category:

Screen Shot 2014 01 18 at 12 29 43

and finally give my developers access to the shared category:

Screen Shot 2014 01 18 at 12 31 20

Now my developers can see the article again:

Screen Shot 2014 01 18 at 12 32 53

The downside to this is that I’ve burnt two data categories out of my allowance of eight in the admin data category group, and I have to replicate the “shared” category in the developers data category group to allow the admins to see the article.  While its not overly onerous, it still strikes me as odd.  If you agree, please vote up my idea to allow users access where they have access to any category, regardless of group, associated with the article:

https://success.salesforce.com/ideaView?id=08730000000kzqPAAQ

Author’s note: all talk of Salesforce Admins and Developers not getting along is artistic license to ensure that I had to use my workaround.  In reality its one big love-in.

Saturday 1 February 2014

Reading QR Codes in Salesforce1

Updated 22/02/2015 to detail the iPhone 6 experience)

(This post covers something I was pretty sure wouldn’t work, so I’m rather pleased to be writing about the solution)

As part of my Ticket to Ride Dreamforce session, I built a mobile application that scanned a QR code and retrieved some information from Salesforce.  I’ve been trying to figure out since then how I can do the same thing in Salesforce1.

The mobile application used the Google Zebra Crossing (zxing) Cordova plugin, but I don’t have the capability to add plugins to Salesforce1 so that wasn’t an option.  Most solutions I’ve looked into rely on capturing a QR code, uploading to a server and receiving a response containing the decoded value.  I got this working using a combination of Salesforce1 and Node.js, but its a pretty awful user experience, and I really wouldn’t want to use this mechanism when I was connecting over 3G.

Some googling led me to jsqrcode, a port of zxing to JavaScript, which sounded exactly what I was looking for. This processes a captured image client side (or server side, if you use the equivalent node.js package).  Hence the title of this post is “Reading” rather than “Scanning”.  The problem with this approach is it is far slower and more error prone than scanning, as you are into a capture, try to scan, repeat if fail loop rather than re-orienting the device until it can scan the code.  So why do it, I hear you ask?  The big upside is that you remain in the Salesforce1 application.  Thus, while using a third party application (or using the mobile SDK to build my own) improves the scanning user experience and the likelihood of success, the best I can do is send the user to a Salesforce URL in the browser afterwards, as Salesforce1 doesn’t have a URL scheme aside from the chatter functionality.

My experiments with jsqrcode indicate that capturing an image and processing it on a phone just doesn’t work.  I suspect this is a combination of the size of the images captured (3000x2000 pixels) and the lack of processing power on the device.  I’ve tried reducing the size of the image that the library processes, but due to the way that they are prepared for processing (written to a fixed size canvas) the image is downscaled which also causes a problem.  The same images can be processed okay on an iPad  or desktop, which leads me to believe the processing power is the bigger issue.  If you really need to do this then sending the image back to the server is the only way I’ve found that works. Update: 22/02/2015 - trying this on an iPhone 6 with a QR code emailed as an image works fine, so it looks like the processing power was the problem. It still seems pretty flaky when taking a picture though.

I first created a QR code that when decoded gives the id of a Salesforce record. I then downloaded the zip of the jsqrcode plugin and uploaded this to Salesforce as a static resource and added the various JavaScript files in the order mandated on the site:

<script type="text/javascript"
      src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.2.min.js"></script>
    
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/grid.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/version.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/detector.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/formatinf.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/errorlevel.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/bitmat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/datablock.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/bmparser.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/datamask.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/rsdecoder.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/gf256poly.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/gf256.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/decoder.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/qrcode.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/findpat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/alignpat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/databr.js')}"></script>

I then have a simple form with JavaScript functions attached to the buttons:

  <form>
    <input type="file" onchange="previewFile()" /><br/>
  <h1>Preview</h1>
   <div style="height:200px">
    <img src="" id="preview" height="200" alt="Image preview..." />
   </div>
  </form>

  <p>If the image above looks clear, click the decode button.  If not, try again!</p>
  <button id="decode" onclick="decode()">Decode</button>

Upon first loading the page, a callback is registered with jsqrcode to alert the result of the code and then redirect to the record matching the id, using the sforce.one JavaScript object if it is available, otherwise using the standard UI URL:

  function read(a)
  {
        alert(a);
		if( (typeof sforce != 'undefined') && (sforce != null) ) {
        	sforce.one.navigateToSObject(a);
        }
        else {
        	window.location="/" + a;
		}
  }
        
  $(document).ready(function() {
        qrcode.callback = read;
  });
  

When the user takes a picture via the file input, the following JavaScript produces a preview:

function previewFile() {
  var preview = document.querySelector('#preview');
  var file    = document.querySelector('input[type=file]').files[0];
  var reader  = new FileReader();

  reader.onloadend = function () {
    preview.src = reader.result;
  }

  if (file) {
    reader.readAsDataURL(file);
  } else {
    preview.src = "";
  }
}

and if the code looks clear enough, clicking the decode button executes the jsqrcode decode function using the source of the image preview:

function decode() {
    try
    {
    var preview=document.querySelector('#preview');
    qrcode.decode(preview.src);
    }
    catch (e)
    {
       alert('Error - ' + e);
    }
}

The full page is available at this gist, and here are some screen shots of it in action:

Screen Shot 2014 01 30 at 16 33 47

Clicking the button to choose a file allows me to capture a new image or pick one already on my iPad:

 Screen Shot 2014 01 30 at 16 36 07

I choose to capture an image that is being displayed on my macbook air which renders into the preview section:

Screen Shot 2014 02 01 at 12 40 31

And if I’m happy with it, I can press the button to decode.  This takes a couple of seconds and then, all things being equal, I’m taken to the record:

 IMG 0067

If there’s a problem, I’ll receive an alert and I can can have another go.  I’ve been pleasantly surprised by how well the codes can be processed - I managed to scan my Dreamforce badge without any issues!  

Finally, take a step back and appreciate what has been done here - an image has been captured from the device, previewed, and the contained QR code decoded, all on the client in HTML5 and JavaScript.  I think that’s pretty amazing and I’m continually impressed by how much more can be done these days without resorting to server side code.