- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago
Hello!
I created a custom portal widget that allows a user to download a document residing on a task, upload that document after they download it, and reject the document task if necessary - essentially our custom usage of document tasks. It is working except for the uploading document piece - currently it looks like it is attaching in the widget, but does not actually attach to the document task record. I have a Script Includes that closes out the task within the button and that is fine - but I can't figure out why the upload part won't work. I also haven't been able to successfully re-enable the upload button when an attachment exists- the button will stay disabled with my current html script for some reason. Finally, I'm not sure why the remove attachment won't show - I am in admin so I don't think its an ACL at this point.
Here are my scripts if you could take a look! I am sure your giant brains can see what I am doing wrong! 🙂
Thanks in advance!
HTML:
<!-- List Mode -->
<div ng-if="c.data.mode === 'list'" class="doc-task-list">
<!-- Active Tasks -->
<h4 ng-click="c.toggle('active')" class="collapsible-header">
<span ng-class="{'icon-vcr-right': !c.show.active, 'icon-vcr-down': c.show.active}"></span>
Active Tasks <span class="badge">{{c.data.tasks.active.length}}</span>
</h4>
<div ng-show="c.show.active">
<div ng-if="c.data.tasks.active.length === 0">
<p>No active tasks assigned to you.</p>
</div>
<div ng-repeat="task in c.data.tasks.active" class="task-card">
<h5>{{task.short_description}}</h5>
<p>Task: {{task.number}} | Parent: {{task.parent}}</p>
<p>State: <span class="state-badge active">{{task.state_display}}</span></p>
<!-- File upload input -->
<div class="task-card">
SAAR Attachment:
<input type="file" multiple
onchange="angular.element(this).scope().setFiles(this, '{{task.sys_id}}')" />
<div ng-show="taskFiles[task.sys_id].length">
<div ng-repeat="file in taskFiles[task.sys_id]">
<span>{{file.name}}</span>
( <span ng-switch="file.size > 1024*1024">
<span ng-switch-when="true">{{file.size / 1024 / 1024 | number:2}} MB</span>
<span ng-switch-default>{{file.size / 1024 | number:2}} kB</span>
</span> )
<span class="glyphicon glyphicon-remove-circle"
ng-click="removeFiles(file, task.sys_id)"></span>
</div>
</div>
</div>
<!-- Action buttons -->
<div class="action-buttons">
<button class="btn btn-primary" ng-click="c.downloadLatest(task.sys_id)">
Download SAAR Attachment for Signature
</button>
<button class="btn btn-success"
ng-click="uploadFiles(task.sys_id); c.completeTask(task.sys_id)"
ng-disabled="!taskFiles[task.sys_id] || !taskFiles[task.sys_id].length">
Upload PDF and Complete SAAR Signature
</button>
<button class="btn btn-danger" ng-click="c.rejectTask(task.sys_id)">
Reject SAAR
</button>
</div>
</div>
</div>
<!-- Completed Tasks -->
<h4 ng-click="c.toggle('completed')" class="collapsible-header">
<span ng-class="{'icon-vcr-right': !c.show.completed, 'icon-vcr-down': c.show.completed}"></span>
Completed Tasks <span class="badge">{{c.data.tasks.completed.length}}</span>
</h4>
<div ng-show="c.show.completed">
<div ng-if="c.data.tasks.completed.length === 0">
<p>No completed tasks.</p>
</div>
<div ng-repeat="task in c.data.tasks.completed" class="task-card completed">
<h5>{{task.short_description}}</h5>
<p>Task: {{task.number}} | Parent: {{task.parent}}</p>
<p>State: <span class="state-badge completed">{{task.state_display}}</span></p>
</div>
</div>
</div>
CSS:
.doc-task-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.task-card {
padding: 15px;
border: 1px solid #d3d6dc;
border-radius: 6px;
background: #fff;
}
.task-card.completed {
background: #f4f4f4;
color: #888;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 10px;
}
.badge {
display: inline-block;
min-width: 20px;
padding: 3px 8px;
font-size: 12px;
font-weight: 600;
color: #fff;
background-color: #5b9bd5;
border-radius: 10px;
vertical-align: middle;
margin-left: 6px;
}
.state-badge {
display: inline-block;
padding: 2px 8px;
font-size: 12px;
font-weight: 600;
border-radius: 12px;
color: #fff;
}
.state-badge.active {
background-color: #0078d4;
}
.state-badge.completed {
background-color: #4caf50;
}
.state-badge.rejected {
background-color: #d9534f;
}
.state-badge.in-progress {
background-color: #f0ad4e;
}
.collapsible-header {
cursor: pointer;
display: flex;
align-items: center;
font-weight: 600;
margin: 15px 0 10px;
}
.collapsible-header span {
margin-right: 6px;
}
Server Script:
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago - last edited 4 weeks ago
Hi @kristenmkar ,
The script isn't working because of the onchange on your input. I don't think it's doing what you think it's doing.
Once you used vanillajs or the browser api "onchange" you stepped out of the AngularJS realm. I know in your code you make up for it with the $apply and scope however that's not applied on the onchange itself. So the sys_id is not actually being captured.
<input type="file" multiple
onchange="angular.element(this).scope().setFiles(this, '{{task.sys_id}}')" />
In this scenario '{{task.sys_id}}' will be passing '{{task.sys_id}}' as the value, not the actual sys_id.
Just like the angular class was used to get to the "setFiles" method you'll need to do the same thing to get the task.sys_id.
Try doing this:
<input type="file" multiple
onchange="angular.element(this).scope().setFiles(this, angular.element(this).scope().task.sys_id)" />
Edit:
Other ways to pass/get the task sys_id:
<input type="file" multiple
onchange="angular.element(this).scope().setFiles(this)" ng-attr-task_sys_id="{{task.sys_id}}" />
And in the setFiles method get the value of the attribute "tasksysid" value
$scope.setFiles = function(element) {
var sys_id = element.attributes.tasksysid.value;
$scope.$apply(function() {
if (!$scope.taskFiles[sys_id]) {
$scope.taskFiles[sys_id] = [];
}
for (var i = 0; i < element.files.length; i++) {
$scope.taskFiles[sys_id].push(element.files[i]);
}
});
};
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Hi @kristenmkar ,
Ensure table_name and table_sys_id are correct and passed to the attachment REST call...In the upload REST URL (/api/now/attachment/file?...), make sure table_sys_id=sys_id matches the record you want.....Double check c.data.table equals "sn_doc_task" or whichever table you want.....
Modify your onchange input to get the actual sys_id properly....Instead of onchange="angular.element(this).scope().setFiles(this, '{{task.sys_id}}')", use something like onchange="angular.element(this).scope().setFiles(this, task.sys_id)" if that's valid, or store task.sys_id in an attribute and retrieve it in the handler....Ensure the Angular digest ($scope.$apply) is triggered correctly.
If you found my response helpful, please mark it as ‘Accept as Solution’ and ‘Helpful’. This helps other community members find the right answer more easily and supports the community.
Kaushal Kumar Jha - ServiceNow Consultant - Lets connect on Linkedin: https://www.linkedin.com/in/kaushalkrjha/
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
I used the second version of the code in my previous post.
<input type="file" multiple
onchange="angular.element(this).scope().setFiles(this)" ng-attr-task_sys_id="{{task.sys_id}}" />
$scope.setFiles = function(element) {
var sys_id = element.attributes.tasksysid.value;
$scope.$apply(function() {
if (!$scope.taskFiles[sys_id]) {
$scope.taskFiles[sys_id] = [];
}
for (var i = 0; i < element.files.length; i++) {
$scope.taskFiles[sys_id].push(element.files[i]);
}
});
};
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Awesome, thank you - I just needed a sanity check. Looks like there are some license entitlement restrictions that are blocking a few of the attachments within Caller Access- however it is definitely working. I really appreciate all your help, I owe you! Thank you again! 🙂
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
I apologize for bothering you again - random question @ChrisBurks - what is a good way to add a check to my script include? Everything seems to work fine, but I have seen a few where it looks as though the attachment did not come over properly and attach to the task.
Thank you again! 🙂
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
No apologies necessary.
What type of "check" are you looking for? Are you looking to check the count of the attachments before actually marking the task complete?
The script include executes after attaching files. So if attachments aren't making it, are you seeing any errors? Maybe catching errors in a variable or at least marking an error occurred during attaching. Then you can condition on if there is an error don't close.
Another check that can be performed is using the "Retrieve metadata for attachments" method filtering on table_sys_id=< record sys id >. This will bring back metadata like the name, size, etc. plus you can get a count and match that count or file names up with the File object to ensure all files are there.
At the very least you could just get the count of attachments before the final update of the task.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Thanks so much for the response- it is much appreciated!
I was trying to devise a way to make sure the attachment was indeed added - I was attempting to add some code to the script include so it could give me an error when something didn't actually attach to the document task/record, but it's strange - sometimes the new attachment does not attach and no error populates - it happens randomly and not often. Another strange thing is when I check the attachment table, I do not see it there either - so no idea where the attachment actually ended up - no corresponding logs either. Apparently disappeared into thin air? I thought maybe it was the type of attachment, but that wasn't the problem- very peculiar.
Anyways, I like your ideas of obtaining the count! I think that would be a good check! Let me look into that method! I think that is probably the best way to go?
Thanks again! 🙂