Repeatedly getting error in Word office add-in "RichApi.Error: Wait until the previous call completes" - ms-word

After applying Content control on long document (around 10 pages) using office Js, when I am trying to select the content control form a document using below code snippet, I am getting an error
OfficeService.js:338 RichApi.Error: Wait until the previous call completes.
at new n (word-win32-16.01.js:25:246227)
at o.processRequestExecutorResponseMessage (word-win32-16.01.js:25:310053)
at word-win32-16.01.js:25:308456
Sometimes I am able to fetch the selected content control but it's very slow and many times I get the above error. Not sure why this issue is occurring, I have checked in Office Js documentation but couldn't find the resolution.
export const findSelectedContentControl = async () => {
return await Word.run(async (context) => {
try {
const selectedContentControl = context.document.getSelection().contentControls;
selectedContentControl.load("items");
console.log("selectedContentControl..",selectedContentControl)
return await context.sync().then(() => {
let tagArray;
if(selectedContentControl.items.length === 0)
return;
tagArray = selectedContentControl.items[0].tag.split("|");
return tagArray;
});
} catch (error) {
console.log(error);
}
});
};

Related

Office-js Word addin, Grabbing document Text

In my code below, I am attempting to grab the current documents text, set it to a variable and then use that variable in a call. My promise may not be formatted correctly but essentially the getScanResult() function uses the docBodyText variable that I set in handleClickRun(). Everytime I call it the variable is empty. Any idea as to why the document text is not being captured correctly?
const [docBodyText, setDocBodyText] = useState('');
const handleClickRun = async () => {
return Word.run(async (context: Word.RequestContext) => {
const docBody = context.document.body;
docBody.load("text");
await context.sync();
setDocBodyText(docBody.text);
await context.sync();
})
.catch(function (error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
};
const handleScanResults = () => {
new Promise(async (resolve) => {
await handleClickRun();
await getScanResult();
resolve('Completed')
})};
I have tried using the docs and looking for other examples but have not seen any other use cases. The docs I am using is this Perhaps I can be pointed to the correct method.
I have also tried making a variable of just plain text and passing it to my api call and it works perfectly fine, so it is not a call issue.

PWA - Cache won't update for offline use

I have a PWA which works fine both online and offline (but only with the initial files). However, the offline cache (let’s say a javascript file) is not being refreshed so whenever I am offline the old javascript file is used, but when online the new version is used.
On an iPad I can use Safari to go to the website and add the PWA to the home page.
If I then go offline, it works fine – all pages work etc.
But if I make a change to say a javascript file (something like adding an alert) and also change the version in my service worker, when I am online the change is reflected but when offline it remains at the older version
To clarify let’s say from the start, on going into a page it alerts “A1”
I then change the javascript to alert “A2” and change the version in the service worker.
If I run the app when online, sure enough the app says New Update Available and All Good (some alerts from the main.js file)
Then when I go into the actual page o the alert says “A2” – so all good.
Then go offline.
The alert still says “A1”
It seems that when online it uses the server latest files but when it tries to use cache the files are old and at the moment seem to be the original files.
I have read many sites on this with no success – some suggest it will sort itself in 24 hours. Some suggest setting the maxage of the service worker to 0 (but how do you do this?). Some say the files need renaming each time they change which seems very clunky.
The service worker is definitely working
main.js
$(document).ready(function () {
'use strict';
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then(res => {
console.log("service worker registered");
res.onupdatefound = () => {
const installingWorker = res.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller){
alert("new update available");
forceReload();
}
else {
alert("all good");
}
break;
}
}
}
})
.catch(err => console.log("service worker not registered", err))
}
});
const forceReload = () =>{
console.log("ForceReload");
navigator.serviceWorker
.getRegistrations()
.then((registrations) =>{
console.log(registrations);
//alert("reg");
Promise.all(registrations.map((r) => r.unregister()))
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
},
)
.then(() => {setTimeout(() => {
location.reload();
}, 500);
})
}
sw.js
let version =5; // update this to send update.
var cacheName = 'cacheV5'
var filesToCache = [
'/',
'/manifest.json',
'/index.html',
'/sales10.html',
'/getdata.html',
....
....
'/js/siteJS/sales10.js',
'/js/siteJS/getdata.js',
'/js/jquery/3.4.1/jquery.min.js',
'/js/bootstrap/bootstrap.min.js',
'/js/bootstrap/popper.min.js'
];
/* Start the service worker and cache all of the app's content */
self.addEventListener('install', function(e) {
self.skipWaiting();
e.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
/* Serve cached content when offline */
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(stripQueryStringAndHashFromPath(e.request.url.replace(/^.*\/\/[^\/]+/, ''))).then(function(response) {
return response || fetch(e.request);
})
);
});
function stripQueryStringAndHashFromPath(url) { //added this so when url paramerters passed grabbing the cashed js works
return url.split("?")[0].split("#")[0];
}
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return true;
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});

Improving PWA Page Load

I have a PWA, which is essentially a book reader. As a result, it needs lots of data (viz. the book text) to operate. When analyzed by Lighthouse, it scores poorly on the Page Load Check.
My question is: What methods could I employ to improve the page load, while still ensuring offline functionality?
I could have a minimal start page (e.g., just display a 'Please wait, downloading text' message) and then dynamically download (via injected script tag or AJAX) the JSON data file. However, I'm not sure how I would subsequently ensure that the data is fetched from the cache.
Just wondering how others have handled this issue...
Since this question has gone tumbleweed, I decided to post the results of my attempts.
Based on Jake's article, I used the following script and Chrome DevTools to study service worker events:
'use strict';
let container = null;
let updateFound = false;
let newInstall = false;
window.onload = () => {
container = document.querySelector('.container');
let loading = document.createElement('div');
loading.classList.add('loading');
loading.innerHTML = 'Downloading application.<br>Please wait...';
container.appendChild(loading);
console.log(`window.onload: ${Date.now()}`);
swEvents();
};
let swEvents = () => {
if (navigator.serviceWorker) {
navigator.serviceWorker.ready.then(() => {
console.log(`sw.ready: ${Date.now()}`);
if (!updateFound) {
loadApp();
return;
}
newInstall = true;
console.log(`new install: ${Date.now()}`);
}).catch((error) => {
console.log(`sw.ready error: ${error.message}`);
});
}
navigator.serviceWorker.register('/sw.js').then((reg) => {
reg.onupdatefound = () => {
updateFound = true;
console.log(`reg.updatefound: ${Date.now()}`);
const newWorker = reg.installing;
newWorker.onstatechange = (event) => {
if (event.target.state === 'activated') {
console.log(`nw.activated: ${Date.now()}`);
if (newInstall) {
loadApp();
return;
}
refresh();
}
};
};
}).catch((error) => {
console.log(`reg.error: ${error.message}`);
});
};
let refresh = () => {
console.log(`refresh(): ${Date.now()}`);
// window.location.reload(true);
};
let loadApp = () => {
console.log(`loadApp(): ${Date.now()}`);
let child;
while (child = container.firstChild) {
container.removeChild(child);
}
let message = document.createComment('p');
message.textContent = 'Application loading';
container.appendChild(message);
let tag = document.createElement('script');
tag.src = './app.js';
document.body.appendChild(tag);
};
Along the way, I learned that once a service worker is registered, it immediately begins downloading all cached resources. I had assumed that resources were cached only after the page loaded them. I also found some definitive event patterns to indicate which lifecycle phase was occurring.
For a new install, the following events are logged in the above script:
window.onload -> reg.updatefound -> sw.ready -> nw.activated
For this case, when sw.ready fires, all resources have been cached. At this point, I can switch the app from the 'please wait' phase and dynamically load the cached resources and start the app.
For a simple page refresh, the following events are logged:
window.onload -> sw.ready
This will be the event sequence if the app has already been downloaded and no updates are available. At this point, I can again switch phase and start the app.
For a page refresh when the service worker script has been updated, the following events are logged:
window.onload -> sw.ready -> reg.updatefound -> nw.activated
In this case, when nw.activated fires, all cached resources have been updated. Another page refresh is required to actually load the changes. At this point, the user could be prompted to update. Or the app would update on its own the next time it was started.
By tracking these event patterns, it is easy to tell which lifecycle phase the service worker is in and take the appropriate action.

Protractor not waiting for modal

I have a modal that shows up in a protractor test. When I run the test case solo, it works perfectly, but when I run it as part of the larger file (currently 10 cases, some lengthy) it slows things down and, as a result, the modal is slower to open and close. The chain effect is that, when I try to click on a button on the page, it crashes since the modal (which was supposed to be closed by now) is still there and blocking the button.
How can I properly sense when the modal is open/closed so that I can work around this?
Thanks!
(Also, this is an AngularJS application)
These are my helper functions for manual waiting:
static async forElementAbsent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.stalenessOf(element), 10000,
`Timed out waiting for element to be absent`).then(() => {
resolve();
});
});
}
static async forElementPresent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.presenceOf(element), 10000,
`Timed out waiting for element to be present`).then(() => {
resolve();
});
});
}
In our tests, we are waiting for modals to be displayed manually. We have helper functions such as
export async function forCondition(condition: () => boolean | PromiseLike<boolean>, timeout = 20000): Promise<boolean> {
try {
return await browser.wait(condition, timeout);
} catch (err) {
return false;
}
}
Given function waits for a condition to fullfill. So in your particular case you would have to find out the css selector of the modal which is displayed.
Let's say the selector is by.css('.modal.visible'), then we would have to write something like the following if we wanted to wait for the modal to be displayed:
t.it('modal should be displayed', async function() {
// wait for modal to be displayed
var result = await forCondition(function() {
return element(by.css('.modal.visible')).isDisplayed();
});
expect(result).toBeTruthy();
});

How to I find an element with protractor?

It seems if I use element(by.css('#id')); protractor sometimes finds it, sometimes doesn't. Depends if the DOM is settled down or not. I've gone and some this:
getElementsByCss: function (cssVal) {
// return element.all((by.css(cssVal)));
return self.wait(function () {
return element.all((by.css(cssVal))).then(function (els) {
return els;
}, function () {
return false; // Don't fail, we want to retry
});
}, GET_ELEMENT_TIMEOUT, 'Elements not found by css: ' + cssVal);
},
which plays a game of retrying to get the element for some timeout period. We use 5 seconds. This seems to work, most of the time. When running locally we have no issues.
When we run on the cloud (sauce labs) they have exceedingly slow VMs. We still get random issues. Unfortunately these games have to be played everywhere, for example:
getText: function (el) {
return self.wait(function () {
return el.getText();
}, 1000, 'Could not get element text.');
},
or
expectBulletin: function () {
var el, text;
return self.wait(function () {
// Find the bulleting element, make sure it's visible and grab its text.
// If any of those three fail, try all of them again.
return self.getElementByCss('.bulletin')
.then(function (elm) {
el = elm;
return self.isElementVisible(el);
}, function () {
return false;
})
.then(function () {
return self.getText(el).then(function (text) {
return text;
}, function () {
return false;
});
}, function () {
return false;
});
}, 10000, 'Count not get bulletin text.')
.then(function (result) {
text = result;
return self.executeScript('arguments[0].click();', el.getWebElement());
})
.then(function () {
return self.isElementNotVisible(el);
})
.then(function () {
return text;
});
},
all the self.waits are just a browser.driver.wait wrapper...
wait: function (fn, timeout, msg) {
return browser.driver.wait(fn, timeout, msg);
},
This feels like a big pack of bandaids and its not working all the time. expectBulletin works 99% of the time locally, but when run remotely on the slow VMs, it works about 50% of the time. Sometimes the text comes back blank, or issues about clicking an invisible element or just not finding the bulletin element.
Is there any better way to do this?
it seems if I use element(by.css('#id')); protractor sometimes finds
it, sometimes doesn't.
You know that it returns a promise right? If you want to get the absolute value of an element, you have to handle its promise first.
Protractor waits for the page to be ready before doing any assertions. If your page is "ready" but some data is missing, there is something wrong with your angular application.