- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
06-07-2023 06:42 PM - edited 06-19-2023 08:06 PM
Using the articles below I was able to come up with my own custom widget incorporating the features of each minus the slidenav as I wasn't a fan of the functionality compared to the OOB widget and the window focus. If this was a sidebar instead would have been perfect. The way I have it setup is if a user has no open interactions they will see the bubble/balloon tip, first screenshot. If you hover over the chat button and move away the bubble will disappear. We found the bubble is in the wrong spot on some catalog requests and you can't click submit and the main reason for this change as I have embedded the widget in the portal footer. But in order to get users attention I wanted it to show the bubble on the page loading. If a user has unread messages there will be a red dot, second screenshot. Lastly, if a user has an open interaction the system will automatically open the chat window upon entering the portal or changing pages during an open interaction.
In case there are others out there that want to do the same here is my code:
Body HTML
<div class="conversation-button-container"> <div class="conversation-region fade" ng-class="{'open': $ctrl.isWindowVisible}"> <div class="sn-connect sn-connect-floating"> <div class="sn-connect-floating-wrapper loaded"> <div class="conversation-container"> <iframe title="${Chat Support}" class = "chat-frame" scrolling="no" horizontalscrolling="no" verticalscrolling="no" frameborder="none" ng-src="{{$ctrl.vaSource}}"> </iframe> </div> </div> </div> </div> <button aria-label="{{$ctrl.helpButtonAriaLabel}}" class="help-button" ng-attr-tabindex="0" ng-class="{'state-open': $ctrl.isWindowVisible, 'state-unread': $ctrl.hasUnreadMessages}" ng-click="$ctrl.toggleWindow()" ng-style="{'background-color': $ctrl.options.button_color}" ng-mouseleave="$ctrl.hideConversationBubble()" ng-mousedown="$ctrl.showConversationWindow()" ng-class1="{'state-open': $ctrl.showConversationBubble}" ng-attr-tabindex="0"> <!-- ng-mouseenter="$ctrl.showConversationBubble()" --> <div class="hover-overlay"></div> <span aria-hidden="true" ng-class="$ctrl.isWindowVisible ? 'help-icon icon-close icon-cross' : 'help-icon icon-open sn-va-widget-icon'"></span> </button> <!-- Conversation bubble--> <!-- <div ng-if="(!$ctrl.firstPress || ($ctrl.timeoutConversationBubble && !$ctrl.expiredConversationBubble))" class="conversation-bubble"> --> <div ng-if="($ctrl.enableConversationBubble && !$ctrl.firstPress) || ($ctrl.timeoutConversationBubble && !$ctrl.expiredConversationBubble)" class="conversation-bubble"> <font class="conversation_bubble_question">{{::data.conversationBubbleQuestion}}</font><br /> <font class="conversation_bubble_answer">{{$ctrl.conversationBubbleAnswer}}</font> </div> </div>
CSS
$color-darker: #485563; $color-white: #ffffff; $window-width: 375px; $window-height: 600px; $button-dimensions: 60px; $button-height: 60px; $bottom-margin: 15px; $bottom-width: 375px; $sn-chatbot-animation-speed: 300ms; // Conversation Bubble .conversation-bubble { border: 1px solid #d4d4d4; background-color: #62baa2; border-radius: $button-dimensions; display: inline-block; width: 325px; height: 70px; box-shadow: 0px 2px 11px #ababab; -moz-box-shadow: 0px 2px 11px #ababab; -o-box-shadow: 0px 2px 11px #ababab; padding: 8px 12px; } .conversation_bubble_question { font-weight: bold; } .conversation_bubble_answer { font-style: oblique; } // OVERRIDE TO DISPLAY RECORD CARDS .sn-card-component_records { display: block !important; } .conversation-button-container { position: fixed; right: 30px; bottom: 15px; z-index: 20; .conversation-region { position: relative; opacity: 0; visibility: hidden; &.open { transition: $sn-chatbot-animation-speed ease-in opacity; opacity: 1; visibility: visible; } } .help-button { position: relative; width: 60px; color: #fff; float: right; border: none; height: 60px; border-radius: $button-dimensions; box-shadow: 0px 2px 11px #ababab; -moz-box-shadow: 0px 2px 11px #ababab; -o-box-shadow: 0px 2px 11px #ababab; padding: 0; background-color: #8D8DE0; &::before { content: ""; width: 60px; height: 60px; border-radius: 50%; position: absolute; top: 0; left: 0; } &:hover::before { background-color: rgba(0,0,0,0.2); } &:focus { outline: thin dotted; outline-color: gray; outline: 5px auto -webkit-focus-ring-color; outline-offset: 2px; } &.state-unread { & > span:after { content: ' '; position: absolute; top: -17px; right: -14px; width: 16px; height: 16px; background-color: #ed6e5c; border-radius: 25px; } } .help-icon { pointer-events: none; position: relative; } .sn-va-widget-icon { background-image: url('/images/sn-va-sp-widget/sn-va-sp-widget-icon.svg'); height: 32px; top: 1px; left: 12px; width: 36px; display: block; } .icon-close { font-size: 20px; } } } // window sizing .sn-connect-floating { position: relative; bottom: 15px; right: 0; display: none; .sn-connect-floating-wrapper { width: $window-width; transition: border-bottom: 1px solid #bdc0c4; margin-right: 0; right: 0; box-shadow: 0px 2px 11px #ababab !important; -moz-box-shadow: 0px 2px 11px #ababab !important; -o-box-shadow: 0px 2px 11px #ababab !important; transition: max-height 0s $sn-chatbot-animation-speed; display: none; border-radius: 10px; overflow: hidden; .conversation-container { height: 100%; display: none; .chat-frame { max-height: $window-height; height: calc(100vh - $button-height - ($bottom-margin * 3)); width: $window-width; margin-bottom: -5px; border: none; overflow: hidden; background-color: #fff; } // Immediate div is autogenerated from serviceportal & > div { height: 100%; } } } } // Mobile SCSS @media (max-width: 425px) { .sn-connect-floating { .sn-connect-floating-wrapper { position: fixed; max-height: 100%; width: 100%; left: 0; right: 0; bottom: calc($button-height + 20px); top: 0; border-radius: 0px !important; .conversation-container { position: absolute; max-height: 100%; width: 100%; height: 100%; left: 0; right: 0; top: 0; bottom: 0; .chat-frame { max-height: initial !important; position: absolute; width: 100%; height: 100%; left: 0; right: 0; top: 0; bottom: 0; } } } } }
Server Script
(function() { var instanceGR = $sp.getInstanceRecord(); options.va_url_params = options.va_url_params || ""; options.button_color = options.button_color || "red"; data.conversationBubbleTimeout = 500; //data.conversationBubbleQuestion = gs.getMessage(gs.getProperty('qt_va_conversation_bubble_question')); // original code data.conversationBubbleQuestion = gs.getMessage("Hi {0}, I am your Virtual Assistant! Can I be of assistance? An example would be:", [gs.getUser().getFirstName()]); data.toggle_chat_time = 500; var topicArr = []; var grTopic = new GlideRecord('sys_cs_topic'); grTopic.addEncodedQuery('active=true^published=true^discoverable=true^design_category!=b1dfd7c254103300fa9b212481c502cb^ORdesign_category=NULL^design_category!=NULL'); grTopic._query(); while (grTopic._next()) { topicArr.push(gs.getMessage(grTopic.getDisplayValue())); } topicArr.join(); data.topicArr = topicArr; //start of added code var interaction = new GlideAggregate('interaction'); var user = gs.getUserDisplayName(); interaction.addActiveQuery(); interaction.addQuery('opened_for.name', '=', user); interaction.addAggregate('COUNT'); interaction.query(); var interactionCount = 0; if (interaction.next()) { interactionCount = interaction.getAggregate('COUNT'); } data.interaction_count = interactionCount; //end of added code })();
Client Controller
function link($log, $scope, $element, $document, spModal, $q, $timeout, $window) { 'use strict'; /* widget controller */ var $ctrl = this; var topicArr = $ctrl.data.topicArr; var interaction_count = $ctrl.data.interaction_count; $ctrl.data.interaction_count = interaction_count; $ctrl.widget_source = ''; var $spContainer = $document.find('.sp-page-root'); var $widgetParent = $element.parent(); var START_SUPPORT_CONVERSATION = "${Start Support Conversation}"; var CLOSE_SUPPORT_CONVERSATION = "${Close Support Conversation}"; $ctrl.isWindowVisible = false; $ctrl.hasUnreadMessages = false; $ctrl.firstPress = false; $ctrl.vaSource = ''; $ctrl.helpButtonAriaLabel = START_SUPPORT_CONVERSATION; //added code starts here $ctrl.showConversationBubble = function() { if (!$ctrl.conversationBubbleAnswer) { var topicStr = topicArr[Math.floor(Math.random() * topicArr.length)]; $ctrl.conversationBubbleAnswer = topicStr; } $ctrl.enableConversationBubble = true; }; $ctrl.hideConversationBubble = function() { $ctrl.enableConversationBubble = false; $ctrl.expiredConversationBubble = true; $ctrl.conversationBubbleAnswer = ''; }; $ctrl.showConversationWindow = function() { if ($ctrl.data.interaction == true) { $ctrl.widget_source = $ctrl.options.url; } }; //added code ends here $ctrl.toggleWindow = function() { if ($ctrl.isWindowVisible) { $ctrl.isWindowVisible = false; $ctrl.helpButtonAriaLabel = START_SUPPORT_CONVERSATION; $timeout(function() { if (!$ctrl.isWindowVisible) { $element.find('.conversation-container').css("display", "none"); $element.find('.sn-connect-floating').css("display", "none"); $element.find('.sn-connect-floating-wrapper').css("display", "none"); // ios overlay hacky fix. $document.find('.touch_scroll').css("-webkit-overflow-scrolling", "touch"); } }, 300); } else { if (!$ctrl.firstPress) { $ctrl.firstPress = true; $ctrl.vaSource = '/$sn-va-web-client-app.do?sysparm_nostack=true&sysparm_stack=no'; if ($ctrl.options.va_url_params) { $ctrl.vaSource = $ctrl.vaSource + '&' + $ctrl.options.va_url_params; } } $ctrl.isWindowVisible = true; $ctrl.hasUnreadMessages = false; $ctrl.helpButtonAriaLabel = CLOSE_SUPPORT_CONVERSATION; $element.find('.conversation-container').css("display", "block"); $element.find('.sn-connect-floating').css("display", "block"); $element.find('.sn-connect-floating-wrapper').css("display", "block"); // ios overlay hacky fix. $document.find('.touch_scroll').css("-webkit-overflow-scrolling", "auto"); } }; $window.addEventListener("message", function(e) { if (e.data === 'sn-va-web-client-app-new-message' && $ctrl.isWindowVisible === false) { $ctrl.hasUnreadMessages = true; } else if (e.data === 'sn-va-web-client-app-trigger-login') { $window.location.reload(true); } }); $element.find('.help-button').on("mouseup", function(e) { e.target.blur(); e.stopPropagation(); }); $element.find('.help-icon').on("mouseup", function(e) { e.target.blur(); e.stopPropagation(); }); $ctrl.openWindow = function() { $ctrl.isWindowVisible = true; // delay before clearing unread message indicator // in-case user opens and closes quickly $timeout(function() { if ($ctrl.isWindowVisible) { $ctrl.hasUnreadMessages = false; } }, 500); }; var _closeWindow = function() { $ctrl.isWindowVisible = false; $ctrl.hasActiveConversation = false; }; }
Link
function link(scope) { var $ctrl = scope.$ctrl, $timeout = $injector.get('$timeout'); $timeout(function() { var interaction_count = $ctrl.data.interaction_count; $ctrl.data.interaction_count = ''; if (interaction_count < 1 && !$ctrl.isWindowVisible) { var topicStr = $ctrl.data.topicArr[Math.floor(Math.random() * $ctrl.data.topicArr.length)]; $ctrl.conversationBubbleAnswer = topicStr; $ctrl.timeoutConversationBubble = true; } if (interaction_count > 0 && !$ctrl.isWindowVisible) { $ctrl.toggleWindow(); } }, $ctrl.data.toggle_chat_time && $ctrl.data.conversationBubbleTimeout); }
Option Schema
[ { "name" : "background_image", "section" : "Presentation", "default_value" : "/images/sn-va-sp-widget/sn-va-sp-widget-icon.svg", "label" : "Background image", "type" : "string" }, { "name" : "button_color", "section" : "Presentation", "default_value" : "#8D8DE0", "label" : "Button color", "type" : "string" }, { "name" : "url", "section" : "Behavior", "default_value" : "$sn-va-web-client-app.do", "label" : "URL", "type" : "string" } ]
Once the widget is created, you can add the following code to the Body HTML template field of the footer associated with your portal theme:
<div> <widget id="sn-va-sp-widget"> <!--replace OOB widget id with widget id created--> </widget> </div>
Screenshots of Widget
Solved! Go to Solution.
- 2,736 Views
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
06-17-2023 11:59 PM
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
06-17-2023 11:59 PM
No solution needed.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-27-2023 03:28 AM
@johndoh Hey, I had an requirement of changing the style of iframe(Changing Background of contact centre to blue and text to white; Reducing the height of the three buttons as acquires lot of area) that appears when we click 3 dots to right corner of Virtual Agent .
When I did it via inspect I was able to do it but when called same classes from my theme it didn't work. How can I do it via theme, if atall it is possible, else what is the way to do it. Currently I'm on San Diego
Kindly help.
#virtualagent #iframe
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-09-2025 01:51 AM
Is there any update to this script based on latest Yokohama version? Looks like the same script is not working.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-15-2025 10:45 AM
I would suggest looking at proactive triggers as a replacement: Exploring Proactive Triggers