Dashboard Tab Cycle on timer

Daniel O_Connor
Kilo Guru

Hi all,

As more and more business units are receiving custom apps from us, and utilizing Dashboards we make for them, they are now appearing on TV's around the office.

Problem I'm encountering is some of these Dashboards have multiple tabs, but on an idle display, I need to get they cycling through the tabs. I cannot find any native or OOB setting or config that lets me schedule a dashboard to cycle tabs, and reviewing documentation site cannot see anything either.

I know this was possible historically through complex methods using Homepage and UI pages, but this isn't something I want to implement as UI pages is going legacy and Homepage stuff I've already phased out.

Anyone know a way to have Dashboard tabs cycle automatically or on a timer? 

9 REPLIES 9

Ah I'd never thought of this, insert the Tab URL into the rotation setting in the chrome browser addon. 

I'll definitely try this out 

Not hard, but not obvious.  Good luck.

gav1n
Giga Contributor

I know this is an old question - but I've been recently looking for a solution. One that ideally uses the in-built refresh capabilities for widgets as opposed to refreshing the entire page using a browser plugin. 

I found some old code to refresh widgets (that had stopped working due to changes in the menu structure) and have updated this so it works, I've also added code to automatically change the tab.

So this code allows you to either refresh widgets on a single tab, or cycle through all the tabs within your dashboard.

find_real_file.png

  1. To use, simply go to add a new widget to your dashboard (click the plus icon).
  2. Choose the category of "Content Blocks"
  3. Then choose "*New Dynamic Content", then Add. (This adds an empty content block to your dashboard)
  4. Now choose "click here" on the content block.
  5. Give it any name ie "*Auto refresh" (note: this will now be available to use in any dashboards)
  6. Then replace the example code with the below. And click Submit.

Ensure this widget is visible as soon as the dashboard loads. If it is off-screen, it will not run.

Also, you do not need to add this widget to each tab. Just place it on the first tab. 

Note: I've seen that occasionally when it refreshes a lot of widgets, some appear with "ERROR" - however these still refresh and the error message vanishes. I'm not sure why.

You can reconfigure reload times, and turn off the auto-tab changing within the widget itself.

Obviously, this may break if ServiceNow makes a lot of changes to their dashboards.

Have fun! 

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<style>.counter{display:block;margin:2px 0px;color:#969696}.sel_active{float:right;margin:5px}.refresh-select{width:auto;display:inline}</style>
	<script>
		var r_sec = 0;
		var t_now = null;
		var t_next = null;
		var t_last = null;
		var myInterval = null;
		
		//auto read seconds on selector change
		function readTime() {		
			r_sec = gel('sel_time').value;			
			localStorage['saved_time'] = r_sec;
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		// refresh using actual page script
		function refreshPage() {
		var scope = angular.element(document.getElementsByClassName("sn-canvas-main ng-scope")).scope();
		scope.refreshAllPanes();	
		}
		
		function nextTab() {	
		var scope = angular.element(document.getElementsByClassName("sn-canvas-main ng-scope")).scope();
		// total tabe scope.tabs.length;
		var tabs=scope.tabs;
		var nexttab=0;
		// Active tab
		for (var i=0; i &lt; tabs.length; i++) {
		if (tabs[i].isActive) {break;}							 
		 }
		nexttab=i+1;		
		if (i>=tabs.length-1) nexttab=0;
		var tab=tabs[nexttab];
		scope.setActiveTab(tab);
			if (nexttab!=0) { // re-run on other tabs as widget is not present.
				clearInterval(myInterval);
				reloadDashboard(); 
			} else {
				clearInterval(myInterval); // do not reload dashboard as new widget will fire				
			}
		}
		
		//auto read active (true/false) on selector change
		function readActive() {
			r_active = gel('sel_active').value;
			localStorage['saved_active'] = r_active;
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		function readTabChange() {
			r_tabs = gel('tab_active').value;
			localStorage['saved_tabs'] = r_tabs;
		}
		
		//refresh now on button click
		function refreshNow() {
			refreshPage();
			clearInterval(myInterval); //always clear interval after refresh			
		}
		
		//run interval every 1 second
		function runInterval(){		
			if(t_now &lt; t_next) {
				// the definition of "('0' + t_X).slice(-2)" is there to print the leading zeros in front
				$j('#refresh .counter').text('Now: '+t_now.getHours()+':'+ ('0'+t_now.getMinutes()).slice(-2) +':'+ ('0'+t_now.getSeconds()).slice(-2) +' Next: '+t_next.getHours()+':'+('0'+ t_next.getMinutes()).slice(-2) +':'+ ('0'+t_next.getSeconds()).slice(-2) +' Last: '+t_last.getHours()+':'+ ('0'+t_last.getMinutes()).slice(-2) +':'+ ('0'+t_last.getSeconds()).slice(-2));
		
				//console.log('Now: '+t_now.getSeconds()+', Next: '+t_next.getSeconds());
		
				t_now.setSeconds(t_now.getSeconds() + 1);
			} else {
				//we refresh all widgets
				if(r_tabs == 'true') {
					nextTab();					
				} else {
					refreshPage();					
					clearInterval(myInterval); //always clear interval after refresh
				}				
				
				//console.log('we refresh dashboard...');
			}
		}
		
		function reloadDashboard() {			

			//read time in seconds		
		    r_sec = localStorage['saved_time'] || gel('sel_time').value; //read saved option from cache after refresh or from the selector
			$j('#sel_time').val(r_sec); //we set it to the selector
		
			//read tab change
			r_tabs = localStorage['saved_tabs'] || gel('tab_active').value;;
			$j('#tab_active').val(r_tabs); //we set it to the selector
		
			//read active, true or false
			r_active = localStorage['saved_active'] || gel('sel_active').value; //read saved option from cache after refresh or from the selector
			$j('#sel_active').val(r_active); //we set it to the selector

			if(r_active == 'true') {				
				t_now = new Date(Date.now());
				t_next = new Date(t_now.getTime());
				t_next.setSeconds(t_next.getSeconds() + parseInt(r_sec)); //we sum read seconds to calculate next refresh
				t_last = new Date(t_now.getTime()); //save last run, as t_now is displayed on real time. eg. t_now=t_now+1
				if (sessionStorage['running']) clearInterval(sessionStorage['running']);
				myInterval = setInterval(runInterval, 1000); //run every 1 sec.				
				sessionStorage['running']=myInterval; // prevents multiple intervals running
			} else {
				$j('#refresh .counter').text('Stopped. Set it to active to start.');
			}
		}

		$j('#sel_time').val('300'); //default option is 60 seconds
		reloadDashboard();					

		//if window tab is active (for optimal performance)
		$j(window).focus(function() {
		if(r_tabs == 'false') {
			clearInterval(myInterval);
			reloadDashboard();
		}
		});					
		
		if(r_tabs == 'true') {
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		//if window tab is inactive (for optimal performance)
		$j(window).blur(function() {			
			if(r_tabs == 'false') {
			clearInterval(myInterval);
			$j('#refresh .counter').text('Stopped. Tab was inactive.');
		}
		});
	</script>
	<div id='refresh' class='panel-body'>
			<span class='counter'></span>

		Refresh:
		<select id='sel_time' class='form-control refresh-select' onchange='readTime()'>
			<option value='15'>15 sec.</option>
			<option value='300' selected="selected">5 min.</option>
			<option value='900'>15 min.</option>
			<option value='1800'>30 min.</option>
			<option value='2700'>45 min.</option>
		</select>
		<button type='button' class='btn btn-default' onclick='refreshNow()'>Refresh now</button>
		<span class='sel_active'>
			Active:
			<select id='sel_active' class='form-control refresh-select' onchange='readActive()'>
				<option value='true'>true</option>
				<option value='false'>false</option>
			</select>
			Cycle Tabs:
			<select id='tab_active' class='form-control refresh-select' onchange='readTabChange()'>
				<option value='false'>false</option>
				<option value='true'>true</option>				
			</select>
		</span>
	</div>
</j:jelly>

gav1n
Giga Contributor

Yes - we have one we made (adjusted some older posts as it wasn't working). It works well.

Go into your dashboard (edit mode)

  • Add Widgets (the + on the top)
  • Choose the widget category "Content Blocks"
  • Click on "*New Dynamic Content"
  • Click "Add" (should add a block at the top with a click_here link)
  • Click "Click here" to configure the content block
  • Name call it "*Auto refresh"
  • Paste the below code into the Dynamic content field.

Done! You can configure the speed of refresh via the widget.

 

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<style>.counter{display:block;margin:2px 0px;color:#969696}.sel_active{float:right;margin:5px}.refresh-select{width:auto;display:inline}</style>
	<script>
		var r_sec = 0;
		var t_now = null;
		var t_next = null;
		var t_last = null;
		var myInterval = null;
		
		//auto read seconds on selector change
		function readTime() {
			console.log('function readTime');
			r_sec = gel('sel_time').value;			
			localStorage['saved_time'] = r_sec;
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		// refresh using actual page script
		function refreshPage() {
		var scope = angular.element(document.getElementsByClassName("sn-canvas-main ng-scope")).scope();
		scope.refreshAllPanes();	
		}
		
		function nextTab() {	
		var scope = angular.element(document.getElementsByClassName("sn-canvas-main ng-scope")).scope();
		// total tabe scope.tabs.length;
		var tabs=scope.tabs;
		var nexttab=0;
		// Active tab
		for (var i=0; i &lt; tabs.length; i++) {
		if (tabs[i].isActive) {break;}							 
		 }
		nexttab=i+1;		
		if (i>=tabs.length-1) nexttab=0;
		var tab=tabs[nexttab];
		
		console.log('Next tab: ' + nexttab);
		
		scope.setActiveTab(tab);
			if (nexttab!=0) { // re-run on other tabs as widget is not present.
		
				console.log('Clear interval');
				if (!myInterval) console.log('No myInterval');
		
				clearInterval(myInterval);
				reloadDashboard(60); // only show other pages for 60 seconds
			} else {
				clearInterval(myInterval); // do not reload dashboard as new widget will fire				
			}
		}
		
		//auto read active (true/false) on selector change
		function readActive() {
			console.log('function readActive');
			r_active = gel('sel_active').value;
			localStorage['saved_active'] = r_active;
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		function readTabChange() {
			console.log('function readTabChange');
			r_tabs = gel('tab_active').value;
			localStorage['saved_tabs'] = r_tabs;
		}
		
		//refresh now on button click
		function refreshNow() {
			refreshPage();
			clearInterval(myInterval); //always clear interval after refresh			
		}
		
		//run interval every 1 second
		function runInterval(){
		
		console.log('runInterval');
	
			if(t_now &lt; t_next) {
		
				console.log('Now: '+t_now.getSeconds()+', Next: '+t_next.getSeconds());
		
				// the definition of "('0' + t_X).slice(-2)" is there to print the leading zeros in front
				$j('#refresh .counter').text('Now: '+t_now.getHours()+':'+ ('0'+t_now.getMinutes()).slice(-2) +':'+ ('0'+t_now.getSeconds()).slice(-2) +' Next: '+t_next.getHours()+':'+('0'+ t_next.getMinutes()).slice(-2) +':'+ ('0'+t_next.getSeconds()).slice(-2) +' Last: '+t_last.getHours()+':'+ ('0'+t_last.getMinutes()).slice(-2) +':'+ ('0'+t_last.getSeconds()).slice(-2));
					
				t_now.setSeconds(t_now.getSeconds() + 1);
			} else {
				console.log('refresh all widgets');
		
				//we refresh all widgets
				if(r_tabs == 'true') {
					console.log('next tab');
					nextTab();					
				} else {
					console.log('refresh page');
					refreshPage();					
					clearInterval(myInterval); //always clear interval after refresh
				}				
				
				console.log('we refresh dashboard...');
			}
		}
		
		function reloadDashboard(seconds = 0) {			
			console.log('function reloadDashboard');
			//read time in seconds		
		
			console.log('function reloadDashboard - sel_time');
		
			if (gel('sel_time')) {
				r_sec = localStorage['saved_time'] || gel('sel_time').value; //read saved option from cache after refresh or from the selector
			} else
				r_sec = localStorage['saved_time']
		
			$j('#sel_time').val(r_sec); //we set it to the selector
		
		console.log('function reloadDashboard - tab_active');
			//read tab change
			if (gel('tab_active')) {
				r_tabs = localStorage['saved_tabs'] || gel('tab_active').value;;
			} else
				r_tabs = localStorage['saved_tabs']
		
			$j('#tab_active').val(r_tabs); //we set it to the selector
		
		console.log('function reloadDashboard - sel_active');
		
			//read active, true or false		
			try {
				if (gel('sel_active')) {
				r_active = localStorage['saved_active'] || gel('sel_active').value; //read saved option from cache after refresh or from the selector
			} else
				r_active = localStorage['saved_active']
		
				$j('#sel_active').val(r_active); //we set it to the selector
			} catch (err) {
				$j('#sel_active').val('true');
			}
		
		if (!r_active) r_active='true';
		
		console.log('function reloadDashboard r_active is ' + r_active);
		
			if(r_active == 'true') {
		
				console.log('function reloadDashboard r_active TRUE ');
				console.log('function reloadDashboard seconds ' + seconds);
				console.log('function reloadDashboard r_sec ' + r_sec);
		
				t_now = new Date(Date.now());
				t_next = new Date(t_now.getTime());
				if (seconds>0) {
				t_next.setSeconds(t_next.getSeconds() + parseInt(seconds)); //we sum read seconds to calculate next refresh
				} else
				t_next.setSeconds(t_next.getSeconds() + parseInt(r_sec)); //we sum read seconds to calculate next refresh

				t_last = new Date(t_now.getTime()); //save last run, as t_now is displayed on real time. eg. t_now=t_now+1
				if (sessionStorage['running']) clearInterval(sessionStorage['running']);
				myInterval = setInterval(runInterval, 1000); //run every 1 sec.				
				sessionStorage['running']=myInterval; // prevents multiple intervals running
			} else {
				$j('#refresh .counter').text('Stopped. Set it to active to start.');
			}
		}

		$j('#sel_time').val('300'); //default option is 60 seconds
		reloadDashboard();					

		//if window tab is active (for optimal performance)
		$j(window).focus(function() {
		if(r_tabs == 'false') {
			clearInterval(myInterval);
			reloadDashboard();
		}
		});					
		
		if(r_tabs == 'true') {
			clearInterval(myInterval);
			reloadDashboard();
		}
		
		//if window tab is inactive (for optimal performance)
		$j(window).blur(function() {			
			if(r_tabs == 'false') {
			//clearInterval(myInterval);
			//$j('#refresh .counter').text('Stopped. Tab was inactive.');
		}
		}
		
		);
	</script>
	<div id='refresh' class='panel-body'>
			<span class='counter'></span>

		Refresh:
		<select id='sel_time' class='form-control refresh-select' onchange='readTime()'>
			<option value='120'>2 min.</option>
			<option value='300' selected="selected">5 min.</option>
			<option value='900'>15 min.</option>
			<option value='1800'>30 min.</option>
			<option value='2700'>45 min.</option>
		</select>
		<button type='button' class='btn btn-default' onclick='refreshNow()'>Refresh now</button>
		<span class='sel_active'>
			Active:
			<select id='sel_active' class='form-control refresh-select' onchange='readActive()'>
				<option value='true'>true</option>
				<option value='false'>false</option>
			</select>
			Cycle Tabs:
			<select id='tab_active' class='form-control refresh-select' onchange='readTabChange()'>
				<option value='false'>false</option>
				<option value='true'>true</option>				
			</select>
		</span>
	</div>
</j:jelly>