Reduce the impact of third-party code (zendesk) - javascript

<script
id="ze-snippet"
src="https://static.zdassets.com/ekr/snippet.js?key=some_zendesk_key"
/>
I'm trying to optimize my web site performance. I've faced a big impact of third-party code to my performance, I think all my bundle has a lower size than zendesk code. How can I load it without impacting on the main thread? Should I use the async or defer tags? Or which approach is better for this case?

This seems to be an issue that tortures so many people without a clear solution.
What I managed to do it to reduce the block time by adding this configuration.
window.zESettings = {
webWidget: {
chat: {
connectOnPageLoad: false
}
}
};
ref https://developer.zendesk.com/embeddables/docs/widget/settings#connectonpageload
ps
I did a performance test to my zendesk helpdesk "domain.zendesk.com" and the results there were even worse

I came across this issue recently and made this hack using a function for loading the zendesk script only when you reached certain point of the doc. I know is kind of dirty but it works:
<script defer type="text/javascript">
(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el){
function visPx(){
var H = $(this).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? H-t : (b<H?b:H)));
} visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$('#trigger_div').inViewport(function(px){
if(px) {
//zopim code
}
});

Starting from this article https://www.newmediacampaigns.com/blog/maintaining-great-site-performanc-using-zendesk-web-widget I have implemented a solution that significantly reduces the load time by at least 3 seconds (in Google Lighthouse).
I have created a fake button in the HTML that will load the Zendesk script and open the widget when clicked. It will also load a localStorage item that will prevent this from happening on subsequent page loads.
⚠️ Warning:
The code relies heavily on how the widget is currently implemented (for example it expects a #launcher and a #webWidget element to appear on the page), so it can break as soon as the original code changes, but at least we will have an improvement in the loading times until they fix it.
Here is the most important part of the code:
HTML Button
<button class="zendesk-button">
<span class="left-icon">
<!-- here you insert the icon -->
</span>
<span class="text">Chat</span>
</button>
JavaScript code
// select the button
const zendeskButton = document.querySelector('.zendesk-button');
// add the script to the page
const loadZendeskScript = () => {
const zenDeskScript = document.createElement("script");
zenDeskScript.id = "ze-snippet";
zenDeskScript.src = "https://static.zdassets.com/ekr/snippet.js?key=HERE_YOU_INSERT_YOUR_KEY";
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] || document.getElementsByTagName('script')[0].parentNode).insertBefore(zenDeskScript, null);
};
// a poller that waits for a condition and executes a callback
const poller = (comparison, callback, timerStep = 250, maxTime = 5000) => {
// why setTimeout instead of setInterval
// https://stackoverflow.com/questions/8682622/using-setinterval-to-do-simplistic-continuous-polling
let currTime = 0;
const checkCondition = () => {
// `comparison` is a function so the condition is always up-to-date
if (comparison() === true) {
callback();
} else if (currTime <= maxTime) {
currTime += timerStep;
setTimeout(checkCondition, timerStep);
}
};
checkCondition(); // calling the function
};
// load the script and execute a callback if provided
const loadZendeskChat = callback => {
loadZendeskScript();
if (callback) {
callback();
}
};
// this function opens the chat
const openZendeskChat = () => {
poller(
() => {
// check that zendesk-related functions are available
return typeof zE !== 'undefined';
},
() => {
// open the widget
zE('webWidget', 'open');
poller(
() => {
// check that the elements exist and that the opacity is already set to "1"
const launcher = document.querySelector('#launcher');
const webWidget = document.querySelector('#webWidget');
return launcher !== null && webWidget !== null && webWidget.style.opacity === '1';
},
() => {
// hide the fake button
zendeskButton.style.opacity = '0';
// save in localStorage
localStorage.setItem('zd_hasOpened', 'true');
}
);
}
);
};
// zendesk management
if (localStorage.getItem('zd_hasOpened')) {
// load the zendesk widget if we find that it was opened
loadZendeskChat();
} else {
// show the fake button if it's the first time it shows
zendeskButton.style.opacity = '1';
}
// This will run when the .zendesk-button element is clicked
zendeskButton.addEventListener('click', () => {
// add a 'Loading' text to the button, as the widget will take some time to load (between 1 and 2 seconds on my laptop)
zendeskButton.querySelector('.text').innerHTML = 'Loading';
// load the zendesk widget
// open the widget and hide the fake button
loadZendeskChat(openZendeskChat);
});
Regarding styles, I have pretty much copied the style in the original widget, converting ems to pixels, but one part I'd like to highlight is the focus style, because in my opinion it helps telling the user that something is happening.
.zendesk-button:focus {
outline: none;
box-shadow: inset 0 0 0 0.21429rem rgb(255 255 255 / 40%) !important;
}

Related

Click event on a button with SetInterval Method

I'm not able to figure out how this can be accomplished. I'm working on the chrome extension and I have a setInterval method which monitors a button on a page and when its clicked, it runs a function. but the problem is, when I click the button, the function runs multiple times and I clearly understand this is because of the Interval function, but I want the setInterval to run always on this page so that I can monitor if the button is clicked or not. Below is my code
$(function(){
var url=window.location.href;
findAction(url);
}
function findAction(url){
setInterval(()=>{
Acceptbtn = $("[slot=primary-content-area")[4].querySelector("sn-inbox");
if(Acceptbtn !== undefined && Acceptbtn !== null){
Acceptbtn.addEventListener('click', myFunction);
}
function myFunction() {
console.log("clicked");
runAction(url)
};
},1000);
}
Is there any way to tackle this situation or have I made something simple, complicated or Is my approach completely wrong.?
Below is the Div which my extension monitors for the Accept button -
And once the Accept button is clicked, this is what happens
The Interval method keeps checking for this button and once found and I click on it, I want the runAction(url) function to execute. With my current code, the function gets executed but multiple times
Only add an event listener once
Also the selector is incorrect if sn-inbox is a class and [slot=primary-content-area] was missing a ]
Here I delegate - if you delegate to the closest static container (here document, because I do not know the closest container in your case), you can ALWAYS find the button when it is there:
const url = window.location.href;
$(document).on("click", "[slot=primary-content-area]", function() {
if ($(this).index() === 3) { // 0 based - so the 4th slot
const $inbox = $(this).find(".sn-inbox");
if ($inbox.length > 0) {
$inbox.html("running");
runAction(url)
}
}
})
Example code to execute the functions:
https://jsfiddle.net/mplungjan/p93cj0Le/
$(function() {
const runAction = url => console.log("running", url);
const url = window.location.href;
$(document).on("click", "[slot=primary-content-area]", function() {
if ($(this).index() === 3) { // 0 based
const $inbox = $(this).find(".sn-inbox");
if ($inbox.length > 0) {
$inbox.html("running");
runAction(url)
}
}
})
// testing the code
setInterval(() => {
$("#container").html(`<div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"></div>
<div slot="primary-content-area"><button class="sn-inbox">Click</button></div>
</div>`)
}, 5000)
setTimeout(() => $("#container").html(""), 2000)
})
<div id="container">
</div>

JavaScript - getElementById doesn't work on specific page

I want to automate clicking the agree button to Google's cookie policies.
(I clean cookies after closing a tab, and I don't want to create a google account, so I get asked every time I use google)
There is a div element with the ID "introAgreeButton" that I'm trying to access with my script:
<div role="button" id="introAgreeButton" [...]></div>
However, document.getElementById('introAgreeButton') always returns null.
My first thought was that the element wasn't loaded by the time my function was executed. But it doesn't work if I execute it on window.onload, or even if I run it in a loop until the element is definitely there:
window.onload = function() {
var x = document.getElementById('introAgreeButton')
console.log(x)
}
Output:
null
function loop() {
var x = document.getElementById('introAgreeButton')
if (x) {
console.log('success')
} else {
loop()
}
}
Output:
null
null
null
...
Can be tested on https://www.google.com/search?hl=en&q=test
Anyone have an idea why this is and how to solve it?
Edit: I execute the script via the browser extension TamperMonkey
You can use setInterval to check if element is rendered in DOM like this :
document.addEventListener('DOMContentLoaded', function () {
var intervalID = null;
function checkElementInDOM () {
var element = document.getElementById('introAgreeButton');
if (element) {
clearInterval(intervalID);
// DO YOUR STUFF HERE ...
}
}
intervalID = setInterval(checkElementInDOM, 100);
});
To be used intelligently, however, so as not to have a setInterval which works continuously. Maybe think about adding a maximum number of attempts.

how toggle between two functions automatically without button

I want to toggle between interstitial ads and rewarded video ads in my game html use it construct 2 every time loading layout like if first runtime show interstitial and if loading again show rewarded video ads and repeat this every time .
SysActs.prototype.GoToLayout = function(to) {
showInterstitialAd();
showRewardedVideoAd();
if (this.runtime.isloading)
return; // cannot change layout while loading on loader layout
if (this.runtime.changelayout)
return; // already changing to a different layout
;
this.runtime.changelayout = to;
};
my testcode aftert toggle between two functions automatically
SysActs.prototype.GoToLayout = function (to)
{
if($(this).data('toggleAds') == 1) {
toggleAds = 0;
if (this.runtime.isloading || showRewardedVideoAd())
return;
if (this.runtime.changelayout )
return;
;
this.runtime.changelayout = to;
}
else {
toggleAds = 1;
if (this.runtime.isloading || showInterstitialAd() )
return;
if (this.runtime.changelayout )
return;
;
this.runtime.changelayout = to;
showInterstitialAd();
}
$(this).data('toggleAds', toggleAds);
return false;
};
i try this but is not work?
It doesn't work because you're not persisting anything on page reload, so you get the exact same page and same code, so the behaviour is exactly the same. You can't toggle this way. Store a state in the localStorage and read it on page load.
const previousState = localStorage.getItem("state"); // null, "interstitial" or "rewarded"
let currentState;
if(previousState){
currentState = previousState === "interstitial" ? "rewarded" : "interstitial";
} else { // First page load ever
currentState = "rewarded"; // or "interstitial", initialize it like you want
}
localStorage.setItem("previousState", currentState); // It's saved for next reload
// Now do something with currentState

SharePoint 2016: how to force JS to execute on every site page despite Ajax and/or MDS?

I'm implemeting a very simple use case, and yet not only do I not find a solution, but I can't find any article that talks about it, as if I was the only one.
I want my custom Javascript to execute on every page of a given SharePoint site.
Easy, you'll say. Well, no. Far from it, like always with SharePoint.
Steps to reproduce :
Create a out-of-the-box publishing site
Include the custom javascript below using any of the means I describe below
Go to the site, to the home page. It's a publishing site, so by default you should have the left navigation pane with at least "Home" and "Documents" by default.
The first time you load the page, the javascript executes. Now, click on "documents". The page changes but the Javascript is not executed.
That's because SharePoint uses Ajax. Even if the MDS is disabled. It uses Ajax through the hash ( # ) in the URL.
For example, it transforms a very inocuous link like this one :
< a href src="/SitePages/Home.aspx">
into this URL when you click it:
https://your-url/sites/your-site/_layouts/15/start.aspx#/SitePages/Home.aspx
Here is my Javascript :
if (ExecuteOrDelayUntilScriptLoaded && _spBodyOnLoadFunctionNames) {
_spBodyOnLoadFunctionNames.push(ExecuteOrDelayUntilScriptLoaded(
function () {
alert("It's working!");
}, "sp.js"));
}
So, I've tried the following ways of including the Javascript :
Through a User Custom Action. I've used this very handy page to add it, but that's not relevant. The action is added to the site and I can see the JS in the DOM on first load. But then after I click on a link in the page and after SP uses Ajax, it does not execute it again.
By modifying the master page -- namely: seattle.html. at first I included it this way, simply under other native inclusions :
<head runat="server">
...
<!--SPM:<SharePoint:ScriptLink language="javascript" name="suitelinks.js" OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->
But then I read about AjaxDelta (here : https://msdn.microsoft.com/fr-fr/library/office/dn456543.aspx ) , and I moved my inclusion (still in the header) into < AjaxDelta >, like this :
<head runat="server">
...
<!--SPM:<SharePoint:AjaxDelta id="DeltaPlaceHolderAdditionalPageHead" Container="false" runat="server">-->
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/>-->
<!--SPM:<SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->
<!--SPM:</SharePoint:AjaxDelta>-->
...and yet nothing works. The Javascript is never executed when switching between pages of the same site by clicking on SharePoint's "managed" links.
I'm looking for a solution that handles elegantly SharePoint's Ajax, not something heavy and risky that hijacks every hyperlink on a page. For example I've tried to hook my code onto ajaxNavigate methods (for example : addNavigate) but I'm not sure I understand what's actualy going on there and if it could be of any help to me.
EDIT :
There seems to be a consensus (for example, here at the very bottom) that User Custom Actions get executed no matter what -- because SharePoint allegedly places their ScriptLink into the AjaxDelta for some reason. Well, that's not what I witnessed.
There's another consensus that this issue can be adressed by using "RegisterModuleInit". This doesn't work for me either.
I'm extermely puzzled. I think those two solutions do address navigation issues when the user clicks on a link and then clicks "back". But it does NOT address SharePoint's clever "managed", Ajax-riddled, hyperlinks.
I've finally found a solution that never seems to fail so far. That's a real relief.
Short answer: use asyncDeltaManager.add_endRequest
This MSDN discussion suggests a simple way to implement it:
https://social.msdn.microsoft.com/Forums/office/en-US/1ae292b4-3589-46f6-bedc-7bd9dc741f1b/javascript-code-to-execute-after-all-the-elements-and-css-are-loaded?forum=appsforsharepoint
$(function () {
ExecuteOrDelayUntilScriptLoaded(function () {
if (typeof asyncDeltaManager != "undefined")
asyncDeltaManager.add_endRequest(MYCUSTOMCODE); //execute it after any ajax event
else
MYCUSTOMCODE(); //execute it at first load
}, "start.js");
});
This shows how to include it properly in SharePoint's cycle (with ExecuteOrDelayUntilScriptLoaded )
https://sharepoint.stackexchange.com/questions/171490/javacript-only-executed-on-first-page-load
Full-blown solution (objet "LefeCycleHelper"), by Mx
https://sharepoint.stackexchange.com/questions/192974/where-to-place-a-js-script-with-whom-i-need-to-get-an-div-id/193009#193009
//use an IIFE to create a scope and dont dirty the global scope
(function (_) {
// use strict to ensure we dont code stupid
'use strict';
var initHandlers = [];
var initMDSHandlers = [];
var ensureSharePoint = function (handler) {
var sodLoaded = typeof (_v_dictSod) !== 'undefined' && _v_dictSod['sp.js'] != null && _v_dictSod['sp.js'].state === Sods.loaded;
if (sodLoaded) {
handler();
} else {
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () { });
SP.SOD.executeOrDelayUntilScriptLoaded(handler, 'sp.js');
}
};
var initMDS = function () {
for (var i = 0; i < initMDSHandlers.length; i++) {
initMDSHandlers[i]();
}
};
var init = function () {
// Register MDS handler
if ('undefined' != typeof g_MinimalDownload && g_MinimalDownload && (window.location.pathname.toLowerCase()).endsWith('/_layouts/15/start.aspx') && 'undefined' != typeof asyncDeltaManager) {
asyncDeltaManager.add_endRequest(initMDS);
} else {
for (var i = 0; i < initHandlers.length; i++) {
initHandlers[i]();
}
}
};
var registerInit = function (handler) {
initHandlers.push(handler);
};
var registerInitMDS = function (handler) {
initMDSHandlers.push(handler);
};
var domReady = (function (handler) {
var fns = [];
var listener;
var loaded = (document.documentElement.doScroll ? /^loaded|^c/ : /^loaded|^i|^c/).test(document.readyState);
if (!loaded) {
document.addEventListener('DOMContentLoaded', listener = function () {
document.removeEventListener('DOMContentLoaded', listener);
loaded = 1;
while (listener = fns.shift()) listener();
});
}
return function (fn) {
loaded ? setTimeout(fn, 0) : fns.push(fn);
};
})();
var attachToLoad = function (functionToAttach) {
registerInit(functionToAttach);
registerInitMDS(functionToAttach);
domReady(function () {
init();
});
};
_.AttachToLoad = attachToLoad;
// THIS WILL PROTECT YOUR GLOBAL VAR FROM THE GARBAGE COLLECTOR
window.LifeCycleHelper = _;
if (window.Function != 'undefined' && typeof (Function.registerNamespace) == 'function') {
Function.registerNamespace('LifeCycleHelper');
}
})({});
var theCodeYouWantToRun = function () {
alert('theCodeYouWantToRun');
};
window.LifeCycleHelper.AttachToLoad(theCodeYouWantToRun);

Ubuntu HTML5 App: Change Tab on JS command

First Question here, too! Yay! Just moved this from AskUbuntu.
I am just about to finish a little private project for gaining some experience where i try to change the app layout so it works as a normal website (on Jimdo, so it was quite of a challenge first) without much JavaScript required but is fully functional on mobile view.
Since Jimdo serves naturally only the actual site, I had to implement an
if (activeTab.getAttribute('jimdo-target') != null)
location.href = activeTab.getAttribute('jimdo-target');
redirect into the __doSelectTab() function in tabs.js . (In js I took the values from the jimdo menu string to build the TABS menu with this link attribute)
Now everything works fine exept at page load the first tab is selected. I got it to set the .active and .inactive classes right easily, but it is not shifted to the left.
So my next idea is to let it initialize as always and then send a command to change to the current tab.
Do you have any idea how to manage this? I couldn't because of the this.thisandthat element I apparently don't really understand...
Most of you answering have the toolkit and the whole code, but I am listing the select function part of the tabs.js:
__doSelectTab: function(tabElement, forcedSelection) {
if ( ! tabElement)
return;
if (tabElement.getAttribute("data-role") !== 'tabitem')
return;
if (forcedSelection ||
(Array.prototype.slice.call(tabElement.classList)).indexOf('inactive') > -1) {
window.clearTimeout(t2);
activeTab = this._tabs.querySelector('[data-role="tabitem"].active');
offsetX = this.offsetLeft;
this._tabs.style['-webkit-transition-duration'] = '.3s';
this._tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
this.__updateActiveTab(tabElement, activeTab);
if (activeTab.getAttribute('jimdo-target') != null)
location.href = activeTab.getAttribute('jimdo-target');
[].forEach.call(this._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)'), function (e) {
e.classList.remove('inactive');
});
var targetPageId = tabElement.getAttribute('data-page');
this.activate(targetPageId);
this.__dispatchTabChangedEvent(targetPageId);
} else {
[].forEach.call(this._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)'), function (el) {
el.classList.toggle('inactive');
});
var self = this;
t2 = window.setTimeout(function () {
var nonActiveTabs = self._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)');
[].forEach.call(nonActiveTabs, function (el) {
el.classList.toggle('inactive');
});
}, 3000);
}
},
...and my app.js hasn't anything special:
var UI = new UbuntuUI();
document.addEventListener('deviceready', function() { console.log('device ready') }, true);
$(document).ready(function () {
recreate_jimdo_nav();
UI.init();
});
So meanwhile found a simple workaround, however I'd still like to know if there is another way. Eventually I noticed the __doSelectTab() function is the one that executes the click, so it does nothing but to show the other tab names when they are hidden first. so I added the global value
var jnavinitialized = false;
at the beginning of the tabs.js and run
var t = this;
setTimeout(function(){t.__doSelectTab(t._tabs.querySelector('[data-role="tabitem"].jnav-current'))}, 0);
setTimeout(function(){t.__doSelectTab(t._tabs.querySelector('[data-role="tabitem"].jnav-current'))}, 1);
setTimeout(function(){jnavinitialized = true;}, 10);
at the top of the __setupInitialTabVisibility() function. Then I changed the location.href command to
if (activeTab.getAttribute('jimdo-target') != null && jnavinitialized)
location.href = activeTab.getAttribute('jimdo-target');
And it works. But originally I searched for a way to change the tab on command, not to run the command for selecting twice. So if you know a better or cleaner way, you are welcome!

Categories