I am making an application with angularjs which can be loaded in a browser as a web page or in Office as an add-in. So i load office.js in index.html for both cases. But I realise that we should not do <body ng-app="myApp"> and angular.bootstrap(document, ['myApp']) together, otherwise controllers will execute twice. So I decided to not write <body ng-app="myApp"> and always use angular.bootstrap in both cases (ie, web page & add-in).
So for a web page, I could write:
$(document).ready(function () {
angular.bootstrap(document, ['myApp'])
})
app = angular.module('myApp', ['ui.router', 'ui.bootstrap'])
...
So for a web page, I need to write angular.bootstrap inside Office.initialize, and share other code with the case of add-in:
Office.initialize = function (reason) {
$(document).ready(function () {
angular.bootstrap(document, ['myApp'])
});
}
app = angular.module('myApp', ['ui.router', 'ui.bootstrap'])
// share the same code
However, if I write these two cases together as follows, it works for a web page, whereas I gives Error: ng:btstrpd App Already Bootstrapped with this Element for add-in.
$(document).ready(function () {
angular.bootstrap(document, ['myApp'])
console.log("bootstrapped outside Office.initialize")
})
Office.initialize = function (reason) {
$(document).ready(function () {
angular.bootstrap(document, ['myApp'])
console.log("bootstrapped inside Office.initialize")
})
}
app = angular.module('myApp', ['ui.router', 'ui.bootstrap']).
If I set a flag, console will display bootstrapped outside Office.initialize followed by isBootstrapped, then running the code will show that Office.context or Office.context.document is undefined:
var isBootstrapped = false;
$(document).ready(function () {
angular.bootstrap(document, ['myApp'])
isBootstrapped = true
console.log("bootstrapped outside Office.initialize")
})
Office.initialize = function (reason) {
$(document).ready(function () {
if (isBootstrapped) console.log("isBootstrapped")
else {
angular.bootstrap(document, ['myApp'])
console.log("bootstrapped inside Office.initialize")
}
})
}
app = angular.module('myApp', ['ui.router', 'ui.bootstrap'])
So I really need an efficient way to check if Office.js is loaded outside of Office client (ie, whether it is a web page or an add-in), to decide which piece of angular.bootstrap should be executed.
Just for reference, here is the StackOverflow question that started this:
https://stackoverflow.com/questions/44989152/check-if-office-js-is-loaded-outside-of-office-client/44989990?noredirect=1#comment76977223_44989990
We also need this feature. We would like to redirect to a different URL (or show a help view without redirecting) when the add-in has been loaded outside of an office client, which could for example happen when the user command-clicks on a link within the add-in.
OK, thanks for pinging this. Let me re-bring it up to the team in terms of trying to get this addressed on the sooner side...
I met the same issue, and this helped me.
So, when will it be fixed?
@Hongbo-Miao , FYI that an internal partner brought this up today as well.
Had a conversation with Michael Zlatkovsky, we are starting the new API design now.
@SoftTimur, in case it helps. I have recently added a functionality in one excel addin to be able to test it in the browser. I am initialising it in the browser by checking typeof Office.context === 'undefined'. Because this API will not be present in browser in case you are using Office Javascript API.
@ershwetabansal yes, that works quite well
@SoftTimur , @KingHenne , @ershwetabansal , @fergiemcdowall : I'm happy to say that we're close to having an API that will fulfill this need. In fact, you can dogfood it already (just for your own testing, don't use it in production yet!) -- instructions lower down.
To give a bit of background: The issue with today's Office.initialize is
setTimeout, to use a contrived examples), and it won't fire.Office.initialize declared (you can't have multiple ones, because you're directly setting the object).The good news is that this is going to change very soon! We are about to release an API to replace (or rather, append, but with the intention of having one supercede the other) Office.initialize with something that addresses both issues. An "Office.onReady" API, that can be used as follows:
Office.onReady(function() {
console.log("Office is now ready 1!");
});
Office.onReady(function() {
console.log("Office is now ready 2!");
});
// And both should fire, after the host is ready
or as a Promise:
Office.onReady()
.then(function() {
console.log("Office is now ready 1!");
});
Office.onReady()
.then(function() {
console.log("Office is now ready 2!");
});
or even with TypeScript's async/await:
(async () => {
await Office.onReady();
console.log("Office is now ready!");
})();
And with Office.onReady, it shouldn't matter where you call it. If you call it before the host is ready, we will wait to fire it until the host is ready. Or if you call it some time later, we'll just immediately fire -- just like jQuery's $(document).ready.
If you'd like to try it, could you please reference the build from https://unpkg.com/@microsoft/[email protected]/dist/office.js, and see whether this API works for you. (Unpkg is a virtual CDN service, not affiliated with Microsoft -- but it does provide an easy way to test things, and is often used elsewhere in the web community). If it works, the good news is that the API should be on the CDN soon (in a matter of weeks). If it doesn't, then we have to dig deeper.
Would love to get your usage and input before the API goes live, in the coming weeks...
Thanks!
FYI, the onReady feature has not shipped to the production CDN.
Note there is still one issue (currently being investigated), where it look like on some hosts/platforms (specifically, Outlook Online, but maybe others), there currently still is a requirement to have an "Office.initialize" in order to get the add-in to load. Having it be empty is totally fine: Office.initialize = function() { }. So for now, just be aware that you may still need to include Office.initialize to appease the framework, but you can use onReady for the actual work.
Hello, @Zlatkovsky,
I noticed that the API is already in the production CDN and I tried it.
While it seems to be working fine in some hosts, in Office Online is resolving the promise even before the Office.Initialize event is triggered. This means that we still wouldn't have access to the context API when the onReady promise is resolved the first time.
Cheers!
@raulgg, that definitely seems like a bug. Let me see if someone can take a look at it.
Opening https://github.com/OfficeDev/office-js/issues/200 in place of this one, to track the Office Online issue
I hope this isn;t too far off topic, but I have a question:
I ham finalizing the JS and CSS loading for an Office add-in, and need to write a fallback for the Office CDN. I have one for jQuery that works, but I don't know what to check for if the AppsForOffice files do not load. What object do I need to test for? I've searches around and did not find any example code for this CDN. I've tried testing for either Office or OSF.
jQuery Example (does work)
<!-- jQuery CDN and fallback -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
</script>
<script>window.jQuery || document.write('<script src="../../Scripts/jquery-3.3.1.min.js">\x3C/script>')</script>
AppsForOffice Example (does not work)
<!-- Office CDN and fallback -->
<link rel="preconnect" href="https://appsforoffice.microsoft.com">
<script src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script>
<link rel="stylesheet" href="https://appsforoffice.microsoft.com/fabric/2.6.1/fabric.min.css">
<link rel="stylesheet" href="https://appsforoffice.microsoft.com/fabric/2.6.1/fabric.components.min.css">
<script>
window.Office ||
document.write('<script src="../../Scripts/Office/1/office.js">\x3C/script>')
document.write('<link rel = "stylesheet" href = "../../Content/fabric.min.css" />')
document.write('<link rel="stylesheet" href="../../Content/fabric.components.min.css" />')
</script>
As mentioned, what object do I need to test for?
Most helpful comment
@SoftTimur , @KingHenne , @ershwetabansal , @fergiemcdowall : I'm happy to say that we're close to having an API that will fulfill this need. In fact, you can dogfood it already (just for your own testing, don't use it in production yet!) -- instructions lower down.
To give a bit of background: The issue with today's
Office.initializeissetTimeout, to use a contrived examples), and it won't fire.Office.initializedeclared (you can't have multiple ones, because you're directly setting the object).The good news is that this is going to change very soon! We are about to release an API to replace (or rather, append, but with the intention of having one supercede the other)
Office.initializewith something that addresses both issues. An "Office.onReady" API, that can be used as follows:or as a Promise:
or even with TypeScript's async/await:
And with Office.onReady, it shouldn't matter where you call it. If you call it before the host is ready, we will wait to fire it until the host is ready. Or if you call it some time later, we'll just immediately fire -- just like jQuery's
$(document).ready.If you'd like to try it, could you please reference the build from https://unpkg.com/@microsoft/[email protected]/dist/office.js, and see whether this API works for you. (Unpkg is a virtual CDN service, not affiliated with Microsoft -- but it does provide an easy way to test things, and is often used elsewhere in the web community). If it works, the good news is that the API should be on the CDN soon (in a matter of weeks). If it doesn't, then we have to dig deeper.
Would love to get your usage and input before the API goes live, in the coming weeks...
Thanks!