Office Dialog API with single-page application throwing error - javascript

I am working on an add-in and it's an Angular single page application. Currently it has only one controller ( one screen ). Based on the documentation:
If your add-in uses client-side routing, as single-page applications
typically do, you have the option to pass the URL of a route to the
displayDialogAsync method, instead of the URL of a complete and
separate HTML page.
HTML
<button type="button" class="ms-Button ms-Button--compound" id="addPlaceholder" ng-click="openPopup()">
<span class="ms-Button-label">Open Popup</span><span class="ms-Button-description">Open Popup</span>
</button>
Controller code
researchApppBuilderModule.controller('researchcontroller', ['$scope', '$location',
function ($scope, $location) {
var urlRoot = $("base").first().attr("href");
$scope.openPopup = function () {
var dialog;
Office.context.ui.displayDialogAsync(urlRoot + '/app/research', { height: 30, width: 20 },
function (asyncResult) {
dialog = asyncResult.value;
});
}
}]);
I have tested this route within the Add-in and it's working. So, I know the route is correct.
So, this code opens up a dialog but with an error:
ADD-IN ERROR:
Sorry, we cannot load the add-in. Please make sure you have network and/or Internet Connectivity. Click "Retry" once you're back online.
Error Screenshot
I tried searching in the documentation but couldn't find anything. What am I missing?

Although you "can" pass a route to the displayDialogAsync method, I don't recommend it for reasons given in the note just below the paragraph of documentation that you quoted: An entirely new instance of your add-in is launched in the dialog. This means that the app goes through its initialization code. This usually confuses the routing strategy and the dialog ends up trying to navigate to a URL that is an invalid combination of the default route and the route that you passed to the method, like https://example.com/app/#/app/research.
I recommend that create a research.html page and pass the URL of it to the dialog (as this sample does: Word-Add-in-AngularJS-Client-OAuth). This may seem to deviate from the purity of a single page app; but you're doing that anyway when you pass a route to the method, because you are really passing a second instance of the app's "single page" (because you're launching a second instance of the whole app).
Otherwise, you can troubleshoot, by having the callback to displayDialogAsync log the asyncResult.error.code and asyncResult.error.message properties to the console. I also recommend that you read Use the Dialog API | Handle errors and events. You could also use the Fiddler tool or the Charles tool to see what URL the dialog is trying to open.
Finally, your code doesn't show what the urlRoot is. Be sure it is an absolute URL including the HTTPS protocol.

Related

Redirect existing website link to app expo react native

I have an app that has some payment sandbox. I am using "expo-linking" Linking.openURL(sandboxURL) to open the unique payment link on a browser everytime user wants to buy the product.
If the payment is cancelled the sandbox redirects to "https://mywebsitedomain/payment/cancel"
If the payment is successful the sandbox redirects to "https://mywebsitedomain/payment/successful"
What I want to achieve is I want to the browser to redirect to my app's payment cancel and payment successful page whenever "https://mywebsitedomain/payment/cancel" and "https://mywebsitedomain/payment/successful" links get triggered in the browser.
How do I do that? I got suggestions like using deeplinking. in such case I dont necessarily need to create any link, I just need the existing website link to be redirected to my app. In that case what would be the ideal configuration?
Thank you.
In this case I would recommend using WebView instead of Linking, because Linking doesn't really give you any kind of controls to the flow after user opens an URL in the browser, DeepLinking would theoretically work, but it would open the app from the splash screen and then you would have to navigate the user manually to the screen you want him to be, I don't think this is the proper solution though..
Instead I would suggest opening an URL in Webview and injecting the custom javascript code. In this way, you would keep the user in your app and maintain the control to the flow. react-native-webview which is deprecated module and should be added through package manager has onMessage and injectedJavaScript props, and using these could solve this issue.
As an Example :
Create separate screen which contains only webview, stretched to the full screen.
Import the screen in your stacknavigator with appropriate name.
When navigating to it, pass webview props as navigation params.
Afterwards:
const jsCode = `
document.addEventListener("click", () => {
window.ReactNativeWebView.postMessage(JSON.stringify({type: "click", message : "goBack"}))
})`;
const onMsg = (event) => {
const res = JSON.parse(event.nativeEvent.data);
if (res.message === "goBack") {
navigation.goBack();
}
}
<WebView
source={{ uri: params.uri, html: params?.html }}
androidHardwareAccelerationDisabled
javaScriptEnabled
injectedJavaScript={jsCode}
javaScriptCanOpenWindowsAutomatically
collapsable
onMessage={onMsg}
/>
this is an example code which closes the webview whenever the user clicks something in there, instead you could check window.location.href, post it as a message to your app and if it equals to https://mywebsitedomain/payment/cancel do something you want to do :)
UPDATE
as mentioned in the comments, for this concrete task webview offers onNavigationStateChange prop, which is meant to determine the navigation updates in the webview. This might be a better option, as some of the websites might not allow you to inject custom javascript code. But however, javascript injection is also possible and would also be one of the solutions here.
As it's a bit hard for explaining here, I've set up an example app here, which uses both of the approaches described above:
https://github.com/RocKer004/webview-example-app
App opens react native navigation docs, if you click on React Native Express (first option) it'll take you back and close the webview (this is handled using the onNavigationStateChange prop), if you click the last option there webview is also going to be closed, but this is handled by javscript injection this time. Otherwise, you're free to browse the web.
For more info, check :
https://github.com/react-native-webview/react-native-webview

angular javascript cleaning URL

I have a SPA application developed using AngularJS. Access to the application can take place in two ways:
1) By entering the address (example) www.example.com, or
2) By clicking on a link like http://www.example.com?do=this&param=1234
When the application is started, it examines the URL and, if suitable parameters are found, it prompts the user to log-in/register and then goes to a specific page within the application where the visitor is supposed to perform some activities related to the action this with parameter 1234.
Traversal through pages is handled by the javascript command:
$window.location.href="#/other_page;.
This works ok so far, except that the URL is kept with the parameters. As such, whenever the application decides that it needs to go to another page, the URL (in the address bar of the browser) would look like:
http://www.example.com?do=this&param=1234#/other_page
which is messing the behavior of the application.
My question is: Once I was able to extract from the original URL the received parameters, I want to clean the url and proceed with its normal contents, which would be something like:
http://www.example.com#/other_page.
How can this be achieved?
Thanks in advance.
Using $location service clear the search query params from address bar of the browser.
Syntax is:
$location.search({});
For Example After finished your login or register page.
$scope.login = function(){
// Check your logic and clear search params
if($routeParams && $routeParams.src)
{
$location.search({});
$location.path($scope.baseurl+$routeParams.src);
}
};
Here am using $routeParams service you could also check $window.location.search
You can use $location service. It says:
Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
So, basically you can do:
$location.path('other_page')
which should clear the URL and set this path instead.
Just found a working solution. I use the following command:
$window.location.href = location.protocol +
"//" +
location.host +
"/index.html#/other_page" ;
which makes the trick. Perhaps not the most elegant, but it works as needed.
Thank you guys (#vivek and #prakashA) for your support.

AngularJS/HTML - Cannot execute links to same page after introducing interceptor

Would like to check what is the issue caused to my <a href> links when after I introduced an interceptor to my angular app and it has cause the links to not reload when it is on the same page? below is how i introduce my interceptor to add jwt's Authentication token to my web service requests header.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(['$q', '$localStorage', '$location', function ($q, $localStorage, $location) {
return {
'request': function (config) {
config.headers = config.headers || {};
if ($localStorage.jwtToken) {
config.headers.Authorization = 'Bearer ' + $localStorage.jwtToken;
}
return config;
},
'responseError': function (response) {
if (response.status === 401 || response.status === 403) {
}
return $q.reject(response);
}
};
}]);
}]);
Noted that the presentation site and the business logic processing part are both independently separated and do not rely on each other. Which means that the presentation site is responsible to load the javascripts and HTML codes while the scripts are the one that is responsible to request data from the server. Authentication are done via JWT therefore I use the interceptor to inject the JWT related headers to every RESTful requests.
PHP => (renders HTML template) + (data from Angular) <= Angular => (send request to server get data)
Using the code above i was able to complete the JWT authentication but it causes all the <a href> links on the presentation page to not reload if it is in the same page. For example I have 3 items in my menu (Home, Page1, Page2). When I'm in Home and if I click on the Home link, it suppose to reload the page (like F5) but nothing happens. I would need to navigate away from the page then only i am able to click on the link.
What have i done wrong here?
Update 1: Question from #Sanjeev: How are you handling routing in you app, are you using ng-router module or custom ui-router module ? Can you add the routing code as well.
Noted that at this moment all routes are within the HTML itself using links. The javascripts do not handle any routes. Its responsibility is to GET and POST data.
Update 2: Added plunker link. Note that i would suggest you to try both commenting and uncommenting the entire interceptor section to see the difference when clicking the link. Follow these instruction below and you will recreate the scenario i mentioned.
Load and run the plunker file
On the top right corner, click on "Launch the preview in a separate window"
Copy the URL in the window and replace it in the section in line 25. The url should look something like run.plnkr.co/somerandomkeys+
Close the separate window and try clicking the link in the menubar.
When commenting the said section, notices that the page will load (acts as a refresh) but when you uncomment the section the link will not work anymore. Some sort of same page detection thing is blocking the action.
Solution :
Analysis: I ran your demo and understood the issue you were highlighting, actually the issue is not related to interceptors at all. Interceptors get called only when you make http requests using $http service.
In Angular apps the anchor tag behavior changes the moment you inject '$location' service in your app, you have injected '$location' service in your interceptor module (although i don't see it being used). So this solves the mystery why you start getting anchor issue when you add interceptor :)
In your example the anchor has same link as the current location so Angular is preventing the default behavior of anchor tag and clicking anchor does not reload your page.
You can solve it be multiple ways:
Don't inject '$location' service if you are not using it, if you can't remove it then go for solution 2 or 3.
Add attribute target="_self" or target="_blank" as per your case, this will solve your issue without requiring any Js code change. I tested this fix with your code and it worked for me.
Add a ng-click handler on anchors and change window.location in
it, or better create a directive for anchors and check if href is
same as current location then force page reload using location.reload()
If you decide to use angular routing which is great feature of Angular JS then use $route.reload() method

Load initial data after log in with an angularjs app

I have a completely separate (from backend) AngularJS app. It uses tokens (JWT) for authentication. I'm also using ui-router and Restangular.
My problem:
I have a sidebar/profile area that displays information from the currently logged in user on every "page". It is implemented as a directive+controller and is outside of the ui-view directive context. Inside of the controller for this sidebar I'm making a request to get the profile information and attach it to the $scope. Now, when a user first visits the app that sidebar and controller get invoked and the request is made to get the profile information. However, if and since the user is not logged in, the request fails and no data is bound with the $scope.
In the meantime (notice this all happens really fast), the event listener (listening for $stateChangeSuccess from ui-router) determines that the user is not logged in (essentially if they don't have a token) and does a $state.go('login') to forward the user to the login view. The user logs in, and a $state.go('dashboard') is invoked to bring them back to the dashboard. However, being that sidebar is outside of the context of the router the controller for it is not instantiated again to cause a new request for profile information. So the sidebar is just empty with no profile information.
I'm aware that there are going to be several possible solutions to this, but I'm trying to find a descent one.
How would you architect an angular app in order to solve or avoid the problem I'm having?
P.S. I think I may be having analysis paralysis over this.
It's hard for me to answer without seeing your code specifically. If I understand correctly your directive is firing prior to the user logging in, and since there is no user profile, the side bar doesn't initiate correctly. What I would suggest is possibly doing an ng-if on the tag that fires the directive something like:
<side-bar ng-if='userData' />
That way the tag isn't inserted into the DOM until the userData exists and therefore doesn't fire the directive on the login page.
Assuming that the sidebar is the highest angular controller in your application and the other controllers are nested inside it you should be able to put a function on it that will load the information that you need. Then you can call $rootScope.loadme() anywhere you inject $rootScope.
var mainMod = angular.module('mainMod',[]);
mainMod .controller('mainController', function($scope)
{
$scope.loadMe = function()
{
//load stuff here
};
$scope.loadMe();
});
mainMod .controller('secondController', function($rootScope, $scope)
{
$rootScope.loadMe();
});
SudoCode probably wont work with copy paste but the idea should be sound.

Why the conflicting variables?

I'm getting conflicting results between the facebook javascript SDK and the python requesthandler variables. The Javascript SDK says my user is not logged in, which is correct, while my template variable that comes from the base request handler says that my user is logged in and displays the name of the user. Is there enough info to tell what is wrong or should I paste the code I think is relevant here? A link to the login page that has the error is here. The example I used is called the runwithfriends demo app from facebook and everything with that app worked except using the logic from the app just from a website without requiring the user to be in the iframe of the app.
Plus I can't seem to get the real-time API working. I can only save userID and not refresh user data - why? I have the code but I'm not sure what's most relevant but here's some of the request handler, the relevant code is basically exactly the same as the one from the demo app:
def render(self, name, **data):
logging.debug('render')
"""Render a template"""
if not data:
logging.debug('no data')
data = {}
data[u'js_conf'] = json.dumps({
u'appId': facebookconf.FACEBOOK_APP_ID,
u'canvasName': facebookconf.FACEBOOK_CANVAS_NAME,
u'userIdOnServer': self.user.id if self.user else None,
})
data[u'logged_in_user'] = self.user #variable that is the problem
data[u'message'] = self.get_message()
data[u'csrf_token'] = self.csrf_token
data[u'canvas_name'] = facebookconf.FACEBOOK_CANVAS_NAME
self.response.out.write(template.render(
os.path.join(
os.path.dirname(__file__), 'templates', name + '.html'),
data))
And even more strange, I can also get the application in a state where the javascript SDK says the user is logged in and the template variable logged_in_user says otherwise. Why are the variables conflicting?
Update: Here are screenshots from the strange login flow. I can go to my page and my name from facebook appears:
Then when I go to next page it also looks alright and has my name
But if I log out then I gets in impossible state: my name + logged out
How can I resolve this strange conflict between js and back-end?
Update: Since I only have this problem for one of my apps I can take what works from my other app and integrate. This page seems to work from my other app: http://cyberfaze.appspot.com/file/20985
Your 'user' is probably referring to the Django user not the Facebook user. Make sure you synchronize the two accounts correctly using a custom authentication backend. It's possible that the accounts get out of sync i.e. if the user switches browsers.
Keep in mind that the Facebook Python SDK will stop working after October 1st unless they update it to Oauth2.0 which is unlikely.
I just updated django-facebook-graph to work with the new authentication flow.

Categories