A Server side function to Percent Encode

If you aren’t sure what is Percent Encode, Here is the RFC describing it.

If you aren’t sure why you need Percent Encode, Here is the link to Twitter oAuth Percent Encode page. We basically encode out parameters, when we send out Authorizations/Requests using oAuth protocol. I was working with a couple of such services, which only accept oAuth and desperately needed a Server Side solution. If you need it on the Client Side, there is a more elegant solution  – You can use encodeURI or encodeURIComponent.
Linking an excellent post covering both these functions.

This post specifically speaks about the Server Side function of Percent Encode, for Service Now.

Note: This post uses Package calls. After Calagry, Package calls are no longer supported. Use the Calgary Package Migration tool to migrate your package calls.

Without further ado, Here is the script:


percentEncode:function(params){

	  if(typeof params =='string'){

         var ENCODING = "UTF-8";
         var s = params;
         s= s.toString();
         var a = Packages.java.net.URLEncoder.encode(s, ENCODING)
         .replace("+", "%20").replace("*", "%2A")
         .replace("%7E", "~");

         return a;

      }

      if(typeof params == 'object'){
         var arr = [];

         for(var i in params){
            if (params.hasOwnProperty(i)) {
               gs.log("percent encodes"+ this.percentEncode(i)+"="+this.percentEncode(params[i]));
			   arr.push(this.percentEncode(i)+"="+this.percentEncode(params[i]));

            }

         }

          return arr.join('&');

      }

   },

Twitter is just an example of a service which uses Percent Encode. Percent Encode will(mostly) be used when ever you work with oAuth protocol, which Service Now is yet to introduce. On a side note, We can still mimic oAuth using HTTPClient’s  GET and POST.

Let me know what you think in the comments.

A simple Bar-Code generator with basic features.

Today, let’s take a look at how to create a Bar code label in Service Now. I’ve seen this being asked so manytimes, and recently here : http://community.servicenow.com/forum/8960 . This started me off, and here is it.

The end product will look like this :ServiceNow Service Automation 2013-05-05 08-38-43

1. We will have a UI Page, where you can specify the table name, 2 column names( this will take the top two columns of the barcode ), and the column name you want to barcode-code(if you leave this blank, then it will automatically encode sys_id)

The barcode landing page will look something like this :landing page

Note: I’ve not even touched a tiny bit of CSS to the above page. If you are interested in having a great look using CSS, just add style tags, or import a Style sheet into your UI page as discussed here.

2. We will have a Global button called “Print Barcode”. This is a global button, and will appear in all those tables which have entry in a table called barcode_template table. The barcode_template table will store the preferences such as Column names that should appear, and the column name that needs to be encoded, along with the table name. A sample of data in barcode_template table:ServiceNow Service Automation 2013-05-05 08-35-30

Now, lets go into how we do it:

All the magic happens with a jQuery plugin called BarCode Generator hosted here  Barcode generator. All I had to do was to get it into a UI Script and write a simple UI Page, to take the table name, and the columns and then encode it and display the results.Lets start by creating a simple landing UI page:

Name: Barcode_landing

HTML:


<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

<div style="margin:auto">

TableName: <input type="text" name="tname" id="tname"/><br/>

<label>You can specify 2 columns, comma separated, and they will be displayed in the first two columns of Barcode Label.</label><br/>
ColumnName(s): <input type="text" name="cname" id="cname"/> <br/>

<label> Column name to be barcoded </label><br/>
Field to be Barcoded: <input type="text" name="fname" id="fname"/> <br/>
Encoded Query: <input type="text" name="enc" id="enc"/> <br/>

<input type="button" onclick= "fnSubmit()" name="GetLabels" value ="GetLabels"></input>


</div>
</j:jelly>

Client Script:

function fnSubmit(){
    
    window.location = "/barcode_generator.do?tname="+$('tname').value+"&cname="+$('cname').value+"&fname="+$('fname').value+"&enc="+$('enc').value+"&stub=no";
    
    
    
}

Here in the above UI page, we take all the inputs from the user, and send it to another UI Page. Notice the parameter, stub which is no, if we are doing it from this UI page – in bulk , or is it from the UI action on a particular table, for which the value will be yes. tname corresponds to Table Name,cname corresponds to comma separated column names, that take the two columns of the Label, fname corresponds to the field to be encoded and enc corresponds to encoded query.If you leave it blank, all the rows from the table tname, will be Barcode labeled.

Now, let’s talk about the UI page that generates the Barcode:
Name : Barcode_Generator
HTML


<!--?xml version="1.0" encoding="utf-8" ?-->

//Get all the parameters from the URL using the RP object.
//Check if you are coming from a UI page, or from a UI action
//Yes = from UI action, No = from UI page(bulk)
stub = RP.getParameterValue('stub');
//Get the table name.
tname = RP.getParameterValue('tname');

//If it is from the UI page, then you already have all the information specified in the UI page.
if(stub == 'no'){
//Get the comma separated column names, and store it in an array.
cname = RP.getParameterValue('cname').split(',');
//Get the field name to be barcode encoded into fname.
fname = RP.getParameterValue('fname');

if(fname == ''){
//If nothing is specified, by default take the sys_id and encode it.
fname = 'sys_id';
}
}

//If coming from the UI action on the table(single record barcode generate)
else if(stub == 'yes'){
//Glide the barcode template table, and get the column name, and field name from the table record for that //table
var grTab = new GlideRecord("barcode_template");
grTab.addQuery('tablename',tname);
grTab.query();

if(grTab.next()){
cname = grTab.column.split(',');
fname = grTab.fname;

}
}

//Finally get the encoded query - 
enc = RP.getParameterValue('enc');

gr = new GlideRecord(tname);
gr.addEncodedQuery(enc);
//Prepare the gr object
gr.query();

</g:evaluate>

<head>
<!-- Include jQuery -->
<script type='text/javascript' src='jQuery.min.jsdbx'></script>

<!-- Include Barcode Generator,Link below. -->
<script type='text/javascript' src='barcode.min.jsdbx'></script>

<!-- take care of some CSS styles. -->
<style>
p.thicker {font-weight:900;}
table,td
{
border:1px dotted #98bf21;
text-align:center;
font-weight:1900px;
font-size: 120%;

},


</style>
</head>
<body>
<div style="margin-left:5cm">
<table width="732" >

<!-- The rest is pure jelly xD -->
<j:while test="${gr.next()}">
  <tr>
    <td width="359" height="64"><p class="thicker">${gr.getValue(cname[0])}</p></td>
    <td width="357"><p class="thicker">${gr.getValue(cname[1])}</p></td>
  </tr>
  


  <tr >
    <td colspan="2" width="732" height="78" align="center">

<!-- This div is a placeholder for the Barcode. We recognize it with the class, and encode the id.Check the Client Script.-->
<div style="margin:auto;" id="${gr.getValue(fname)}" class="bcode"> </div>


</td>
  </tr>
<tr style="border:none;background-color:#EAF2F3;">
<td  colspan="2" style="border:none;background-color:#EAF2F3;">
$[sp]
</td>

</tr>
 
</j:while>

</table>
</div>
</body>

</j:jelly>

Now, here is the place where we insert the Barcode –

Client Script

(function($){
  
	$('.bcode').each(function(){ $(this).barcode($(this).attr("id"), "code128");     })
    
})($j||jQuery);

— This ends the bulk Bar code Generation —

Let’s now talk about generating it being on a record of a table. For that I’ve created a UI action like this:

Name: Print Barcode
Table: Global
client: checked
onClick : showBarCode()
Condition: new BarCode().isThere(current.getTableName())


function showBarCode(){
   var url =' /barcode_generator.do?        tname='+g_form.getTableName()+'&enc=sys_id='+g_form.getUniqueValue()+'&stub=yes'; 
    popupOpenStandard(url);
    
}

OKay, now we are done, except for a Script Include by the name BarCode that we use to validate if we have to show that button on a particular table. Here is the Script Include:


var BarCode = Class.create();
BarCode.prototype = {
    initialize: function() {
    },
    
    isThere:function(tName){
        var gr = new GlideRecord("barcode_template");
        gr.addQuery('tablename',tName);
        gr.query();
        if(gr.next()){
            return true;
        }
        return false;
        
    },
    
    
    type: 'BarCode'
}

Easy ain’t it? Couple of things to wrap this post up:

Firstly – You can download the barcode jQuery plugin from Barcode Generator

Secondly – As i have told many times, I’m not a very good CSS designer.So I only worked with basics here. Feel free to improve upon the CSS.

Three – If you have noticed the UI page, you will know any layout is possible. So mix and match HTML and comeup with the layout that you need.

Most important: To push a jQuery plugin into a Service Now UI Script, you sometimes need to tweak it a tiny little bit, as it conflicts with Prototype. I’ll soon write a post on it, but until then you can use the UI Script here : Barcode UI Script and use it in your instance.

Here is the update set : Barcode Generator – Service Now Update set

Let me know what you think in the comments!

SendJSON- A Script Include to change your data to JSON

The main reason for writing this Script Include is to easily make calls in Service Now(without a JSON processor, in the middle) and retrieve the record set in JSON format.

Another reason is that,I’m positive this will help a lot in coding Single Page apps/ UI Pages in Service Now, using Backbone.js, as Backbone provides extensive support for JSON.

If you are interested in looking at the Script Include here is the link to my blog on Community.

Also,I’ll be improving this Script Include, and updating the page.The current version is hosted here with comments, for one function(yet,I’m working on other too,will update).

Current version link : SendJSON2

I’ll post on how this script include will help when I write my backbone series, starting tomorrow(22-Apr-2013).
I’ll in sometime add it to GitHub, so that you can fork it and use/modify it.

Any suggestions/comments as always are welcome!

Random : Calculate the count and friends who wished you on Facebook using GreaseMonkey

If you dont have Grease Monkey, then go get this here : Grease Monkey
Install this in your Firefox, and copy this in a user script. Load the entire page on which you want to calculate the number of birthdays and then run this User script.

Add as much as search terms you would choose to and enjoy!


// ==UserScript==
// @name        Calculate No of birthday postings
// @namespace   www.servicenowdiary.com
// @description It calculates the number of birthday postings
// @include     https://www.facebook.com/*
// @version     1
// @require       http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
// ==/UserScript==

$(document).ready(function(){
var whoSaid = [];
a  = $.map($('.timelineUnitContainer'),function(a){
var isThere  = '';
var isThere = $(a).html();
isThere = isThere.toLowerCase();
if( isThere.indexOf('many many') != -1|| isThere.indexOf('happy returns') != -1 || isThere.indexOf('happy birthday')  != -1 || isThere.indexOf('birthday') != -1 || isThere.indexOf('b\'day') != -1 || isThere.indexOf('returns') != -1  || isThere.indexOf('bday') != -1){
$(a).find('.fwb').each(function(){
if($(this).parent().attr('class') == 'fcg'){
whoSaid.push($(this).find('a').html());
}
})}});
if(whoSaid.length != 0){
alert('on this page, there are ' + whoSaid.length +' wishes');
alert('These are the ones who wished you! - Thank them ! \n' +whoSaid.join('\n'));
}

});

Create a Service Request from Change and associate it

I’ve not searched for any other way to do this, but when i saw this community post here wanted to do it myself and this way:

Here we ,

1. Have a button on the change_request form, which when you click on it will open a Slushbucket which will have all the Catalog Items in it.
2. The user,when he selects the Catalog Item from left to right slushbucket, and clicks on the catalog item in the right slush bucket, a new tab will open in which he will have an option to fill the Catalog Item’s form he selected.
3. When he opens a Catalog Item form, the Change Request from where he is opening this Catalog Item’s form, will be populated in a variable(present in a variable set) called change_request.
4. Now finally, we write a Business Rule to populate back the request created into corresponding Change Request ticket in a field called “Associated Request”.

Create a new field on Change_Request by the name Associate Request, which references to Request(sc_request) Table.

Creation of the Create a Service Request button on Change Request form :

Type : UI Action
Name : Create a Service Request
Client : checked
onClick : addCatItem

function addCatItem(){
   //Open a dialog window to request items that we want to add.
   var dialog = new GlideDialogWindow('add_service_request');
   dialog.setTitle('Select Request Item');
   dialog.setPreference('sysparm_groupQuery', 'active=true');
   dialog.render();

   return false;
}

Type: UI Page
Name : add_service_request

<!--?xml version="1.0" encoding="utf-8" ?-->
<table border="0">
<tbody>
<tr>
<td>Please select the Service Request which you want to order.</td>
</tr>
<tr>
<td><!-- Include the 'ui_slushbucket' UI macro --></td>
<td align="right"><!-- Include the 'dialog_buttons_ok_cancel' UI macro --></td>
</tr>
</tbody>
</table>

Client Script:

addLoadEvent(function(){
   //Load the groups when the form loads
    document.getElementById('slush_right').ondblclick = dbClick;

   slush.clear();
   var ajax = new GlideAjax('GroupCatalogItems');
   ajax.addParam('sysparm_name', 'getItems'); 
   ajax.getXML(loadResponse);
   return false; 
});
function dbClick(){
var sysID  = gel('sys_uniqueValue').value;
url =  '/com.glideapp.servicecatalog_cat_item_view.do?sysparm_id='+this.value+'&amp;sysparm_chg='+sysID;
window.open(url);

}
function loadResponse(response){
   //Process the return XML document and add groups to the left select
   var xml = response.responseXML;
   var e = xml.documentElement; 

   var items = xml.getElementsByTagName("item");
   if(items.length == 0)
      return;

   //Loop through item elements and add each item to left slushbucket
   for (var i = 0; i &lt; items.length; i++) {
      var item = items[i];
      slush.addLeftChoice(item.getAttribute('value'), item.getAttribute('text'));
   }
}

Type: Script Include

Name : GroupCatalogItems

var GroupCatalogItems = Class.create();

GroupCatalogItems.prototype = Object.extendsObject(AbstractAjaxProcessor, {

   getItems : function() {  
      var gr = new GlideRecord("sc_cat_item");
      gr.addQuery('active', true);  
      gr.query(); 

      //Add the available groups to select from
      while (gr.next()) {
         var item = this.newItem();
         item.setAttribute('value', gr.getValue('sys_id'));
         item.setAttribute('text', gr.getValue('name'));
}
}
      });

Creation of a Variable Set – Creation of a Variable – Creation of a Catalog Client Script on Variable Editor :

Catalog Client Script :

function onLoad() {
var test = getUrlVars();
g_form.setValue('change_request',test['sysparm_chg']);
}

function getUrlVars() {
   var vars = [],
   hash;
   var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
   
   for (var i = 0; i < hashes.length; i++) {
      hash = hashes[i].split('=');
      vars.push(hash[0]);
      vars[hash[0]] = hash[1];
   }
   
   return vars;
}

Please note, the important thing is to Add this Variable set to which all Catalog Items that you need to order from a Change Request.

Finally, to map, the change_request with the request, you need to write this Business Rule.
Name : Assosiated Request
Table : sc_req_item
condition : !current.variables.change_request.nil()
After : true
When : Update

gs.addInfoMessage('Its getting inserted');
var gr  = new GlideRecord('change_request');
gr.get(current.variables.change_request);
if(gr){
if(gr.u_assosiated_request == ''){
gr.u_assosiated_request  = current.request;
gr.update();
}
}

You should have the change_request in the requested item variable change_request .. you can access it using current.variables.change_request. And the above business rule will populate it in the Change Request table by populating the field Assosiated Request.

Send an Attachment from Service Now and store it on FTP server via MID server

Basically we are trying to send an attachment as soon as it is attached, to FTP Server. As I mentioned in , its always better to write the logic of writing the file to FTP in a MID server Script Includes.r_

We write a Business Rule on Attachment table, so that whenever an entry is inserted we call the MID script Include pass the attachment,that got just inserted in encoded64 format -create a file(from the information in the attachment) on FTP server using any of the Writers/Outbuffers of java and close the connection.

Name of the Business Rule :call_MIDServer_SendFile
Table: sys_attachment
When : After insert

fnToMidserver();
function fnToMidserver(){
   
   try{
      var gr = new GlideRecord("your_table");
      gr.get(current.table_sys_id);
      var fileName ='SNOW_'+gr.number+'_'+gr.sys_id+'_'+current.file_name;
      var ftpServer = gs.getProperty("host");
      var username = gs.getProperty("login");
      var pswrd = gs.getProperty("password");
      var port= gs.getProperty("port");
      var type=gs.getProperty("type");
      var timeout=gs.getProperty("timeout");
      var passive=gs.getProperty("passive");
      var ftpFilePath = gs.getProperty("FTP Attachment");
      /* We encode the Attachment to base64 as we cannot transfer it as a plain string.The code below handles the conversion*/

      var StringUtil = Packages.com.glide.util.StringUtil;
      var sa = new  Packages.com.glide.ui.SysAttachment();
      var binData =  sa.getBytes(current);
      var encData =StringUtil.base64Encode(binData);

/*End of the code */
      var jspr = new JavascriptProbe('Integration');
      jspr.setName('FileToMidServer'); //Any descriptive name will do
      jspr.setJavascript("var ddr = new AttachmentSender();res = ddr.execute();");
      jspr.addParameter("targetFileName",fileName);
      jspr.addParameter("encodedData",encData);
      var cred = username+":"+pswrd;
      jspr.addParameter("ftpCred",cred);
      jspr.addParameter("ftpTargetServer",ftpServer);
      jspr.addParameter("ftpPort",port);
      jspr.addParameter("targetPath",ftpFilePath);
      jspr.addParameter("host",ftpServer);
      jspr.addParameter("transportMethod",type);
      jspr.create();
      gs.log('Completed: check Mid Server log');
   }
   catch(e){
      gs.log('exception in calling mid server : '+e);
      
   }
}

One of the challenges we faced in sending the base64 encoded string to MID server was, We don’t have the StringUtil class included with jars of MIDserver. Upon using Reflection API we came up with a different class Base64() and we use the decode method for the conversion into a plain string.

The Script Include goes this way :

Name : Attachment Sender



var AttachmentSender = Class.create();

AttachmentSender.prototype = {
   initialize : function() {
          this.debug = false;
      
      //If you would like to move processed files to a target location, set the next two parameters.
      //Make sure your file name ends up being unique. We will not overwrite a file by the same name
      //in the target directory...
      this.moveProcessedFiles = true;
      this.MIDSERVER_PROCESSED_FILE_PATH = "processed/";
      
      this.resLog = "FTP Log: \n";
      this.log("Initializing SNDataRetriever");
      this.MIDSERVER_FILE_PATH = "work/";      
      this.MIDSERVER_FILE_NAME = probe.getParameter('targetFileName');
      
      this.Encoded_Data = probe.getParameter('encodedData');
      
      this.useProxy = this.getConfigParameter("mid.proxy.use_proxy");
      if (this.useProxy){
         this.proxyHost =this.getConfigParameter("mid.proxy.host");
         this.proxyPort =this.getConfigParameter("mid.proxy.port");
         this.proxyUser =this.getConfigParameter("mid.proxy.username");
         this.proxyPass =this.getConfigParameter("mid.proxy.password");
      }
      this.user = this.getConfigParameter("mid.instance.username");
      this.password = this.getConfigParameter("mid.instance.password");
      
      var ftpCredArr = this.decryptCredentials(probe.getParameter('ftpCred'));
      this.ftpUser = ftpCredArr[0];
      this.ftpPass = ftpCredArr[1];
      this.log('ftp username : '+ftpCredArr[0]);
      this.log('ftp password : '+ftpCredArr[1]);
      this.ftpTargetServer = probe.getParameter('ftpTargetServer');
      this.ftpPort = (this.isANumber(probe.getParameter('ftpPort'))) ? (probe.getParameter('ftpPort')) : null;
      
      this.targetPath = probe.getParameter('targetPath');
      this.targetFileName = probe.getParameter('targetFileName');
      
      this.host = probe.getParameter('host');
      
      this.transportMethod = probe.getParameter('transportMethod');
      
      if (this.debug){
         this.log("\n********** Debug Info **********\nuser: " + this.user + "\npass: " + this.password + "\ntransportMethod: " + this.transportMethod + "\nreport: " + this.reportURL+ "\nhost: "+ this.host);
         this.log("\n********** Proxy Info **********\nproxyNeeded: " + this.proxyNeeded +"\nproxyUser : " + this.proxyUser + "\nproxyPass :" + this.proxyPass );
         this.log("\nproxyHost: " + this.proxyHost + "\nproxyPort:" + this.proxyPort);
         this.log("\n********** FTP Info ************\nftpUser: "+ this.ftpUser + "\nftpPass: " + this.ftpPass + "\nftpPort: " + this.ftpPort);
         this.log("\n********** End of Debug ********\n\n");
      }
      
   },
   
   getConfigParameter: function(parm){
      var m= Packages.com.service_now.mid.MIDServer.get();
      var config = m.getConfig();
      var res = config.getParameter(parm);
      var res2 = config.getProperty(parm);
      if (res){
         return res;
      }
      else if (res2){
         return res2;
      }
      else{
         config = Packages.com.service_now.mid.services.Config.get();
         return config.getProperty(parm);
      }
      
   },
   
   decryptCredentials: function(data) {
      var cred = new String(data);
      var e = new Packages.com.glide.util.Encrypter();
      
      var jsCred = cred + '';
      var usernamePass = e.decrypt(jsCred);
      var credArr = usernamePass.split(":", 2);
      return credArr;
   },
   
   saveToFile: function(targetPath) {
      var tmpLoc;
      var result = true;
      var strContent=new Packages.com.glide.util.Base64().decode(this.Encoded_Data);
      
      try{
         tmpLoc = this.MIDSERVER_FILE_PATH + this.MIDSERVER_FILE_NAME;
         
         var fos = new Packages.java.io.FileOutputStream(tmpLoc);
         
         for(var index=0 ; index<strContent.length ; index++){
            fos.write(strContent[index].toString());
         }
         fos.close();
         
         
         this.log("File saved to: " + tmpLoc);
         
      }
      catch(e){
        
         result = false;
      }
      return result;
   },
   
   copyToFTP: function() {
      var tmpLoc;
      var connectionType;
      var ftpSuccess = false;
      
      if (this.transportMethod == "FTPS (Auth SSL)"){
         connectionType = "AUTH_SSL_FTP_CONNECTION";
         if (!this.ftpPort) {this.ftpPort = 21;}
         }
      else if (this.transportMethod == "FTPS (Auth TLS)"){
         connectionType = "AUTH_TLS_FTP_CONNECTION";
         if (!this.ftpPort) {this.ftpPort = 21;}
         }
      else if (this.transportMethod == "FTPS (Implicit SSL)"){
         connectionType = "IMPLICIT_SSL_FTP_CONNECTION";
         if (!this.ftpPort) {this.ftpPort = 990;}
         }
      else if (this.transportMethod == "FTPS (Implicit TLS)"){
         connectionType = "IMPLICIT_TLS_FTP_CONNECTION";
         if (!this.ftpPort) {this.ftpPort = 990;}
         }
      else{
         connectionType = "FTP_CONNECTION";
         if (!this.ftpPort) {this.ftpPort = 21;}
         }
      this.log("ConnectionType: " + connectionType + " and port: " + this.ftpPort);
      
      var pt = new Packages.java.util.Properties();
      pt.setProperty("connection.host", this.ftpTargetServer);
      pt.setProperty("connection.port", this.ftpPort);
      pt.setProperty("user.login", this.ftpUser);
      pt.setProperty("user.password", this.ftpPass);
      pt.setProperty("connection.type", connectionType);
      pt.setProperty("connection.timeout", "10000");
      pt.setProperty("connection.passive", "true");
      
      try{
         var connection = Packages.org.ftp4che.FTPConnectionFactory.getInstance(pt);
         
         var fromFile = new Packages.org.ftp4che.util.ftpfile.FTPFile(this.MIDSERVER_FILE_PATH, this.MIDSERVER_FILE_NAME);
         var toFile = new Packages.org.ftp4che.util.ftpfile.FTPFile(this.targetPath, this.targetFileName);
      }
      catch(e){
         
      }
      var connectionLog = "Connection Log:\n";
      var connected = false;
      try{
         connection.connect();
         this.log("Connecting to " + this.ftpTargetServer + " on port " + this.ftpPort);
         
         connection.noOperation();
         connected = true;
      }
      catch(e){
         connectionLog += "\nException in block B: " + e;
         connected = false;
      }
      
      if (connected == true){
         try{
            this.log("Connected...");
            this.log("Uploading " + fromFile + " to " + toFile);
            connection.uploadFile(fromFile, toFile);
            this.log("File successfully uploaded\n\n");
            connection.disconnect();
            ftpSuccess = true;
         }
         catch(e){
            connectionLog += "\n**********FAILURE: Connection to FTP server failed**************\n";
            connectionLog += "\nException in block C: \n" + e;
         }
      }
      else{
         connectionLog += "\n**********FAILURE: Connection to FTP server failed**************\n";
      }
      
      this.log(connectionLog);
      return ftpSuccess;
   },
   
   moveProcessedFile: function(){
      
      file = new Packages.java.io.File(this.MIDSERVER_FILE_PATH+this.MIDSERVER_FILE_NAME);// Work directory
      dir = new Packages.java.io.File(this.MIDSERVER_PROCESSED_FILE_PATH);// Move file to new (processed) directory
      success = file.renameTo(new Packages.java.io.File(dir, file.getName()));
      
      if (!success) {
         this.log('File was not successfully moved!');
      }
      else{
         this.log("File was moved from TMP directory to a processed directory");
      }
   },
   
   getLog: function() {
      return this.resLog;
   },
   
   log: function(data) {
      ms.log(data);
      this.resLog+="\n"+data;
   },
   
   isANumber: function(data) {
      data = data + '';
      return ((data - 0) == data && data.length > 0);
   },
   
   
   execute: function() {
      var result = '';
     
      var pushRes = true;
      
      var saveRes = this.saveToFile(this.targetPath+ this.targetFileName);
      if(saveRes == true){
         pushRes = this.copyToFTP();
         result = "Sucess";
         //Move tmp file to a processed directory
         if (saveRes && pushRes && this.moveProcessedFiles){
            this.moveProcessedFile();
         }
      }
      else{
         result = "Failed";
         this.log("The report was empty?");
      }
     return result;
   },
   
   type : "AttachmentSender"
};

All the methods are mostly taken from SNDataRetriever how ever, only one method needs mention : moveProcessedFile. This is used to copy the file from the one directory to another(in the MID server) once the transfer to FTP is done. Also notice we used the FileOutputStream() in saveToFile() function for creating a file on the FTP server.

Acks :
1. The entire code is written by Mohammed. I am just blogging it.
2.Again, as you observe most of the code is from Service Now Guru. We just modified it and I blogged it,so that it may come handy to anybody.

The PopupOpenStandard method & window.opener

This week I was working on a requirement where in I need to

I.Have a slush bucket on a Catalog Item’s form which will have a list of all Catalog Items(from the cat_item table), and clicking on any of the entries should take you to the catalog item form(which is a pop-up) and submitting it should create a Cart item.

For this, I figured out that I thought of writing a Glide Window, But somehow, I wanted to do it more simpler, and more over I need a pop-up which is similar to Reference Selection pop-up,i.e., it should disappear if you click anywhere else.
I came up finally with this method- nothing written on my own, but a method from external javascript libraries called the PopOpenStandard.

Also, I had to override the custom DB click event of the Right Slush bucket, so that I can write my own code when someone double clicks, it should give a pop-up(the usual functionality is it moves the entry to left slush bucket)
The code is simple and it goes something like this :

function onLoad() {
var fe= g_form.getControl('_select_1');
fe.ondblclick = fun;
}
function fun(){
/* The URL in there, is used to open a catalog item's form. You can pass different parameters to
the UI page,com.glideapp.servicecatalog_cat_item_view and also visible to all the
macros called within it. */
popupOpenStandard("/com.glideapp.servicecatalog_cat_item_view.do?sysparm_view=&sysparm_id=9e107d7b0a0a3cdd0018276a5e3f79c1&sysparm_cart_edit=9d3fc027ff522000ae69fb56e77efe3c&sysparm_seq=yes");
}

This is how you call any URL in the popOpenStandard method.


II. Now that we are giving an option to fill the Catalog Item’s form, We need to copy the price from the pop-up(which is a catalog item form in itself) and the parent form that is calling the pop=-up. This I did using a Global Function in the UI script and calling it using the top.window.opener. method.

I would post the code, might come handy to people working on pop-ups. As price is a complex thingy I would skip this for now, and explain how to call the function rendered on the Parent’s form(the Global UI Script)

Created a onSubmit() which runs only when the catalog_item is called as a pop-up( This again is a long story, I sent a parameter in the popupOpenStandard – sysparm_seq, which is again copied into a hidden variable on the UI macro catalog_item) and in the onSubmit call the Calling form’s function :

function onSubmit() {
top.window.opener.funcGlobal(100);
}

The funcGLobal is a function defined in the UI script, with Global as true. Also remember, you can set any variable on the form in funcGlobal using g_form.setValue(‘variable_name’,'value);

Another Small tip, If you have to read the Price in any catalog item’s client script for doing some calculation use the following script :

function onSubmit() {
alert(gel('price_span').innerHTML);
}

For the entire code of PopupOpenStandard use any DOM inspector like firebug.

Any suggestions/comments are welcome.

Request Submission through Script

// nuke the current cart
nukeCart();
// new cart
var realCart = getCart();
var cartID = realCart.sys_id;
// order a blackberry
addToCart(cartID, "05ef1c960a0a3cdd00587525899ea18f", 1);
addOptions(cartID);
doOrder();
function addToCart(cartid, cat_item, quantity) {
   var gr = new GlideRecord('sc_cart_item');
   gr.initialize();
   gr.cart = cartid;
   gr.cat_item = cat_item;
   gr.quantity = quantity;
   gr.insert();
}
function addOptions(cartID) {
   // Get the Cart Item
   var kids = new GlideRecord('sc_cart_item');
   kids.addQuery('cart', cartID);
   kids.query();
   if (kids.next()) {
      // Look up the options for the item in the cart
      var options = new GlideRecord('item_option_new');
      options.addQuery('cat_item', kids.cat_item);
      options.query();
      // Add the appropriate Item Options
      while(options.next()) {
         var gr = new GlideRecord('sc_item_option');
         gr.initialize();

/* These are the variables to be included for the request, for example Brief description of the request is
the "Brief Description of Request */
         if(options.question_text == 'Brief description of the request') {
            gr.item_option_new.setValue(options.sys_id);
            gr.value = current.u_mlt_bpe_comments;
         }
      }
}

function nukeCart() {
   var cart = getCart();
   var id = cart.sys_id;
   var kids = new GlideRecord('sc_cart_item');
   kids.addQuery('cart', cart.sys_id);
   kids.deleteMultiple();
}
function getCart() {
   var cart = new GlideRecord('sc_cart');
   var userid = gs.getUserID();
   cart.addQuery('user', userid);
   cart.query();
   if (cart.next()) {
      // we already have a cart all is well
   }
   else {
      cart.initialize();
      cart.user = userid;
      cart.insert();
   }
   return cart;
}
function doOrder() {
   var req = new Packages.com.glideapp.servicecatalog.RequestNew();
   req.copyCart();
   gs.addInfoMessage('Item created');
}


Courtesy : Mohammed Ishaq Khan

Get the list of the file Names on FTP server

The credit for this post is given to Ishaq Khan. The script will get all the names of the files in a particular folder of FTP.

Name : GetFileNamesFTP

try{
   var ftpServer = "xxxxxx.coxp.xxxx.com";
   var username = "xxxx/\xxxxx";
   var pswrd = 'xxxxx';
   var ftpFilePath = "FILELOC/\DOWNLOAD/\SND";
   var ftpFileName = 'ServiceNow_AreaData.csv';
   var pt = new Packages.java.util.Properties();
   pt.setProperty("connection.host", ftpServer);
   pt.setProperty("connection.port", "21");
   pt.setProperty("user.login", username);
   pt.setProperty("user.password", pswrd);
   pt.setProperty("connection.type", "FTP_CONNECTION");
   pt.setProperty("connection.timeout", "10000");
   pt.setProperty("connection.passive", "true");
   
   var connection = Packages.org.ftp4che.FTPConnectionFactory.getInstance(pt);
   connection.connect(); //connect
   connection.noOperation(); //keep connection alive
   
   
   
   
  
   
   gs.addInfoMessage('path is : '+ftpFilePath);
   
   var myFiles = new Packages.java.util.ArrayList();
   myFiles = connection.getDirectoryListing(ftpFilePath);
   
   gs.addInfoMessage('My files are : '+myFiles);
   
   
}
catch(e){
   gs.addInfoMessage('Exception caught :'+e);
}


Coming posts will contain docs and explanation on imp methods of FTP Connection Factory!

Drafts – Part 2 : Add Cart_items to Draft Module

This is the second post in the series, In this post we will look on how we can add the cart_item to the drafts module, when the user clicks on ” Draft” button on the Catalog Item page.

As said in the earlier post, the ” My Drafts” Module will look entirely similar to the cart page in 2 -step check out.

Firstly I designed(lifted) a new Cart Page,Most of this cart page is copied from the catalog_cart_default which will be the default cart if no Cart is specified. This condition will be present in the catalog_item UI Macro.

As we don’t want to disturb OOB components,we need a  new UI Macro Catalog_cart_draft, which should be attached to “cart” reference field on the ITEM you want to enable Draft functionality.

The code of the catalog_cart_draft is looks something like this :

Name of the UI Macro : catalog_cart_draft

<!--?xml version="1.0" encoding="utf-8" ?-->
<table style="float: right;">
<tbody>
<tr>
<td id="cart"></td>
</tr>
</tbody>
</table>
<input id="sysparm_id" type="hidden" value="${sysparm_id}" />
<table id="qty" style="display: none; width: 100%;">
<tbody>
<tr>
<td colspan="2"><a id="order_now" onclick="orderNow();"><img src="images/order_now.pngx" alt="${gs.getMessage('Submit')}" /></a></td>
</tr>
<!-- Introducing the cart_draft button.Upon click we are calling draftNow()-->
<tr>
<td colspan="2"><a id="cart_draft" onclick="draftNow();"><img src="cart_draft.png" alt="${gs.getMessage('Draft')}" /></a></td>
<!-- These two buttons we will discuss in the later posts --></tr>
<tr>
<td><a onclick="orderEdit();"><img src="images/order_now.pngx" alt="${gs.getMessage('Update Cart')}" /></a></td>
</tr>
<tr>
<td colspan="2"><a id="cart_draft_two" onclick="draftNowTwo();"><img src="cart_draft1.png" alt="${gs.getMessage('Submit')}" /></a></td>
</tr>
</tbody>
</table>
<script type="text/javascript" language="javascript">


   var g_cart = new SCCart();</p-->

function draftNowTwo(target){
g_form.setValue('cbx_drft_check','draft');
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){
  g_form.setMandatory(fields[i], false);
i++;
}
gel("cart_draft_two").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('HelloWorld');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();
orderEdit(target);
}

function draftNow(){
g_form.setValue('cbx_drft_check','draft');
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){

g_form.setMandatory(fields[i], false);
i++;
}
gel("cart_draft").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('Hello');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();

orderNow();
//window.location.href = "wishlistpage.do?";

}      
   function scCartOnRender() {    
	 g_cart = new SCCart(); 

     <j:if test="${sc_cat_item.no_order != true}">
	  g_cart.attachWindow('qty', 'cart', "${gs.getMessage('Order this Item')}");
     </j:if>
     <j:if test="${sc_cat_item.no_cart == true}">
         g_cart.setCartVisible(false);
     </j:if>

	 g_cart.addCartContent();
         g_cart.editID = '${sysparm_cart_edit}';
	 g_cart.getWithBackButton();
   }   

   addRenderEvent(scCartOnRender);    

	function addToCart() {
          var m = g_form.catalogOnSubmit();
	  if (!m) 
		return;

          var guid;
          var item_guid = gel("sysparm_item_guid");
          if (item_guid)
              guid = item_guid.value

          // To prevent duplicate key violations due to multiple rapid clicks 
          // clear the item_guid if not empty and continue with the addToCart
          // else return until a new item_guid is returned from the server 
          if (guid == "")
              return;

          item_guid.value = "";

	  // hide the attachment header and delete out attachment name spans
          var attachmentList = gel("header_attachment_list");
          if (attachmentList) {
              var count = attachmentList.childNodes.length;
              while (count &gt; 1) {
                  count--;
                  var node = attachmentList.childNodes[count];
                  rel(node);
              }
              var listLabel = gel("header_attachment_list_label");
              listLabel.style.display = "none";
          }

	  g_cart.add(gel("sysparm_id").value, getQuantity(), guid);
	}

	function orderNow() {
	  var m = g_form.catalogOnSubmit();
	  if (!m)
		return;

	  // Disable the Order Now button to prevent muliple item order
	  // as a result of muliple clicks before navigating away
	  gel("order_now").onclick = "";

          var item_guid = gel("sysparm_item_guid");
          if (item_guid)
              item_guid = item_guid.value

	  g_cart.order(gel("sysparm_id").value, getQuantity(), item_guid);
	}

	function calcPrice() {
	  g_cart.recalcPrice(gel("sysparm_id").value, getQuantity());
	}

	function orderEdit(target) {
          if (!target)
              target = '${sysparm_cart_edit}';

          var m = g_form.catalogOnSubmit();
	  if (!m)
		return;

	  g_cart.edit(target, getQuantity());
	}

	function proceedCheckout(target) {
	  var m = g_form.catalogOnSubmit();
	  if (!m)
		return;
	  g_cart.edit(target, getQuantity());
	  //g_cart.orderUpdate(target, getQuantity());
	}

        function getQuantity() {
	  var quantity = 1;
	  var quan_widget = gel("quantity");
	  if (quan_widget)
	     quantity = quan_widget.value;
	  return quantity;

	}

</script>

When we click on ” order now” button the orderNow() function defined above will be called.g_form.catalogOnSubmit(); function call will in-turn call the processor ServiceCatalog which will invoke the script include ServiceCatalogProcessor.

If You open ServiceCatalogProcessor, it has a lot of if conditions and each if condition will call a Script Include based on the g_request and some parameters which will be sent along.
When Order Now is clicked, CatalogTransactionOrder Script include gets called, and the same script include will be responsible for setting the Response Object parameters.

 if (this.action == 'order')
           return new CatalogTransactionOrder(this.request, this.response, this.processor);

this.action is a request parameter set which will contain,the ID of the button you just clicked on. Yes I love Service Now too. But that’s not it.The amount of flexibility you have is also really HUGE when the tool is SNOW.

So all we will have to do here is, We will call ordernow() function because it creates a cart_item,but before that we should have a mechanism in which we can identify if the item created, is because of the click on Order_Now or Draft.
Also, We need to take care that, when the user clicks on Draft, all the mandatory fields should become, non-mandatory,also the onSubmit scripts should not run.

g_form.setValue('cbx_drft_check','draft');
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){

g_form.setMandatory(fields[i], false);
i++;
}

The value cbx_draft_check will be on a variable_set which will be included in all the Items which need Draft enabled.We set this variable,when the Draft button is clicked, and make sure the onSubmit doesn’t run. Also, We will clear this out at the end of each onSubmit( to make sure when the user clicks on Order Now, we want the validations to happen)

How do we identify a cart_item which is created because of the user clicking on Draft ?

I have created 2 fields on the sc_cart_item table
a) stage
b) belongs_to

The stage value will be 0 if the cart_item is created by the normal Ordering way, or 1 if its created when Draft is clicked, and belongs_to will always contain the user to whom the cart item belongs to.
Reason why we have belongs_to :
The relationship between a cart and cart_item is from the Reference field of CART on the cart_item table. This field will contain the reference of the Requester’s cart.
When a user clicks on DRAFT, we remove this link, the link between a cart and cart_item because we don’t want this cart_item to appear in the 2nd cart summary page.
And because we have removed it, we need to track it. So we have this belongs_to field.Anytime you want to link it again(just incase) there is a Script Include with Cart functions which when called will return the Cart ID of the user, use that , get the Cart ID and update the value in the reference of cart_item.
So the field belongs_to.

gel("cart_draft").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('Hello');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();
orderNow();
//window.location.href = "wishlistpage.do?";

One more beautiful thing to note : the guid is a random sys_id generated on catalog_item page,using gs.generateID() which is available to this UI Macro as well, because this is called in the context of the catalog_item.
The GUID acts as identifier when the catalog item(i.e when you first click on any item in Service Catalog) is first created and also, the cart item created will have the same sys_id as that of GUID.

I wish cart_item to request_item translation were also this beautiful.

The last piece left is, ” How am I updating the belongs_to and the status.Here is how it is:

The Script Include Hello is as follows :

Name : hello

var Hello = Class.create();
Hello.prototype = Object.extendsObject(AbstractAjaxProcessor, {
   hello: function() {
      var guid = this.getParameter('sysparm_guid');
      var gr = new GlideRecord("u_draft_guide");
      gr.initialize();
      gr.u_order_id = guid;
      var a =gr.insert();
      return a;
   }
  });

Patience.Yes, I am inserting this GUID into a table called u_draft_guide.The reason is SEQUENCING. I will explain, but first let me complete the way I handled this.
The above piece of simple code will insert the record into a table called u_draft_guide. So when ever you click on draft and a cart_item is created, the cart_items sys_id will be available in the table called u_draft_guide.

I have a before insert Business Rule that runs on the sc_cart_item table which will check if the sys_id that is being created( yes in before insert you can still access the sys_id of the record that’s going to get created)

If it sees that there is an entry of sys_id in u_draft_guide table, it understands that this cart_item is created because of clicking on Draft and it does the following things.

1. Clears the CART reference field
2.Maps the belongs_to
3.Changes the status.

Here is how it looks :

var gr= new GlideRecord("u_draft_guide");
gr.addQuery("u_order_id",current.sys_id);
gr.query();
if(gr.next()){
    current.cart = '';
            current.u_belongsto = gs.getUserID();
            current.u_status = '1';
//gs.addInfoMessage("Item added to Draft !");
}

Now the entire setup is complete.Except for the Drafts module page; Here is the code for it. The only thing that I changed in this page is the glide of sc_cart_item and removed the Submit Order button

Type: UI Page
Name : draftpage

In the first Glide of servicecatalog_cart_template(the UI macro rendering the 2nd cart summary page) I have changed the first few lines of Glide:

 var sc_cart_item = new GlideRecord('sc_cart_item');
       sc_cart_item.addQuery('u_status', 1);
       sc_cart_item.addQuery('u_belongsto',system.getUserID());
       var sc_cat_item = new GlideRecord('sc_cat_item');
       sc_cat_item.initialize();
       sc_cart_item.query();

As you can see, i am getting all those cart_items as per the belongs_to and status.

Now coming to the point why do I need all the intermediate table: Its because I wan’t to make damn sure the code runs in order. You could argue and ask me to write a Glide or call a Script Include in the UI Macro itself as shown below

Our lines of Glide/Script Include Invocation
orderNow();

yes I tried that, But many a time, my orderNow() is invoked asynchronously. Hence the Hungama.
Correct me if you see any irresponsible coding.. I had a gun to my head.

Acknowledgments : Loyola, Prashanth.