Important Update: Community URLs redirect issues are partially resolved. Learn More. .

cancel
Showing results for 
Search instead for 
Did you mean: 
Archer Custom Objects Knowledge Base

Articles

Hijacking the Save Buttons

I've recently been working on a project to integrate Archer with an Artificial Intelligence tool which will do some natural language processing on our Risks. As part of that I needed to be able to have Risks sent to the AI every time someone clicked ...

Color Section Header Text

I created a custom object that will check the status value in a values list field. This status field would show multiple values. For example, if you had 4 sections that needed to be completed and none were complete, your values list would show "secti...

jsol5 by Advocate II
  • 1010 Views
  • 0 comments
  • 2 kudos

Toggle Tab Controls (previous,next)

I know that Ali Hari Krishna was kind enough to share what they developed - Custom Object Button for <Previous and Next> Tab set So, I thought I'd post my version. The document I'm providing has a JavaScript and HTML.In the environment I support, we ...

pastedImage_3.png
jsol5 by Advocate II
  • 1057 Views
  • 1 comments
  • 6 kudos

Data Feeds - What's the status?

Until this idea gets some more traction and gets implemented ... Data Feed Failure Notification Thought I'd share a little tool I created to see all data feeds that have errors. I feel most of us get enough emails as it is. So, we do not need an emai...

pastedImage_1.png
jsol5 by Advocate II
  • 1526 Views
  • 1 comments
  • 15 kudos
We've all been there; we package up an application/questionnaire that contains a custom object (or two) from one environment to be installed in another environment (that's the easy part).  The package install goes well and either by checkout testing the application/questionnaire or a barrage of emails/phone calls that the application/questionnaire isn't working anymore and the culprit is that a custom object was referencing field ids that doesn't match the environment anymore, now you have to go tracking down the proper field ids (if it's not documented in the code. hint! hint!) and proceed updating the custom object and hoping a typo or something doesn't break the custom object. Now if there was only a way these pesky custom objects could dynamically get the field IDs based on the environment, they are running in... Well, they can, well with a little help and some magic! I've found a way based on the field name to get the actual field ID of that field dynamically without using any APIs. Custom Object Configuration Add the following function to your custom object (only needs to be in one if you have multiple) in each application/questionnaire that has a custom object that uses field ids.   function lookupFieldId(fldName){ var goFindId = null; var r = /^a$/; try{ $('.FieldLabel').each(function(){ if(($(this).text().indexOf(fldName + ':') != -1) && ($(this).text().indexOf(fldName + ':') == 0)){ goFindId = $(this).find("span")[0].id; return false; } }); } catch (err) {console.log(err)} try {if (!goFindId) goFindId = $('.SectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} try {if (!goFindId) goFindId = $('.SubSectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} try {if (!goFindId) goFindId = $('.SubSectionLabelCollapsible:findField("' + fldName + '")')[0].id;} catch (err) {} return goFindId ? $LM._layoutItems[goFindId.replace( /^\D+/g, '')].fieldId : 0; }   You may have to tweak some of you code depending of if you are currently passing an array of field id's (see Advanced Custom Object Configuration on a different approach to getting multiple field IDs). How to Use To get the field ID, past the field name [string] to the lookupFieldId(); function, like so:   lookupFieldId("Tracking ID");‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Calling the lookupFieldId function and passing "Tracking ID" (this has to match exactly how the field is spelled in Archer) will return the ID for the Tracking ID field.  You could also call the lookupFieldId function as part of defining variables to be use later within the custom object.   var TrackingId = lookupFieldId("Tracking ID");‍‍‍‍‍‍‍‍‍‍‍‍‍‍   The only caveat is that field has to be on the layout or if the field is in a tab, the tab has to be selected.   Make sure you call this function within Sys.Application.add_load(function (){// your code}); Otherwise 0 will be returned for all request field names. Advanced Custom Object Configuration I've also created a way to pass multiple field names at once and the field IDs will be stored in an array that you can reference in your code.  Add the following function to your custom object (only needs to be in one if you have multiple) in each application/questionnaire that has a custom object that uses field ids.   function getFieldId(fld){ var layoutId; if (typeof fld == 'string' || fld instanceof String) { return lookupFieldId(fld) } else if (fld.constructor === Array) { var fieldDefinitions = {}; $.each(fld, function(index,value){ layoutId = lookupFieldId(value); layoutId != 0 ? fieldDefinitions[value] = {'id': layoutId} : fieldDefinitions[value] = {'id': 0}; }); return fieldDefinitions; } } function lookupFieldId(fldName){ var goFindId = null; var r = /^a$/; try{ $('.FieldLabel').each(function(){ if(($(this).text().indexOf(fldName + ':') != -1) && ($(this).text().indexOf(fldName + ':') == 0)){ goFindId = $(this).find("span")[0].id; return false; } }); } catch (err) {console.log(err)} try {if (!goFindId) goFindId = $('.SectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} try {if (!goFindId) goFindId = $('.SubSectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} try {if (!goFindId) goFindId = $('.SubSectionLabelCollapsible:findField("' + fldName + '")')[0].id;} catch (err) {} return goFindId ? $LM._layoutItems[goFindId.replace( /^\D+/g, '')].fieldId : 0; }   Then pass the field names like so:   var myFields = getFieldId(["Tracking ID","Control Procedures","Control Standards","Policy"]);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Calling the getFieldId function and passing an array of field names [string] (field names has to match exactly how the fields are spelled in Archer) will return an array of all the request fields and there associated ids. Then in your custom object you can reference the field ID like so:   myFields['Policy'].id‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍       If a field name passed to either the getFieldId or lookupFieldId() function isn't found in the application/questionnaire, it will return a value of 0 for the id. "Re-Usable" Custom Object Configuration Now if you plan on using this in a lot of applications/questionnaires you might want to put the Advanced Custom Object Configuration version in a .js file and upload it to the company_fields folder\[instance number]\[filename.js] and then just add the following line at the top of one of the custom objects in your application/questionnaire:   <script type="text/javascript" src="../company_files/[instance number]/[filename.js]"></script> ‍‍ <!-- Example --> <script type="text/javascript" src="../company_files/50000/GetFieldIds.js"></script>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   So, if in the future you want to tweak the code or I provide enhancements/bug fixes you don't have to go to every application and make the necessary changes. This configuration is only for those with running on-prem version of Archer being SaaS customers cannot directly upload non-image files to the company_files folder. I tested this a lot but we all know that testing ones own code never reveals any major show stoppers.  So enjoy, and hopefully this make life easier moving application/questionnaires with custom object from one environment to another.  Please provide any feedback and any suggestions to make this better. Archer Versions Supported 6.5.x 6.6.x 6.7.x 6.8.x 6.9.x 6.12.x 6.13.x 6.14.x Support If you have any issues with this custom object, feel free to create a Discussion in Archer Custom Objects or post your comment below.   This is not an RSA supported code and is intended as application/questionnaire customization purposes.  Archer Support or Engineering teams will not be able to support, customize or enhance it.  Use at your own risk! History Version Date Notes 1.0 08/2/2019 Initial release. 1.1 10/31/2019 Updated the way the field is matched.  The :contains finds the first match so if you had a field name of Contact - Primary and another of Contact - Secondary and if you wanted the field id of Contact - Secondary the code would return Contact - Primary field ID being it found the first hit on "Primary". 1.2 05/24/2021 Minor change to the lookupFieldId function that was preventing it from finding certain field types. n/a 02/01/2024 Updated the link to the Custom Object user group. 1.3 02/6/2024 Minor change to the lookupFieldId function to include a new (css) style that was introduced when a cross-reference field is located inside a section (with other fields).
View full article
The following custom object will hide the 'Add New' link for specific cross-reference fields.   <script type="text/javascript"> var debugging = false; Sys.Application.add_load(function (){ var crossReferenceToHideAddNew = getFieldId(['Cross-Reference Field A', 'Cross-Reference Field B']); if (debugging) console.log(crossReferenceToHideAddNew); $.each(crossReferenceToHideAddNew, function(key, value){ if(value.id != 0) { control = $find($CM.getFieldById(value.id).clientId); if (debugging) console.log('Control: ' + control); if(control) { var namingContainer = control._namingContainer; if (debugging) console.log('Naming container: ' + namingContainer); switch(control._displayControlType) { case 'Grid': if (debugging) console.log('Cross-reference is grid display'); if($('#'+namingContainer).find('.add-new').length >0) { if (getStyle('#' + $('#'+namingContainer).find('.add-new')[0].id) == '') document.styleSheets[2].addRule('#' + $('#'+namingContainer).find('.add-new')[0].id, 'display: none !important;'); if (getStyle($('#' + $('#'+namingContainer).find('.add-new').parent()[0].id + '> span:first-of-type')) == '') document.styleSheets[2].addRule('#' + $('#'+namingContainer).find('.add-new').parent()[0].id + '> span:first-of-type' , 'display: none !important;'); } else { if (debugging) console.log(' User doesn\'t have access to create records'); } break; case 'SingleColumn': if (debugging) console.log('Cross-reference is single-column display'); if($('#'+control._referenceFieldId+'_Add_New').length >0) { if (getStyle('#' + $('#'+control._referenceFieldId+'_Add_New')[0].id) == '') document.styleSheets[2].addRule('#' + $('#'+control._referenceFieldId+'_Add_New')[0].id, 'display: none !important;'); } else { if (debugging) console.log(' User doesn\'t have access to create records'); } break; } } } else { if (debugging) console.log('Couldn\'t find field, ' + key); } }); }); function getFieldId(fld){ var layoutId; if (typeof fld == 'string' || fld instanceof String) { return lookupFieldId(fld) } else if (fld.constructor === Array) { var fieldDefinitions = {}; $.each(fld, function(index,value){ layoutId = lookupFieldId(value); layoutId != 0 ? fieldDefinitions[value] = {'id': layoutId} : fieldDefinitions[value] = {'id': 0}; }); return fieldDefinitions; } } function lookupFieldId(fldName){ var goFindId = null; try{ $('.FieldLabel').each(function(){ if($(this).text().indexOf(fldName + ':') != -1){ goFindId = $(this).find("span")[0].id; return false; } }); } catch (err) {} try {if (!goFindId) goFindId = $('.SectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} try {if (!goFindId) goFindId = $('.SubSectionLabel:findField("' + fldName + '")')[0].id;} catch (err) {} return goFindId ? $LM._layoutItems[goFindId.replace( /^\D+/g, '')].fieldId : 0; } $.expr[':'].findField = function(a, i, m) { return $(a).text().replace(/[&\/\\#,+()$~%.'":*?<>{}]/g,'_').match("^" + m[3].replace(/[&\/\\#,+()$~%.'":*?<>{}]/g,'_') + "$"); }; function getStyle(className) { var cssText = ""; var classes = document.styleSheets[2].rules || document.styleSheets[2].cssRules; for (var x = 0; x < classes.length; x++) { if (classes[x].selectorText == className) { cssText += classes[x].cssText || classes[x].style.cssText; } } return cssText; } </script>   How To Use Update the crossReferenceToHideAddNew varible with the field names of the cross-reference field(s) you want to hide the 'Add New' link. Archer Version Supported 6.8+ 6.9+ Support If you have any issues with this custom object, feel free to create a Discussion in the Archer Custom Objects - RSA Link  sub-community or post your comment below. There is dubugging information the script will output.  Change the debugging variable value from false to true and use the browsers developer tools console to see the output.   This is not an RSA supported code and is intended as application/questionnaire customization purposes.  Archer Support or Engineering teams will not be able to support, customize or enhance it.  Use at your own risk!   History Version Date Notes 1.0 07/27/2021 Initial release.  
View full article
I've recently been working on a project to integrate Archer with an Artificial Intelligence tool which will do some natural language processing on our Risks. As part of that I needed to be able to have Risks sent to the AI every time someone clicked Save on a change to the risk in the UI. @Ilya Khen had posted some code that showed how to clone the Save buttons and assign a new function to them which could include your own code and eventually the normal save or apply functionality. I struggled to make that work in 6.9 because of some changes RSA had made to the design of the Save and Apply buttons since Ilya had written the original code. But then I realized, we don't need to clone new buttons at all. We can just re-assign the onclick function for the original Save and Apply buttons. For those who will find it useful, here is my adaptation of Ilya's original code which shows how you can add additional code\integrations to the existing Save and Apply buttons in Archer.   <script> Sys.Application.add_load(function() { $('#master_btnSave1').unbind('click').prop("onclick", null).click(function(){ ERM_Submit('Save');}); $('#master_btnApply1').unbind('click').prop("onclick", null).click(function(){ ERM_Submit('Apply');}); $('#master_btnApplyIcon').unbind('click').prop("onclick", null).click(function(){ ERM_Submit('Apply');}); }); function ERM_Submit(P_Action_Type) { console.log('This is where the custom code goes'); javascript:ShowAnimationAndPostback('master$btn' + P_Action_Type); return false; }; </script>
View full article
At times there needs an ability to hide the ability to copy a record because copying a record would copy over approval and such.  With the following custom object both the Copy in the toolbar and the right-click (within a record) menu will be hidden. Custom Object  Add the following function to your custom object in each application/questionnaire that requires the copy feature to be hidden.   Archer Version 6.6.x <script type="text/javascript"> $('#master_btnCopy').hide(); $('.rmLink:Contains("Copy")').parent().parent().hide(); </script>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍   Archer Version 6.7.x , 6.8.x <script type="text/javascript"> $('#master_btnCopy').hide(); $('.rmText:Contains("Copy")').closest('li').remove(); </script>‍‍‍‍‍‍‍‍   Support If you have any issues with this custom object, feel free to create a Discussion in the RSA Archer Custom Objects  sub-community or post your comment below. NOTE: This is not an RSA supported code and is intended as application/questionnaire customization purposes.  Archer Support or Engineering teams will not be able to support, customize or enhance it.  Use at your own risk! History Version Date Notes 1.0 01/02/2020 Initial release. 1.1 07/24/2020 Update the custom object to reflect the differences in syntax for the different version of Archer.  Thanks Eric Bays for catching this.‌
View full article
I created a custom object that will check the status value in a values list field.   This status field would show multiple values.  For example, if you had 4 sections that needed to be completed and none were complete, your values list would show  "section 1- incomplete", "section 2- incomplete", "section 3... and so forth.   The custom object then looks at those incomplete values, loops through them and then based on the value (and the logic I've built), it would color the font of the sections that are incomplete red.   To get the section headers, you will have to use the browser developer tools to identify the name.   This code is a little old and I am considering altering it so that instead of interrogating the field via javascript/jquery, I would interrogate the record using API.     Anyway, thought I'd share... Hope this helps people make a better end user experience!
View full article
Top Contributors