I have implemented this plugin https://retargeting.biz/plugins/custom in my laravel app and I have some issues not with the plugin ...
The scripts that sits on the header ... when the page is loaded it created an object _ra and the functions that needs to exectute it only when
_ra.ready !== undefined
The problem is with the functions that I need to execute only when the page is loaded and send the data - because the script only run once. For the functions that need to be trriggered on a action (button, etc...) it's not a problem.
For example this script need to be loaded when the user enter on a category page.
function send_category() {
var _ra = _ra || {};
_ra.sendCategoryInfo = {
"id": 20,
"name": "Shoes",
"url": 'url_to_the_page',
"parent": false,
"breadcrumb": []
};
if (_ra.ready !== undefined) {
_ra.sendCategory(_ra.sendCategoryInfo);
}
}
What I did is:
if (document.readyState !== 'loading') {
send_category();
} else {
document.addEventListener('DOMContentLoaded', (event) => {
send_category();
})
}
but still... sometimes
_ra.ready = undefined
because the function runs and the object _ra in not yet created and the function is not executed (_ra.sendCategory)
What would you do it, in this case ?
I was thinking to use this:
window.addEventListener('load', (event) => {
send_category();
});
but what I know load event will do it when all the images and sub-frames have finished loading.
Does this event "load" ... wait until the _ra object is created ???
Using
window.addEventListener('load', (event) => {
send_category();
});
seems to work.
Related
(simplification) I have two javascript files I want to include. They inter-link each other.
Problem: If I just include them the following there is an error because source1.js needs something from source2.js and vice-versa.
How can I include inter-linking source files properly in HTML, without merging them? (Imaging various already-large files)
<head>
<script src="source1.js"></script>
<script src="source2.js"></script>
source1.js
function filtersomething() {
...
othersource.somefunction();
}
source2.js
var columns = {
text: filtersomething()
}
Added more to a working snippet. I'd use that code over some of the examples here. it deals more with arguments, promises, etc. Have to run, hope this helps.
You can place an event listener within each JS file, which would not call any functions until the dom is loaded. Doing this allows both JS files to load in and see the others functions available.
// script.js
window.addEventListener('DOMContentLoaded', (event) => {
filtersomething();
});
function filtersomething() {
...
othersource.somefunction();
}
Because these are loaded after script.js, script2.js always sees what script.JS has available. So, script.JS does not see what script2.JS has until after it is loaded
// script2.js
window.addEventListener('DOMContentLoaded', (event) => {
var columns = {
text: filtersomething()
}
});
We can also watch for a pointer, as suggested. This is useful when waiting for jQuery to load as well. So within your script files, watch for a property to be set, then execute.
<head>
<script>
function deferRun(thisMethod, scriptNum) {
if (window[scriptNum])
return thisMethod();
// No property found, set timeout of 50ms and try again
setTimeout(function() { defer(thisMethod, scriptNum) }, 50);
}
</script>
<script src="source1.js"></script>
<script src="source2.js"></script>
</head>
// script2.JS
// wait until script.js is available, then return result
var columns = {
text: deferRun(filtersomething, 'script1')
}
// Set our window property saying script loaded
window.script2 = true;
//script.js
function filtersomething() {
...
deferRun(othersource.somefunction, 'script2');
}
// Set our window property saying script loaded
window.script1 = true;
// Think of script1/script2 as <script> tags.
window.script1 = {
// script.js
filtersomething: () => {
return deferRun('somefunction', 'script2', 'message to use');
}
};
// Now "load" script2.js
window.script2 = {
somefunction: (msg) => {
return `msg response is ${msg}`
},
columns: {
// wait until script.js is available, then return result
text: deferRun('filtersomething', 'script1')
},
render: async() => {
console.log(await window.script2.columns.text);
}
};
(async() => {
await window.script2.render();
})();
<head>
<script>
// this is available to all and before either script 1 or 2 loads
function deferRun(thisMethod, property, argument = null) {
if (window[property])
return window[property][thisMethod](argument);
// No property found, set timeout of 50ms and try again
return setTimeout(function() {
deferRun(thisMethod, property, argument)
}, 50);
}
</script>
</head>
You can place an event listener within each JS file, which would not call any functions until the dom is loaded. Doing this allows both JS files to load in and see the others functions available.
<pre>
// script.js
window.addEventListener('DOMContentLoaded', (event) => {
filtersomething();
});
function filtersomething() {
...
othersource.somefunction();
}
```
Because these are loaded after script.js, script2.js always sees what script.JS has available. So, script.JS does not see what script2.JS has until after it is loaded
```js
// script2.js
window.addEventListener('DOMContentLoaded', (event) => {
var columns = {
text: filtersomething()
}
});
```
We can also watch for a pointer, as suggested. This is useful when waiting for jQuery to load as well. So within your script files, watch for a property to be set, then execute.
</pre>
I'm trying to add two scripts to a specific page in a gatsby.js app.
The page is /apply, and the second script depends on the first (the first script must be loaded before the second).
Of course this is straightforward on a traditional site as the scripts would be loaded synchronously in order. But in react-helmet, the scripts are loaded asynchronously so my second script errors (its trying to call a function in the first before the first is loaded).
I've taken a hook from https://usehooks.com/useScript/ and am trying to get things working.
If I inspect the page source after load, both scripts are present but I still get errors in the console (as if script2 is trying to run before script1).
myPage.js
const Apply = () => {
const scriptLoaded = useScript("https://myscripturl/script1.js");
if(scriptLoaded !== "ready"){
return <>Not loaded</>
}
return (
<>
<Helmet>
{scriptLoaded === "ready" &&
<script src="https://myscripturl/script2.js"></script>
}
</Helmet>
<!-- The rest of the page -->
</>
)
}
useScript.js
// taken from https://usehooks.com/useScript/
function useScript(src) {
// Keep track of script status ("idle", "loading", "ready", "error")
const [status, setStatus] = useState(src ? "loading" : "idle");
useEffect(
() => {
// Allow falsy src value if waiting on other data needed for
// constructing the script URL passed to this hook.
if (!src) {
setStatus("idle");
return;
}
// Fetch existing script element by src
// It may have been added by another intance of this hook
let script = document.querySelector(`script[src="${src}"]`);
if (!script) {
// Create script
script = document.createElement("script");
script.src = src;
script.async = true;
script.setAttribute("data-status", "loading");
// Add script to document body
document.body.appendChild(script);
// Store status in attribute on script
// This can be read by other instances of this hook
const setAttributeFromEvent = (event) => {
script.setAttribute(
"data-status",
event.type === "load" ? "ready" : "error"
);
};
script.addEventListener("load", setAttributeFromEvent);
script.addEventListener("error", setAttributeFromEvent);
} else {
// Grab existing script status from attribute and set to state.
setStatus(script.getAttribute("data-status"));
}
// Script event handler to update status in state
// Note: Even if the script already exists we still need to add
// event handlers to update the state for *this* hook instance.
const setStateFromEvent = (event) => {
setStatus(event.type === "load" ? "ready" : "error");
};
// Add event listeners
script.addEventListener("load", setStateFromEvent);
script.addEventListener("error", setStateFromEvent);
// Remove event listeners on cleanup
return () => {
if (script) {
script.removeEventListener("load", setStateFromEvent);
script.removeEventListener("error", setStateFromEvent);
}
};
},
[src] // Only re-run effect if script src changes
);
If I place both scripts in the index.html in /public, the code works without issue. But of course it runs on every route in the app which is no good. Is what I'm trying to do even possible?
Thanks for any help.
You can use Gatsby's Script API: https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-script/
It also has a section about loading script dependently: https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-script/#loading-scripts-dependently
So your code could be:
import React, { useState } from "react"
import { Script } from "gatsby"
function Apply() {
const [loaded, setLoaded] = useState(false)
return (
<>
<Script src="https://myscripturl/script1.js" onLoad={() => setLoaded(true)} />
{loaded && <Script src="https://myscripturl/script2.js" />}
</>
)
}
export default Apply
I created a chrome extension for one of my pages. I'm getting the data in a dom element on the page and querying the api and calling it back. On the site where I get data, the page does not reload when switching between pages. So I can't catch Dom loading or locationchange in foreground.js because the page is not refreshed and i solve my problem by timeout.The dom in foreground.js is only triggered when the page is refreshed. The codes I wrote in settimeout are triggered when switching between pages. How can i solve this problem without timeout?
background.js CODES
chrome.tabs.onUpdated.addListener((tabId, changeInfo,tab) => {
if(changeInfo.status == "complete" && tab.status == "complete") {
chrome.scripting.executeScript({
target: {tabId: tabId},
files: ["./foreground.js"]
})
}
});
foreground.js CODES
document.addEventListener("DOMContentLoaded", () => { alert('Dom'); }); //not working
window.addEventListener('locationchange', function(){
console.log('location changed!');
}) //not working
setTimeout(() => {
let find = document.querySelector(".contentsdetailscontainer").textContent;
},1000); //this is working
foreground.js executed when document.readyState is complete, which means DOMContentLoaded has already been fired.
If at that point the needed element is not present in the DOM, you can use the Mutation Observer and wait for it.
Here is a simple example (very inefficient because it observes entire body, if only a specific element changes, you should observe it instead, so use it only as a guide):
var nodeFound = false;
const observer = new MutationObserver(() =>
{
const node = document.querySelector(".contentsdetailscontainer");
if (node && node.textContent)
{
if (!nodeFound)
{
//only show alert once per new page.
alert("node found");
nodeFound = true;
}
}
else
{
nodeFound = false; //reset in case page updated again
}
});
observer.observe(document.body, {subtree: true, childList: true});
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);
I'm writing a firefox plugin and keeping track of each page's workers in an array. Apart from a bit of fancy footwork required to manage this array (as described here https://bugzilla.mozilla.org/show_bug.cgi?id=686035 and here Addon SDK - context-menu and page-mod workers) everything is working properly. One issue I'm having is that when listening to the tabs pageshow event (or the worker's own pageshow event for that matter), the callback seems to fire before the worker is actually ready. When retrieving the page's corresponding worker in the callback and using it to try to send a message to the content script, I'm receiving the error The page is currently hidden and can no longer be used until it is visible again. Normally, I'd just use a setTimeout and grit my teeth, but this isn't available for add-ons. What's a suitable workaround? The code for the main part of the add-on is below:
var { ToggleButton } = require('sdk/ui/button/toggle');
var panels = require('sdk/panel');
var tabs = require('sdk/tabs');
var self = require('sdk/self');
var pageMods = require('sdk/page-mod');
var ss = require('sdk/simple-storage');
var workers = [];
ss.storage.isPluginActive = ss.storage.isPluginActive || false;
var button = ToggleButton({
id: 'tomorrowww',
label: 'Tomorowww',
icon: {
'16': './icon-16.png',
'32': './icon-32.png',
'64': './icon-64.png'
},
onChange: handleButtonChange
});
var panel = panels.Panel({
contentURL: self.data.url('panel.html'),
contentScriptFile: self.data.url('panel-script.js'),
onHide: handlePanelHide,
width: 342,
height: 270
});
panel.port.on('panel-ready', handlePanelReady);
panel.port.on('plugin-toggled', handlePluginToggled);
panel.port.on('link-clicked', handleLinkClicked);
pageMods.PageMod({
include: ['*'],
contentScriptFile: [self.data.url('CancerDOMManager.js'), self.data.url('content-script.js')],
contentStyleFile: self.data.url('content-style.css'),
onAttach: function (worker) {
addWorker(worker);
sendActiveState(ss.storage.isPluginActive);
}
});
// move between tabs
tabs.on('activate', function () {
sendActiveState();
});
// this actually fires before the worker's pageshow event so isn't useful as the workers array will be out of sync
//tabs.on('pageshow', function () {
// sendActiveState();
//});
function addWorker (worker) {
if(workers.indexOf(worker) > -1) {
return;
}
worker.on('detach', handleWorkerDetach);
worker.on('pageshow', handleWorkerShown);
worker.on('pagehide', handleWorkerHidden);
workers.push(worker);
}
function handleWorkerDetach () {
removeWorker(this, true);
}
function handleWorkerShown () {
addWorker(this);
// back / forward page history
// trying to send the state here will trigger the page hidden error
sendActiveState();
}
function handleWorkerHidden () {
removeWorker(this);
}
function removeWorker (worker, removeEvents) {
var index = workers.indexOf(worker);
removeEvents = removeEvents || false;
if(index > -1) {
if(removeEvents) {
worker.removeListener('detach', handleWorkerDetach);
worker.removeListener('pageshow', handleWorkerShown);
worker.removeListener('pagehide', handleWorkerHidden);
}
workers.splice(index, 1);
}
}
function getWorkersForCurrentTab () {
var i;
var tabWorkers = [];
i = workers.length;
while(--i > -1) {
if(workers[i].tab.id === tabs.activeTab.id) {
tabWorkers.push(workers[i]);
}
}
return tabWorkers;
}
function handlePanelReady () {
setActive(ss.storage.isPluginActive);
}
function setActive (bool) {
ss.storage.isPluginActive = bool;
panel.port.emit('active-changed', bool);
sendActiveState();
}
function sendActiveState () {
var tabWorkers = getWorkersForCurrentTab();
var i = tabWorkers.length;
while(--i > -1) {
tabWorkers[i].port.emit('toggle-plugin', ss.storage.isPluginActive);
}
}
function handleButtonChange (state) {
if(state.checked) {
panel.show({
position: button
});
}
}
function handlePanelHide () {
button.state('window', {checked: false});
}
function handleLinkClicked (url) {
if(panel.isShowing) {
panel.hide();
}
tabs.open(url);
}
function handlePluginToggled (bool) {
if(panel.isShowing) {
panel.hide();
}
setActive(bool);
}
try using contentScriptWhen: "start" in the page-mod
I was dealing with a similar problem. I think I have it working the way I want now by putting the listener in the content script instead of the addon script. I listen for the event on the window, I then emit a message from my content script to my addon script, my addon script then sends a message back to the content script with the information needed from the addon script.
In my code, I am working on update the preferences in the content script to ensure that the tab always has the most up to date settings when they are changed, only the addon script can listen to the prefs change event.
This particular snippet will listen for when the page is navigated to from history (i.e., back or forward button), will inform the addon script, the addon script will get the most up to date preferences, and then send them back to a port listening in the content script.
Content script:
window.onpageshow = function(){
console.log("onpageshow event fired (content script)");
self.port.emit("triggerPrefChange", '');
};
Addon Script (e.g., main.js:
worker.port.on("triggerPrefChange", function() {
console.log("Received request to triggerPrefChange in the addon script");
worker.port.emit("setPrefs", prefSet.prefs);
});
Since the event is being fired from the DOM event, the page must be shown. I am not sure if listening to the pageshow event in the addon script is doing what we think.