Christian Penoy
Tera Contributor

Pre-requisite to this article

In order to understand this article, I recommend you read fully and carefully lasse's article How to support copy / paste of pictures on Service Portal as well as the Script Include "XXX_Attachment" in the project Copy paste screenshot as attachment to any incident (or task).

You can also refer to my article How to support copy / paste of pictures on Service Portal using IE11.

 

General overview of the solution

You will face several problem trying to paste an image from the clipboard and you will solve them one by one:

  • there is no standard zone to paste into: you will create and embed a Widget in the "SC Catalog Item" Portal Widget.
  • you can't paste an image with IE11: in fact you can. You will use a "content editable" container and save the image with your own code.
  • to create an attachment, you need a record ID and it has not been created yet: the Widget has generated an ID for the cart but you don't see it. You will read this ID and communicate it to the new Widget

So here are the steps to follow:

  1. modify the "SC Catalog Item" Portal Widget to broadcast the cart id.
  2. create the Paste Widget, which will listen for the cart id and save the image.
  3. embed the Paste Widget in the "SC Catalog Item" Portal Widget.

 

1. Modify the "SC Catalog Item" Portal Widget to broadcast the cart id

After having created a copy of the OOTB Widget, open the HTML template, locate the first container and add an init event. The code should now be something like this:

<div id="sc_cat_item" ng-if="::(data.recordFound && !data.not_for_mobile)" sn-atf-blacklist="IS_SERVICE_CATALOG"
     ng-init="onLoad()">

 Add the following code to the Client Script to broadcast the cart id to the embedded-to-be Widget:

$scope.onLoad = function() {
	var table = ''+$scope.data._attachmentTable;
	var id = ''+$scope.data._generatedItemGUID;
	$timeout(function() {
		$scope.$broadcast('catalog_widget.table.sys_id', id);
		$scope.$broadcast('catalog_widget.table.name', table);
	},500); // wait for the page to load
}

 

2. Create the Paste Widget

(sorry for the screenshots: I couldn't find how to correctly display the double braces on this forum)

Create a new Portal Widget

Create a new Portal Widget and name it as you wish. I named it "fuji_pastearea_for_ie11".

Create the HTML template:

find_real_file.png 

Create the Client Script

Add the following code:

function spPasteAreaForIE11($scope, nowAttachmentHandler, $animate, $rootScope, cabrillo, $timeout, snRecordWatcher, spUtil, spAriaUtil, $http, $window, snAttachmentHandler, i18n) {
    $scope.showLocationIcon = false;
    $scope.msg = "";
    $scope.isNative = cabrillo.isNative();
    $scope.errorMessages = [];
    var existingEntries = {};
    var c = this;
    var skipNextRecordWatchUpdate = false;
    $scope.typing = [];

    c.generatedItemGUID = null;
    c.generatedItemTablename = null;

    $scope.$on('catalog_widget.table.sys_id', function (e, id) {
        c.generatedItemGUID = id;
    });

    $scope.$on('catalog_widget.table.name', function (e, name) {
        c.generatedItemTablename = name;
    });

    $scope.addInformativeText = function (event) {
        getPasteArea().innerHTML = c.data.pasteText;
    }

    $scope.removeInformativeText = function (event) {
        getPasteArea().innerHTML = "";
    }

    function getPasteArea() {
        return document.getElementById("pasteArea");
    }

    $scope.pasteIE = function (event) {
        console.log('pasteIE');
        event.preventDefault(); // prevent image from displaying in the paste area
        if (typeof window.clipboardData != 'undefined') {
            var clip = window.clipboardData;
            if (clip) {
                if (clip.files.length == 0) {
                    pastedNotImage();
                } else if (clip.files[0].type.indexOf('image/') !== -1) {
                    //var url = URL.createObjectURL(clip.files[0]);
                    //var blob = url.substring(4);
                    var myFile = clip.files[0];
                    var reader = new window.FileReader();
                    reader.onloadend = function () {
                        var base64Image = reader.result;
                        attachClipboardData(base64Image);
                    };
                    reader.readAsDataURL(myFile);
                } else {
                    pastedNotImage();
                }
            }
        }

    }

    function pastedNotImage() {
        alert(c.data.notImageText);
    }

    function attachClipboardData(data) { // date is 'date:image/png;base64,...'
        console.log('attachClipboardData');
        var temp = data.toString().replace(/data:/g, '').split(';');
        var contentType = temp[0];
        var fileName = 'Screenshot';
        var fileType = contentType.split('/')[1];
        var content = temp[1].toString().replace(/base64,/g, '');
        c.data.action = "attachScreenshot";
        c.data.attachment_contentType = contentType;
        c.data.attachment_fileName = fileName;
        c.data.attachment_fileType = fileType;
        c.data.attachment_content = content;
        c.data.attachment_generatedItemGUID = c.generatedItemGUID;
        c.data.attachment_generatedItemTablename = c.generatedItemTablename;
        c.server.update().then(function () {
            c.data.action = undefined;
            $rootScope.$broadcast("sp.attachments.update", $scope.data.sys_id);
            spAriaUtil.sendLiveMessage($scope.data.attachAddedMsg);
        });
    }


}

 

Create the Server Script

The most important part here is the ""attachScreenshot" action : it will create an Attachment in "sys_attachment" table with "table_name" field set to "sc_cart_item" and the "table_sys_id" field set to the id sent by the "SC Catalog Item" Widget.

(function() {
	gs.log('Server script');
	data.maxAttachmentSize = parseInt(gs.getProperty("com.glide.attachment.max_size", 1024));
	if (isNaN(data.maxAttachmentSize))
		data.maxAttachmentSize = 24;
	data.uploadingAttachmentMsg = gs.getMessage("Uploading attachment...");
	data.sharingLocMsg = gs.getMessage("Sharing location...");
	data.scanBarcodeMsg = gs.getMessage("Scan barcode");
	data.checkInLocMsg = gs.getMessage("Check in location");
	data.messagePostedMsg = gs.getMessage("Message has been sent");
	data.viewMsg = gs.getMessage("View");
	data.attachAddedMsg = gs.getMessage("Attachment added");
	data.attachFailMsg = gs.getMessage("Failed to add attachment");
	data.scanFailedMsg = gs.getMessage("File failed security scan");
	data.sys_id = input.sys_id || options.sys_id || $sp.getParameter("sys_id");
	data.table = input.table || options.table || 'sc_req_item';
	// don't use options.title unless sys_id and table also come from options
	if (options && options.sys_id && options.table)
		data.ticketTitle = options.title;
	data.placeholder = options.placeholder || gs.getMessage("Type your message here...");
	data.placeholderNoEntries = options.placeholderNoEntries || gs.getMessage("Type your message here...");
	data.btnLabel = options.btnLabel || gs.getMessage("Send");
	data.includeExtended = options.includeExtended || false;
	data.use_dynamic_placeholder = options.use_dynamic_placeholder;
	data.isNewRecord = data.sys_id == -1 || gr.isNewRecord();
	data.hideAttachmentBtn = options.hideAttachmentBtn;
	data.pasteText = options.pasteText || gs.getMessage("Paste your screenshot here using CTRL+V...");
	data.pasteTitle = options.pasteTitle || gs.getMessage("Paste image area");
	data.notImageText = options.pastenotImageTextTitle || gs.getMessage("You are not pasting a screenshot");
  data.attachmentTable = 'sc_cart_item';
	
	var gr = new GlideRecord(data.table);
	if (!gr.isValid()) {
		gs.log('gr is not valid');
		return;
	}

	data.table = gr.getRecordClassName(); // use actual table for the record
	
	if (input) { // if we have input then we're saving
		if (input.action == "attachScreenshot") {
			var itemId = input.attachment_generatedItemGUID;
			var tablename = input.attachment_generatedItemTablename;
			var value = GlideStringUtil.base64DecodeAsBytes(input.attachment_content);	
			var attachment = new fujiAttachment();
			var name = input.attachment_fileName + " " + gs.nowDateTime() + "." + input.attachment_fileType;
			gs.log('Creating attachment with tablename='+tablename+', itemId='+itemId+
						', name='+name+', input.attachment_contentType='+input.attachment_contentType);
			var attID = attachment.write(tablename, itemId, name, input.attachment_contentType, value);
			gs.log("Created sys_attachment with id "+attID);
		}
		
	}

	data.canWrite = gr.canWrite();
	data.canAttach = gs.hasRole(gs.getProperty("glide.attachment.role")) && GlideTableDescriptor.get(data.table).getED().getAttribute("no_attachment") != "true";
	data.canRead = gr.canRead();

})()

 

3. create fujiAttachment Script Include

Use this code:

/*
* Adapted from https://developer.servicenow.com/app.do#!/share/contents/7877086_copy_paste_screenshot_as_attachment_to_any_incident_or_task?v=1.04&t=PRODUCT_DETAILS
*/
var fujiAttachment = Class.create();
fujiAttachment.prototype = Object.extendsObject(Attachment, {

	attach: function() {
		//gs.log("BEGIN attach");
		if(this.target == null) {
			//build target via gliderecord call
			if(this.tablename == null || this.targetID == null)
				return "Table name and/or target sys id are null. Please specify valid parameters.";
			var targetRecord = new GlideRecord(this.tablename);
			if(!targetRecord.get(this.targetID)) {
				//	return "Could not find a record in table '" + this.tablename + "' with sys_id '" + this.targetID + "'";
			}
			this.setTarget(targetRecord);
		}
     
		var sa = new GlideSysAttachment();
		var attachmentId = sa.write(this.target, this.filename, this.contentType, this.value);
		//gs.log("END attach");
		if(attachmentId)
			return attachmentId;
		else
			return "Attachment creation failed";
	},
	
    type: 'fujiAttachment'
});

 

4. embed the Paste Widget in the "SC Catalog Item" Portal Widget

Modify the HTML template

Add the following line to the HTML template where you want it (I put it under the attachments list) :

<sp-widget widget="data.widget_fujiPasteIE11" class="form-group m-b-xs" />

find_real_file.png

Modify the Server Script

Add the following line as the first line of the script :

data.widget_fujiPasteIE11 = $sp.getWidget('fuji_pastearea_for_ie11',null);

 

And ... that's it. It should be working.

Comments
Christian Penoy
Tera Contributor

I forgot a script include in my solution: fujiAttachment

Description: Handler for attachment processing
Modified from OOTB to return id of new attachment added instead of some text message

/*
* Adapted from https://developer.servicenow.com/app.do#!/share/contents/7877086_copy_paste_screenshot_as_attachment_to_any_incident_or_task?v=1.04&t=PRODUCT_DETAILS
*/
var fujiAttachment = Class.create();
fujiAttachment.prototype = Object.extendsObject(Attachment, {

	attach: function() {
		//gs.log("BEGIN attach");
		if(this.target == null) {
			//build target via gliderecord call
			if(this.tablename == null || this.targetID == null)
				return "Table name and/or target sys id are null. Please specify valid parameters.";
			var targetRecord = new GlideRecord(this.tablename);
			if(!targetRecord.get(this.targetID)) {
				//	return "Could not find a record in table '" + this.tablename + "' with sys_id '" + this.targetID + "'";
			}
			this.setTarget(targetRecord);
		}
     
		var sa = new GlideSysAttachment();
		var attachmentId = sa.write(this.target, this.filename, this.contentType, this.value);
		//gs.log("END attach");
		if(attachmentId)
			return attachmentId;
		else
			return "Attachment creation failed";
	},
	
    type: 'fujiAttachment'
});
vicks
Tera Guru

Hi Christian,

 

1st of all, you are amazing.

I am able to get the results as expected on sc catalog item.

I forgot to mention that we are using sc catalog item deprecated and results are correctly displayed.

Now, image is being attached to the deprecated catalog item while composing; however, in order to display that screenshot i have to add another one using given attachment clip which then displays both in attachment list...

 

any idea whats difference between sc catalog item and deprecated one which would be causing this..?

 

Again thanks for your precious time..

vicks
Tera Guru

Added recordwatch in deprecated catalog item and now its working...

Thanks Christian for everything... 🙂

Christian Penoy
Tera Contributor

Hi vicks,

I would say, without knowing the deprecated one, that the refresh attachment list is triggered in a different way. The non-deprecated one seems to use a Record Watcher.

Why are you using the old one ?

By the way, you were right: I had to make minor adjustments after posting this article. So I checked again and corrected the article. The crucial part is to grab the internal variables '$scope.data._generatedItemGUID' and '$scope.data._attachmentTable' from the Catalog Widget and pass them to the Attachment Widget so you can use them in the Server Script.

vicks
Tera Guru

Well, i just started working on ServiceNow and the project i got has been using old catalog item because they have made many customizations on service portal before the new one came in.

 

Christian Penoy
Tera Contributor

Legacy code ... always a delicacy ...

Glad you were able to achieve your intended result 🙂

Version history
Last update:
‎10-24-2019 10:13 AM
Updated by: