- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-14-2023 07:03 AM
created a service portal widget but after entering the serial number nothing happens.
Client :
// Widget Script
function handleCheckIn() {
// Get the scanned barcode from the input field
var scannedBarcode = document.getElementById('barcodeInput').value;
// Call a function to process the scanned barcode (e.g., initiateCheckInProcess)
initiateCheckInProcess(scannedBarcode);
// Clear the input field for the next scan
document.getElementById('barcodeInput').value = '';
}
function initiateCheckInProcess(scannedBarcode) {
// Example: Query the alm_hardware table to find the hardware record based on the scanned barcode
var hardwareRecord = new GlideRecord('alm_hardware');
hardwareRecord.addQuery('serial_number', scannedBarcode);
hardwareRecord.query();
if (hardwareRecord.next()) {
// Hardware record found, get the assigned user
var assignedUser = hardwareRecord.assigned_to;
// Check if the assigned user is valid
if (assignedUser) {
// Create an interaction ticket
var interaction = new GlideRecord('interaction');
interaction.initialize();
interaction.caller_id = assignedUser;
interaction.location = hardwareRecord.location; // Assuming 'location' is a field on the hardware table
interaction.reason = "Laptop refresh";
interaction.insert();
// Log success or perform additional actions
gs.info('Check-in process initiated for user: ' + assignedUser.getDisplayValue());
} else {
// Assigned user not found, log an error or handle as needed
gs.error('Assigned user not found for hardware with serial number: ' + scannedBarcode);
}
} else {
// Hardware record not found, log an error or handle as needed
gs.error('Hardware record not found for serial number: ' + scannedBarcode);
}
}
html template. :
<!-- Barcode Check-In Widget Template -->
<div class="barcode-checkin-widget">
<label for="barcodeInput">Scan Barcode:</label>
<input type="text" id="barcodeInput" placeholder="Scan barcode..." autofocus>
<button onclick="handleCheckIn()">Check-In</button>
</div>
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-18-2023 02:39 AM
Well, your code is faulty: 1st you load the asset's user (assigned to) only after that you check whether the asset's user (assigned to) is actually filled in or not.
And that assuming that you have an actual user id in the actual code in line:
alm_hardware.addQuery('serial_number', 'your_serial_number');
Otherwise the Opened for always ends up empty.
Also why would you look up and load the same thing twice?
I mean you 1st load the asset to get the assigned to user than you load the asset once more to check whether the assigned to user is filled in or not and do the rest of the stuff.
Try formatting your code, maybe it will make such things more obvious.
Also you are not calling createInteraction function with the expected data type: parameter userId - it seems - must be a string.
A functioning code should at minimum look something like:
function lookUpAssetAndCreateInteraction (barCode) {
var alm_hardware = new GlideRecord('alm_hardware');
alm_hardware.setLimit(1);
if (alm_hardware.get('serial_number', barCode)) {
if (!alm_hardware.assigned_to.nil()) {
var facade = new sn_walkup.ExtPointUtil().loadExtension('InteractionFacade');
var queueId = '775bd4f62fd5b110207170682799b6c6';
var reasonId = '837468135b8b3300f6bc098b41f91ab8';
var reasonDescription = 'Something not working';
var badgeId = true;
return facade.createInteraction(
// Here a string is needed, not an object -
// alm_hardware.assigned_to is an object, a GlideRecord,
// '' + alm_hardware.assigned_to on the other hand is a string
'' + alm_hardware.assigned_to,
queueId,
reasonId, // reasonID
reasonDescription, // reason description
false, // is_guest
'', // guest_name
'', // guest_email
false, // is_online_checkin
false, // is_appointment
badgeId ? true : false // is_badge_checkin
);
}
else {
data.error = gs.getMessage('Assigned user not found for hardware with serial number: {0}', barCode);
}
}
else {
data.error = gs.getMessage('Hardware record not found for serial number: {0}', barCode);
}
}
Pasting only function lookUpAssetAndCreateInteraction here.
I am assuming that the rest is correct, like the queue id and that if there is an extension point defined, it is working - I have tested with OOB.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-19-2023 04:29 AM
That means you have not updated your Script Include code as suggested previously.
You should have in it
MyScriptInclude.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
not
MyScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-19-2023 04:50 AM - edited 12-19-2023 04:51 AM
I assume that is because the user has stuff assigned to him that has no serial number.
You could handle this in two ways - depending on the business requirements:
- eliminate assets without a serial number by adding one more condition to the GlideRecord in the server side script include:
gr.addQuery('assigned_to', openedForSysId); // Assuming 'opened_for' is a reference field to sys_user
gr.addNotNullQuery('serial_number') // Don't load assets with no serial number
- or add something else instead of the serial number - if it is missing - when loading the assets:
while (gr.next()) {
serialNumbers.push(gr.serial_number.nil() ?
'#N/A (' + gr.model.getDisplayValue() + ')' :
gr.serial_number.toString());
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
01-05-2024 05:26 AM
Well, sometimes I get the feeling you are not reading my posts :-).
To quote myself from above:
Which raises the issue that even my configuration is not correct as for this script the proper configuration is Mobile / Service Portal really as variable g_modal is not available in Core UI, so the script would fail in that UI.
For Core UI (what you call "classic workspace"*) the solution is totally different, one based on GlideModal and UI Pages.
If you want to support both UIs, you could write a Client Script something like below:
function onChange (control, oldValue, newValue, isLoading, isTemplate) {
if (newValue != '')
getMessage('Incidents of the Opened for user', showList(newValue));
function showList (callerId) {
return function (title) {
if (typeof g_modal == 'undefined')
showListInCoreUI(title, callerId);
else
showListInWorkspace(title, encodeURIComponent('caller_id=' + callerId + '^ORDERBYDESCsys_created_on'));
};
}
function showListInCoreUI (title, callerId) {
var $gm = new GlideModal('show_list');
$gm.setTitle(title);
$gm.setSize(768);
$gm.setPreference('focusTrap', true);
$gm.setPreference('table', 'incident_list');
$gm.setPreference('sysparm_query', 'caller_id=' + callerId + '^ORDERBYDESCsys_created_on');
$gm.setPreference('sysparm_view', 'sys_popup');
$gm.render();
}
function showListInWorkspace (title, query) {
g_modal.showFrame({
'height': '64rem',
'size': 'lg',
'title': title,
'url': '/incident_list.do?sysparm_query=' + query + '&sysparm_isWorkspace=true&sysparm_view=sys_popup',
});
}
}
As is this uses not GlideAjax, so the Script Include is not even needed anymore.
And pops up mostly the same dialog in both "places".
This could be enhanced so that the Script Include is repurposed to return the no. of incidents an Opened for user has and only do the popping if there is at least one incident - using GlideAjax, of course.
Also it would be possible to do a Workspace native solution where the dialog is defined in UI Builder, end responding to framework events triggers the dialog, but it would be far more complex.
And the difference would be maybe nicer visuals (in Service Operations Workspace).
*) In ServiceNow world classic and workspace are actually yin and yen - two different, opposing terms when talking about UI; workspace is what replaces classic UI, so anything but the same thing.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-14-2023 10:03 AM - edited 12-14-2023 10:20 AM
In order to create widgets you need to 1st familiarize yourself with at least the basics of
- AngularJS
- ServiceNow Scripting.
Portal is based around AngularJS, widgets should be creating by adhering to that paradigm.
That means using AngularJS directives to get data from and into the DOM.
No document.whatever or window.whatever or jQuery().whatever (e.g. document.getElementById).
Than you need to know that in ServiceNow - as any any web application - there is two major kind of scripts and entity availability: server side scripts and entities and client side (browser) scripts and entities.
And the two never mix together.
Your Client controller script mixes objects/constructors available only server side into client side code.
There is no GlideRecord in client controllers.
That is a server side (only) capability.
Strictly speaking Core UI (UI 16)/CMS (Portal predecessor) and to some extent, in select widgets Portal to has some GlideRecords support, but not in the form you are using it, and most importantly, while named the same, server side GlideRecord has absolutely nothing to do with client side GlideRecord.
The former is a Java object made available to the server side JavaScript run-time (of course executing server side, in the Tomcat environment, in the Rhino JavaScript engine) while the latter is a JavaScript object that exists/executes in the browser.
Therefore if you want to do stuff on the server as a result of stuff happening in the browser, you need to use - one way or another - XMLHttpRequest.
This is - of course - wrapped into some more usable objects/APIs in most frameworks and so is in Portal too.
The ServiceNow specific APIs to use are server.get(), server.update() or server.refresh().
Back to the original problem, this is how the solution might look like:
Body HTML Template:
<div class="x-contents">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="javascript:void(0)"><span style="vertical-align: middle;"><svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128">
<path d="M240-120q-60 0-95.5-46.5T124-270l72-272q-33-21-54.5-57T120-680q0-66 47-113t113-47h320q45 0 68 38t3 78l-80 160q-11 20-29.5 32T520-520h-81l-11 40h12q17 0 28.5 11.5T480-440v80q0 17-11.5 28.5T440-320h-54l-30 112q-11 39-43 63.5T240-120Zm0-80q14 0 24-8t14-21l78-291h-83l-72 270q-5 19 7 34.5t32 15.5Zm40-400h240l80-160H280q-33 0-56.5 23.5T200-680q0 33 23.5 56.5T280-600Zm480-160-25-54 145-66 24 55-144 65Zm120 280-145-65 25-55 144 66-24 54ZM760-650v-60h160v60H760Zm-360-30Zm-85 160Z" />
</svg></span></a>
</div>
<div class="collapse navbar-collapse">
<form class="navbar-form navbar-left">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">${Scan Barcode}:</span>
<!--
because of the ng-model directive AngularJS will by magic copy values to the scope and from the scope
-->
<input autofocus="autofocus" class="form-control" ng-model="barCode" placeholder="${Scan barcode}..." type="text">
</div>
</div>
<!--
because of directive ng-click AngularJS will link the click even of the button to the specified method of the scope
because of directive ng-disabled the button will be disabled if property barCode of the scope is null/falsy
-->
<button class="btn btn-default" ng-click="handleCheckIn()" ng-disabled="!barCode || runningRequests > 0" type="submit">${Check-In}</button>
</form>
</div>
</div>
</nav>
<!--
Display the error that resulted from submitting the scan - if such an error resulted
-->
<div class="alert alert-danger" ng-if="scanError" role="alert">{{ scanError }}</div>
<!--
Display the list of created interaction records with an anchor opening each
-->
<div class="alert alert-info" ng-repeat="interactionUniqueValue in interactionUniqueValues track by interactionUniqueValue.uniqueValue" role="alert"><a href="/interaction.do?sys_id={{ interactionUniqueValue.uniqueValue }}" target="_SN">${Interaction for: {{ interactionUniqueValue.barCode }}}</div>
</div>
Throw in some Bootstrap goodies - another thing/framework on which Portal is based; makes it look something like:
CSS:
.navbar-brand svg {
position: relative;
top: 50%;
transform: translate(0, -50%);
}
.x-contents {
padding: 2em;
}
Not really necessary, but will make the log look centered, in case it is too big.
Server script:
(function ($sp, options, input, data) {
if (barCodeScanned(input)) {
data.interactionUniqueValue = lookUpAssetAndCreateInteraction(input.barCode);
}
function barCodeScanned (input) {
return input && input.barCode;
}
function lookUpAssetAndCreateInteraction (barCode) {
var alm_hardware = new GlideRecord('alm_hardware');
alm_hardware.setLimit(1);
if (alm_hardware.get('serial_number', barCode)) {
if (!alm_hardware.assigned_to.nil()) {
var interaction = new GlideRecord('interaction');
interaction.initialize();
interaction.caller_id = alm_hardware.assigned_to;
interaction.location = alm_hardware.location; // Assuming 'location' is a field on the hardware table
interaction.reason = "Laptop refresh";
return interaction.insert();
}
else {
data.error = gs.getMessage('Assigned user not found for hardware with serial number: {0}', barCode);
}
}
else {
data.error = gs.getMessage('Hardware record not found for serial number: {0}', barCode);
}
}
})($sp, options, input, data);
Contains the logic extracted from the original solution that actually belongs to server side
Client controller:
api.controller = function ($scope) {
/* widget controller */
var c = this;
$scope.interactionUniqueValues = [];
$scope.runningRequests = 0;
// The method on the $scope object needs to be created, because only stuff on the $scope object can be referenced in the Template HTML
$scope.handleCheckIn = handleCheckIn;
function handleCheckIn () {
// Set the transaction running flags - when higher then 0 the button will be disabled
$scope.runningRequests++;
// The framework takes care extracting the value from the input and of placing the value into property barCode of the widget's scope
// Though this is not really necessary, one could use $scope.barCode directly in the next instruction (below)
var scannedBarcode = $scope.barCode;
// Execute the call to server side, passing in data to be used in the server script and doing something with the response
c.server
.get({ 'barCode': scannedBarcode, })
.then(makeCheckInHandled(scannedBarcode))
// ['<method name>'] accessor is necessary because stupid SN linter
['finally'](checkInFinally);
}
function makeCheckInHandled (barCode) {
return function checkInHandled (response) {
console.log('checkInHandled', response);
// Update the data in scope with new values received from the server
if (response.data.interactionUniqueValue)
$scope.interactionUniqueValues.splice(0, 0, { 'barCode': barCode, 'uniqueValue': response.data.interactionUniqueValue, });
// Set or reset the "last" error message
$scope.scanError = response.data.error;
};
}
function checkInFinally () {
console.log('checkInFinally');
// Reset transaction running flags - when reachis 0 the button will be enabled
$scope.runningRequests--;
}
};
Contains the original logic re-written for AngularJS paradigm.
Also adds some goodies, like showing error and info messages and disabling the button while a transaction is on-going or while there is no serial number entered.
To learn more about AngularJS just visit their API reference site.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-14-2023 01:55 PM - edited 12-14-2023 01:58 PM
@-O- thank a lot for this, i am still very new to this
i meant with some errors :
when i input the data, it did show something but it did not open the interaction
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-14-2023 03:14 PM
About those warning: you can super safely ignore those, but for production code console.log statements would better be removed.
As for the Interaction not being opened, there can be many reasons.
Cross-scope access, domain access, data policy not being fulfilled, etc.
If the interaction has really not been created, then the interaction creation code must be adjusted to count for all restrictions that could exist.
In my PDI no restrictions being present, it created interactions just fine.
Can you post the address for that link in the blue box?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-14-2023 03:17 PM
address to my instance ? do you mean that ?