Find your people. Pick a challenge. Ship something real. The CreatorCon Hackathon is coming to the Community Pavilion for one epic night. Every skill level, every role welcome. Join us on May 5th and learn more here.

Client controller must contain a JavaScript function, see default value for example

ServiceNow Use6
Tera Guru

Hi,

In the widget i am getting error under Client Controller

"Client controller must contain a JavaScript function, see default value for example". . Kindly help.

 

			function($scope, TimeCardPortalService, spUtil, $sanitize, $rootScope, $window, $timeout, spModal, $q) {
	var c = this;
	/*
	console.log('c:');
	console.log(c);
	*/
				
	_highlightField();
	var skipNextRecordWatchUpdate = false;
	$scope.pageSize = $scope.data.pageSize;
	var minWidthFields = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "total"];
	$scope.getWidth = function(field) {
			if(field && $scope.data.column_meta[field] 
			 && $scope.data.column_meta[field]['width_in_percent']) {
				return {
						'min-width': $scope.data.column_meta[field]['width_in_percent'] + '%'
				};
			}
			
			// Days and Total will have Minimum width of 5%.
			// Smaller the resolution, smaller the scroll. 
			if (field && $scope.data.column_meta[field] && (minWidthFields.indexOf(field) > -1)) {
				return {
					'min-width': '5%'
				}
			}
			
			// Default width of 10% which has not configured width_in_percent.
			return {
				'min-width': '10%'
			}
	}
	
	function updatePagination(from, to) {
			$scope.total = c.data.totalRows;
			$scope.current = {
					to: to > c.data.totalRows? c.data.totalRows: to,
					from: from,
					pageSize: $scope.pageSize
			};
			$scope.paginatedList = c.data.list.slice(from-1, to);
	}
	
	updatePagination(1, $scope.pageSize);
	
	$scope.$on('sp_load_new_page', function(event, page) {
			$scope.paginatedList = c.data.list.slice(page.from-1, page.to);
	});
	
	$scope.cancelEdit = function(evt, index){
		if(_isEmptyObject($scope.data.editTimecardObj))
			return;
		_preventDefault(evt);
		$scope.data.list[index] = $scope.data.editTimecardObj;
		$scope.data.editTimecardObj = {};
		$scope.data.timecardInEditMode = '';
		$scope.data.highlightedField = $scope.data.previousHighlightedField || $scope.data.defaultField;
		$scope.highlightDuplicates();
	};
	
	$scope.saveRecord = function(evt, record, promiseResolver){
		_preventDefault(evt);
		if($scope.data.recordSetForDelete == record.sys_id) return;
		var data = {};
		var editabelFields = $scope.data.editable_fields;
		for (var i in editabelFields){
			var field = editabelFields[i];
			if(field === "rate_type" || field === "project_time_category" || field === "resource_plan" ) {
				// we don't need sanitization here as this is not directly edited by user
				// this value is only selected from pre existing values and typed by user
				data[field] = record[field].value;
				continue;
			}
			var val = $sanitize(record[field].display_value);
			if(!isValidNumber(val))
				data[field] = 0;
			else
				data[field] = val;

		}
		$scope.data.timecardInEditMode = '';
		$scope.data.editTimecardObj = {};
		TimeCardPortalService.updateTimeCard(record.sys_id, data, editabelFields.join(',')+',total').then(function(response){
			var result = response.data.result;
			for(var field in result){
				record[field].display_value = result[field].display_value;
				record[field].value = result[field].value;
				if(isValidNumber(result[field].value))
					record[field].invalid = false;
				$scope.data.highlightedField = $scope.data.previousHighlightedField;
			}
			var status = response.data.status;
			if(status == 'error')
				refreshWidget();
			else {
				skipNextRecordWatchUpdate = true;
				$scope.highlightDuplicates();
			}
			if(promiseResolver) {
				promiseResolver();
			}
		});
	};

	$scope.deleteRecord = function(evt){
		var _target = $(evt.target);
		var _id = _target.closest('.flex-item').data('id')  || evt.target.id;
		var record = $scope.data.list.filter(function(listItem) {
			return (listItem.sys_id == _id);
		})[0];
		var confirmModalCtrl;

		var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
		$scope.data['tcPopover' + record.sys_id] = false;

		$scope.data.confirmDialogOpts = {
			title: $scope.data.messages.confirmDlgTitle,
			text: $scope.data.messages.confirmMssg,
			okTxt: $scope.data.messages['delete'],
			cancelTxt: $scope.data.messages.cancel,
			okBtnClass: 'btn-danger',
			ok: function(){
				$scope.data.recordSetForDelete = record.sys_id;
				TimeCardPortalService.deleteTimeCard(record.sys_id).then(function(response){
					$scope.data.recordSetForDelete = '';
					$scope.data.list.splice(index, 1);
					skipNextRecordWatchUpdate = true;
					$scope.highlightDuplicates();
					c.data.totalRows -= 1;
					updatePagination($scope.current.from, $scope.current.to);
					$rootScope.$broadcast("record-delete");
				});
				confirmModalCtrl.close();
			},
			afterOpen: function(ctrl){
				confirmModalCtrl = ctrl;
			},
			afterClose : function(){
				$scope.showConfirm = false;
				confirmModalCtrl = null;
				$scope.data.confirmDialogOpts = null;
			}
		};
		$scope.showConfirm = true;
		$scope.safeDigest();
		_preventDefault(evt);
	};
	
	function updateRecordState(evt, newState) {
		var _target = $(evt.target);
		var _id = _target.closest('.flex-item').data('id') || evt.target.id;
		var record = $scope.data.list.filter(function(listItem) {
			return (listItem.sys_id == _id);
		})[0];
		TimeCardPortalService.updateTimeCardState(record.sys_id, newState).then(function(response) {
			var status = response.data.status;
			var message = response.data.data.message;
			if (status != 'success')
				spUtil.addErrorMessage('message');
			refreshWidget();
		})
	}

	$scope.updateRecordState = function(evt, newState){
		$scope.hideTcPopover();
		if($scope.data.timecardInEditMode) {
			cancelGridEditModePromiseWrapper(event, true).then(function () {
				updateRecordState(evt, newState);
			})
		} else {
			updateRecordState(evt, newState);
		}
	};

	$scope.recallRecord = function(evt){
		var _target = $(evt.target);
		var _id = _target.closest('.flex-item').data('id')  || evt.target.id;
		var record = $scope.data.list.filter(function(listItem) {
			return (listItem.sys_id == _id);
		})[0];
		var confirmModalCtrl;

		var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
		$scope.data['tcPopover' + record.sys_id] = false;

		$scope.data.confirmDialogOpts = {
			title: $scope.data.messages.recallDlgTitle,
			text: $scope.data.messages.recallConfirmMssg,
			okTxt: $scope.data.messages.recall,
			cancelTxt: $scope.data.messages.cancel,
			okBtnClass: 'btn-danger',
			ok: function(){
				TimeCardPortalService.updateTimeCardState(record.sys_id, 'Recalled').then(function(response) {
					var status = response.data.status;
					var message = response.data.data.message;
					if (status != 'success')
						spUtil.addErrorMessage(message);
					else
						spUtil.addInfoMessage(message);
				})
				confirmModalCtrl.close();
			},
			afterOpen: function(ctrl){
				confirmModalCtrl = ctrl;
			},
			afterClose : function(){
				$scope.showConfirm = false;
				confirmModalCtrl = null;
				$scope.data.confirmDialogOpts = null;
			}
		};
		$scope.showConfirm = true;
		$scope.safeDigest();
		_preventDefault(evt);
	};

	$scope.generateTimecards = function(){
		TimeCardPortalService.generateTimecards({
			timesheet_id: $scope.data.timesheetId,
			tables: $scope.data.tables
		}).then(function(response){
			spUtil.addInfoMessage(response.data.message);
		});
	};

	$scope.copyFromPreviousTimesheet = function(){
		$rootScope.$broadcast('tcp.copy.timesheet');
	};

	$scope.validateInput = function(field){
		var val = field.display_value;
		if(isValidNumber(val))
			field.invalid = false;
		else
			field.invalid = true;
	};

	function isValidNumber(number){
		var decimalSeperator = g_user_decimal_separator;
		var regx =  new RegExp('^-?((\\d+(\\'+ decimalSeperator + '\\d+)?)|(\\' + decimalSeperator + '\\d+))$');
		if(regx.test(number) && !isNaN(parseFloat(number))){
			return true;
		}
		return false;
	}

	function refreshWidget(start, end){
		/* Carry forward options for grid customizaions */
		start = start ? start:1;
		end = end ? end: $scope.pageSize
		$scope.data.options = $scope.options.tm_grid_options;
		spUtil.update($scope).then(function(){
			var timecardToEdit = $scope.data.timecardToEdit;
			if(timecardToEdit){
				$scope.data.timecardToEdit = '';
				$scope.showTimecardInEditMode(timecardToEdit);
			}
			updatePagination(start, end);
			$scope.highlightDuplicates();
			$scope.addSyncScrollEventListener();
		});
	}

	var filterQuery = 'time_sheet='+ $scope.data.timesheetId;
	var recordWatcherTimer;
	spUtil.recordWatch($scope, 'time_card', filterQuery, function(){
		$timeout.cancel(recordWatcherTimer);
		recordWatcherTimer = $timeout(function(){
			if (skipNextRecordWatchUpdate) {
				skipNextRecordWatchUpdate = false;
				$scope.highlightDuplicates();
			} else
				refreshWidget();
		}, 250);
	});

	$scope.mergeDuplicateTimeCards = function(record) {
		TimeCardPortalService.mergeDuplicateTc({
			timesheet_id: $scope.data.timesheetId,
			record : record
		}).then(function() {
			refreshWidget();
		});
	};

	$scope.$on('tcp.refresh.widgets', function(){
		skipNextRecordWatchUpdate = true;
		refreshWidget();
	});

	$scope.$on('tcp.highlight.field',function(scope, field){
		_highlightField(field);
	});

	$scope.$on('tcp.edit.timecard', function(scope, timecardId){
		$scope.showTimecardInEditMode(timecardId);
	});
	
	var foundRecord = null;
	
	$scope.$on('pagination.send.window.for.index', function() {
		$scope.enableEditMode(foundRecord[0]);
		foundRecord = null;
	});
	
	$scope.$on('ng.repeat.completed.tc-grid', function() {
		if(foundRecord) {
			$scope.enableEditMode(foundRecord[0]);
			foundRecord = null;
		}
	});

	function cancelGridEditModePromiseWrapper(event, buttonClicked) {
		return $q(function(resolve) {
			$scope.cancelGridEditMode(event, buttonClicked, resolve);
		});
	}
	
	function submitTimesheet (timesheetId, oldState) {
		TimeCardPortalService.submitTimesheet(timesheetId).then(function(response){
				$scope.data.canSubmitTimesheet = response.data.canSubmitTimesheet;
				var newState = response.data.data.value;
				$scope.data.state = response.data.data;
				var mssg = response.data.message;
				if(response.data.status == 'error'){
					refreshWidget();
					if(mssg)
						spUtil.addErrorMessage(mssg);
				}
				else if(oldState == newState){
					refreshWidget();
					if(mssg)
						spUtil.addInfoMessage(mssg);
				}
				else if(mssg && response.data.status == 'success'){
					spUtil.addInfoMessage(mssg);
				}
			}
		);
	}
	
	function submitTimesheetButtonClicked(event, timesheetId, oldState) {
		if($scope.data.timecardInEditMode) {
			cancelGridEditModePromiseWrapper(event, false).then(function () {
				submitTimesheet(timesheetId, oldState);
			})
		} else {
			submitTimesheet(timesheetId, oldState);
		}
	}

	$scope.$on('submitTimesheetButtonClicked', function(event, data) {
		submitTimesheetButtonClicked(data.event, data.timesheetId, data.oldState);
	});

	$scope.showTimecardInEditMode = function(timecardId){
		var index = null
		var record = $scope.data.list.filter(function(record, idx){
			if(record.sys_id == timecardId) {
				index = index ? index: idx;
				return true;
			}
			return false;
		});
		
		if(record.length > 0) {
			foundRecord = record;
			
			// if index is on current page. directly call enableEditmode.
			// else figure out on which page it is.
			var pageIndex = index + 1;
			if (pageIndex <= $scope.current.to && pageIndex >= $scope.current.from) {
				$scope.enableEditMode(foundRecord[0]);
			} else {
				// figure out which page it will be.
				var targetPage = parseInt(pageIndex / $scope.current.pageSize);
				targetPage += pageIndex % $scope.current.pageSize ? 1 : 0;
				$rootScope.$broadcast('pagination.get.window.for.index',  {
					targetPage: targetPage
				});
			}
		} else {
			$scope.data.timecardToEdit = timecardId;
			skipNextRecordWatchUpdate = true;
			refreshWidget();
		}
	};

	function _highlightField(field){
		if(field)
			$scope.data.highlightedField = field;
		else
			$scope.data.highlightedField = $scope.data.defaultField;
	}

	function _preventDefault(evt){
		if(evt){
			evt.stopPropagation();
			evt.preventDefault();
		}
	}

	function _isEmptyObject(obj){
		return !obj || Object.keys(obj).length === 0;
	}

	$scope.openTimecard = function(ev){
		_preventDefault(ev);
		var _target = $(ev.target);
		var sysId = _target.closest('.flex-item').data('id');
		$scope.data['tcPopover' + sysId] = false;

		spUtil.get('widget-modal', {embeddedWidgetId: 'widget-form', embeddedWidgetOptions: {table:'time_card', sys_id: sysId, view: 'worker_portal'}}).then(function(widget){
			var modalCtrl;
			var unregister = $scope.$on('sp.form.record.updated', function(){
				skipNextRecordWatchUpdate = true;
				refreshWidget();
				$rootScope.$broadcast("record-add");
				modalCtrl.close();
			});
			widget.options.afterClose = function(){
				unregister();
				$scope.data.modalInstance = null;
				modalCtrl = null;
				$rootScope.$broadcast("timesheet_portal_form_closed");
			};
			widget.options.afterOpen = function(ctrl){
				modalCtrl = ctrl;
			};
			$scope.data.modalInstance = widget;
		});
	};

	$scope.highlightDuplicates = function() {
		$scope.duplicateList = {};
		if($scope.data.list && $scope.data.list.length) {
			for(var i=0; i< $scope.data.list.length; i++) {
				list_item = $scope.data.list[i];
				var _sd = list_item['task.short_description'].value.replace(/\s/g, "");
				var _state = list_item.state.value;
				if($scope.data.restrictedStatesForDuplicateMerge.indexOf(_state.toLowerCase()) !== -1)
					continue;
				var _ptc = list_item.project_time_category.value || '';
				var _taskSysId = list_item.taskSysId;
				
				//Since task can be associated with any categories
				var _category = list_item.category.value;
				
				var _rateType = (list_item.rate_type && list_item.rate_type.value) || '';
				var _rpln = (list_item.resource_plan && list_item.resource_plan.value) || '';

				var uniqueItemKey = _sd + _state + _ptc + _rateType + _taskSysId + _category + _rpln;
				var duplicateList = $scope.duplicateList;
				if(duplicateList[uniqueItemKey] && duplicateList[uniqueItemKey].length && _taskSysId) {
					duplicateList[uniqueItemKey][0].isDuplicate = true;
					duplicateList[uniqueItemKey].push(list_item);
					list_item.isDuplicate = true;
				} else {
					duplicateList[uniqueItemKey] = [];
					duplicateList[uniqueItemKey].push(list_item);
				}
			}
		}

		for(var prop in $scope.duplicateList) {
			if($scope.duplicateList[prop] && $scope.duplicateList[prop].length === 1) {
				$scope.duplicateList[prop][0].isDuplicate = false;
			}
		}
		$scope.$$phase || $scope.$root && $scope.$root.$$phase || $scope.$digest();
	};

	$scope.highlightDuplicates();
}

 

Regards

Suman P.

4 REPLIES 4

Tanushree Maiti
Kilo Patron

Try this tips if it works for you.

https://www.servicenow.com/community/developer-forum/client-controller-must-contain-a-javascript-fun...

 

https://www.servicenow.com/community/developer-forum/error-message-on-client-controller-quot-it-shou...

 

Please mark this response as Helpful & Accept it as solution if it assisted you with your question.
Regards
Tanushree Maiti
ServiceNow Technical Architect
Linkedin:

lauri457
Tera Sage

The controller script is assigned to the directive definition object with an eval() so it needs to be properly formed. Either you write it as a function (my pdi prevented an anon function with a linter error), to which it prepends "api.controller=" or you yourself include the whole "api.controller = function() {...." 

 

In your case there is a client script that doesn't like the empty spaces/tabs in the beginning

ServiceNow Use6
Tera Guru

Hi @lauri457,

Here is the error I am getting.

unexpected.png

 

Regards

Suman P.

Add the text "api.controller =" in front of the function keyword or give any valid name for the function

api.controller = function(){...
//OR
function ctrl(){....