Collaboration Store, Part LXXIII

“A little more persistence, a little more effort, and what seemed hopeless failure may turn to glorious success.”
Elbert Hubbard

Last time, we went over some of the remaining problems with this iteration of the software, and stopped short of providing a remedy for one of the issues, the fact that there is no capacity to include an instance logo image during the initial set-up process. Today we will take a look at correcting that design flaw.

Not one to create anything from scratch when someone else might have already put in all of that effort, I looked around for a portal page that had an image upload feature already in place. It turns out that the stock user_profile page, which uses the User Profile widget, has just the sort of thing that I was thinking of.

Stock User Profile page with existing image upload feature

Starting with the HTML, I dug into the code for the page and identified this section as pertaining to the image and upload button.

<div class="row">
  <div class="avatar-extra-large avatar-container" style="cursor:default;">
    <div class="avatar soloAvatar bottom">
      <div class="sub-avatar mia" ng-style="avatarPicture"><i class="fa fa-user"></i></div>
    </div>
  </div>
</div>
<div class="row">
  <button ng-if="::connectEnabled()" ng-click="openConnectConversation()" type="button"
          class="btn btn-primary send-message"><span class="glyphicon glyphicon-comment pad-right"></span>${Message}</button>
  <!-- file upload -->
  <span ng-if="::data.isLoggedInUsersProfile">
    <input ng-show="false" type="file" accept="image/jpeg,image/png,image/bmp,image/x-windows-bmp,image/gif,image/x-icon,image/svg+xml" ng-file-select="attachFiles({files: $files})" />
    <button ng-click="uploadNewProfilePicture($event)"
            ng-keypress="uploadNewProfilePicture($event)" type="button"
            class="btn btn-default send-message">${Upload Picture}</button>
  </span>
</div>

It was all pretty close to what I wanted, but I did make a few little tweaks here and there and finally ended up with this.

<div class="row" style="padding: 15px;">
  <div class="avatar-extra-large avatar-container" style="cursor:default;">
    <div class="avatar soloAvatar bottom">
      <div class="sub-avatar mia" ng-style="instanceLogoImage"><i class="fa fa-image"></i></div>
    </div>
  </div>
</div>
<div class="row" style="padding: 15px;">
  <input ng-show="false" type="file" accept="image/jpeg,image/png,image/bmp,image/x-windows-bmp,image/gif,image/x-icon,image/svg+xml" ng-file-select="attachFiles({files: $files})" />
  <button ng-click="uploadInstanceLogoImage($event)"
          ng-keypress="uploadInstanceLogoImage($event)" type="button"
          class="btn btn-default send-message">${Upload Instance Logo Image}</button>
</div>

Without bothering to build out the referenced functions to make it all actually work, I could still pull the page up and see how it rendered out at this point, just to get an idea of how it was going to look.

New initial set-up page with added logo image feature

So that looks pretty good. Now we need to add the client-side code needed to make it work. As with the HTML, we will turn to the User Profile widget to find some code to start out with.

$scope.uploadNewProfilePicture = function($event) {
	$event.stopPropagation();
	if($event.keyCode === 9) {
		return;
	}
	var $el = $element.find('input[type=file]');
	$el.click();
}

$scope.attachFiles = function(files) {
	var file = files.files[0];

	var validImage = false;

	switch(file.type) {
		case 'image/jpeg':
		case 'image/png':
		case 'image/bmp':
		case 'image/x-windows-bmp':
		case 'image/gif':
		case 'image/x-icon':
		case 'image/svg+xml':
			validImage = true;
			break;
		default:
			break;
	}

	if(!validImage) {
		alert(file.name + " " + i18n.getMessage("isn't a recognized image file format"));
		return;
	}

	snAttachmentHandler.create("live_profile", $scope.data.liveProfileID).uploadAttachment(file, {
		sysparm_fieldname: "photo"
	}).then(function(response) {
		var obj = {};
		obj.newAvatarId = response.sys_id;
		$scope.avatarPicture = {
			'background-image': "url('" + response.sys_id + ".iix')",
			'color': 'transparent'
		};
		$rootScope.$broadcast("sp.avatar_changed", obj);
		// timeout is required for screen reader to pick up the message once file upload prompt is dismissed
		$timeout(function() {
		   spAriaUtil.sendLiveMessage(i18n.getMessage('Profile picture updated successfully'));
		}, 500);
	});
}

$scope.avatarPicture = "";

$http.get('/api/now/live/profiles/sys_user.' + $scope.data.sysUserID).then(function (response) {
	if (response.data.result && response.data.result.avatar){
		var avatarPicture = response.data.result.avatar.replace("?t=small", "");
		$scope.avatarPicture = {
			'background-image': "url('" + avatarPicture + "')",
			'color': 'transparent'
		};
	}
});

Once again, I went through the code and made a few minimal modifications to make things work for our purpose and ended up with this.

$scope.uploadInstanceLogoImage = function($event) {
	$event.stopPropagation();
	if($event.keyCode === 9) {
		return;
	}
	var $el = $element.find('input[type=file]');
	$el.click();
};

$scope.attachFiles = function(files) {
	var file = files.files[0];

	var validImage = false;

	switch(file.type) {
		case 'image/jpeg':
		case 'image/png':
		case 'image/bmp':
		case 'image/x-windows-bmp':
		case 'image/gif':
		case 'image/x-icon':
		case 'image/svg+xml':
			validImage = true;
			break;
		default:
			break;
	}

	if(!validImage) {
		alert(file.name + " " + i18n.getMessage("isn't a recognized image file format"));
		return;
	}

	snAttachmentHandler.create('x_11556_col_store_member_organization', $scope.data.sys_id).uploadAttachment(file, {
		sysparm_fieldname: "logo"
	}).then(function(response) {
		$scope.instanceLogoImage = {
			'background-image': "url('" + response.sys_id + ".iix')",
			'color': 'transparent'
		};
	});
};

$scope.instanceLogoImage = "";

All of that would work great just as it is except for one thing: the snAttachmentHandler.create function wants to attach the file to an existing record, and the current design of the process doesn’t actually create the record in the database until after the Host instance accepts the registration request. We had that same problem with the Update Set XML file during the application publishing process, and I ended up attaching the file to the system application record, and then copying it over to the version record later, once the version record was created. Here, though, there really isn’t a record to which it could be attached temporarily, so in order for this to work, I am going to have to go ahead and create the record up front, and then do an update instead of an insert once the Host has granted access to the community. That took a little bit of re-engineering, but once that was done, everything seemed to work out OK. This is something that really needs to be tested thoroughly, though, but just from my cursory check-out it appears to be functioning as desired.

Image upload function in operation

With that up and running, it is time to release yet another Update Set for testing purposes. As with the 0.7.1 release, this should be a straight drop-in replacement for the 0.7/0.7.1 Update Set, and all of the other artifacts should be OK just as they are.

More information on previewing, committing, and testing can be found here and here and here. As always, feedback of any kind in the comments section is welcome, encouraged, and very much appreciated. Any information on your experiences is always a treat, and will give us a little something to review next time out.