I have created a Tab App in Teams. Now I want to make a dialog from one tab accessible via a Message Extension App. This works partially now through embedding the contentUrl of the specific tab as an iFrame in the Task Module of the Message Extension like here: https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/create-task-module?tabs=dotnet#create-a-task-module-with-an-embedded-web-view
. The issue is the authentication. Api Calls won't work and the dialog is not able to retreive or send data.
In the manifest.json of the Tab App are the contentUrls of the tabs in the "staticTabs" section:
"staticTabs": [
{
"entityId": "dashboard",
"name": "Dashboard",
"contentUrl": "https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?app.locale={locale}&page.subPageId={subEntityId}&app.theme={theme}",
"scopes": [
"personal"
]
}
],
I gave the dialog a specific Route via React Router so that you can access the dialog via subPageId. This works fine.
The Problem is, if you access the contentUrl, you won't be authenticated and API calls to the Graph API and own API won't work. This issue does not go away if I embed the tab via contentUrl in a Task Module for the Message Extension to give it a Teams Context:
public async handleTeamsMessagingExtensionFetchTask(
context: any,
action: any
): Promise<any> {
return {
task: {
type: 'continue',
value: {
width: 925,
height: 925,
title: 'Feedback Dialog',
url: "https://2e70-37-201-241-91.ngrok.io/feedbackDialog.html",
fallbackUrl: "https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?page.subPageId=feedbackDialog"
}
}
};
}
Directly embedding the url like in "fallbackUrl" will result in an empty Task Module so I embedded the Url in a like this in feedbackDialog.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello World Feedback!</title>
</head>
<body>
<script src="https://statics.teams.microsoft.com/sdk/v1.7.0/js/MicrosoftTeams.min.js"></script>
<script>
microsoftTeams.initialize();
microsoftTeams.getContext((context) => {
console.log(context);
microsoftTeams.authentication.getAuthToken({
successCallback: (token) => {
console.log(token);
},
failureCallback: (reason) => {
console.error(reason);
}
});
});
</script>
<div style="padding:50px;">
<iframe id="feedbackDialog" width="800" height="800", frameBorder="0"
src="https://cdne-stcsfeedbackuidev.azureedge.net/tabs/dashboard.html?page.subPageId=feedbackDialog"></iframe>
</div>
</body>
</html>
The dialog gets displayed in the Task Module this way like if I open the contentUrl in the browser directly. But the Authentication does not work. The Teams context can be retreived but all API calls return with the Error: "Error: SDK initialization timed out".
Is there a simple way for me to authenticate the user here because this runs in Teams as a MessageExtension App and the embedded content in the Task Module comes from a Teams Tab App. Or do I need to manually implement something using MSAL?
Setup
"workbox-cdn": "^5.1.4",
"nuxt": "^2.15.2"
Context
My app, Pictalk, let users save and get pictograms. So basically every user has a custom set of pictograms. For now, it works only online but I want to implement offline-mode.
Technical Details
I display all my pictograms with the html <img .../> tag.
Every time I load a new pictogram I do so:
created(){
if(navigator.onLine){
caches.open('pictos').then((cache) => {
cache.add(this.collection.path)
.then(() => {})
.catch((err)=> {console.log(err)})
});
}
},
Here is a screenshot of the Cache Storage :
As we see the URL is correct and the requests are cached correctly.
Problem
The <img .../> tag doesn't use the workbox cache I created.
Found out the solution here and here.
Here is the files I had to modify in order to make it work :
First, my <img/> tags had to use the crossorigin="anonymous" method :
<img class="image" style :src="path" crossorigin="anonymous"/>
Once the <img/> tags are more flexible with their origin we can start building our custom registered workbox route:
// plugins/workboxConfig.js
workbox.routing.registerRoute(
new RegExp('https://myapi\\.somewhere\\.com/pictalk/image/.*\\.(png|jpg|jpeg)'),
new workbox.strategies.CacheFirst({
cacheName: 'pictos',
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({ statuses: [200] }),
new workbox.rangeRequests.RangeRequestsPlugin(),
],
matchOptions: {
ignoreSearch: true,
ignoreVary: true
}
}),
);
I had to declare this file here in the nuxtjs.config.js :
pwa: {
workbox: {
cachingExtensions: '#/plugins/workboxConfig.js'
}
}
Is there anyway to use JSON-LD without including the script inline in the HTML, but still get Google (& other) spiders to find it? Looking around I've seen some conflicting information.
If this was the JSON-LD file:
<script type="application/ld+json">
{
"#context" : "http://schema.org",
"#type" : "WebSite",
"name" : "Example Site",
"alternateName" : "example",
"description" : "Welcome to this WebSite",
"headline" : "Welcome to Website",
"logo" : "https://example.com/public/images/logo.png",
"url" : "https://example.com/"
}
</script>
And I have this in the head of the HTML:
<script src="/public/json-ld.json" type="application/ld+json"></script>
EDIT: I've also tried:
<link href="/public/json-ld.json" rel="alternate" type="application/ld+" />
Google Spiders seem to miss it and so does the testing tool unless I point it directly at the file. I'm trying to work around unsafe-inline in the CSP. And the only thing I can find is this, which would work in Chrome but don't want to be firing console errors on every other browser. Plus, I just like the idea of Schema.org data being abstracted out of the page structure. Would adding the JSON-LD to the sitemap for Google Webmaster Tools help?
Apologies, total noob to JSON-lD and keep ending up in email documentation (this would be for a site) or old documentation.
True, it can not be made external and it is not supported inline, but you can still achieve what you want by injecting it into the DOM via a JavaScript file.
Note: I am using an array for neatness so I can segment all the structured data elements and add an infinite amount of them. I have a more complicated version of this code on my websites and actually have an external server side rendered file masquerading as a JavaScript file.
An yes Google search bot does understand it. May take days for it to register and using Webmaster tools to force a re-crawl does not seem to force a refresh of JSON-LD data - seems like you just have to wait.
var structuredData = {
schema: {
corporation: {
'#context': 'http://schema.org',
'#type': 'Corporation',
'name': 'Acme',
'url': 'https://acme.com',
'contactPoint':
{
'#type': 'ContactPoint',
'telephone': '+1-1234-567-890',
'contactType': 'customer service',
'areaServed': 'US'
}
},
service: {
'#context': 'http://schema.org/',
'#type': 'Service',
'name': 'Code optimization',
'serviceOutput' : 'Externalized json',
'description': 'Inline json to externalized json'
},
},
init: function () {
var g = [];
var sd = structuredData;
g.push(sd.schema.corporation);
g.push(sd.schema.service);
//etc.
var o = document.createElement('script');
o.type = 'application/ld+json';
o.innerHTML = JSON.stringify(g);
var d = document; (d.head || d.body).appendChild(o);
}
}
structuredData.init();
I have been trying to get someone to explain to me how the dojo AMD loading works and to get a simple piece of code to work. I understand that if using for example the CDN, one has to call the dojo library and load all modules you wish to use. I have tried to implement other javascript functions based on activity from the main page and I will always get the function either undefined or an error related to a dojo control undefined. It seems that all the modules that initially load are not available to the rest of the code. Any helpful explanations would be really appreciated.
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dojo/resources
/dojo.css" />
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dijit/themes/
tundra/tundra.css" />
<link rel="stylesheet" type=
"text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/dojox/mobile/themes/
iphone/iphone.css" />
<title> DOJO </title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.8.4/
dojo/dojo.js"
data-dojo-config="async:true"></script>
<script type="text/javascript" src="Scripts/login.js"></script>
<script type="text/javascript">
require(["dojox/mobile/parser",
"dojo/parser",
"dojo/on",
"dojo/request/xhr",
"dijit/form/Form",
"dojo/store/Observable",
"dojo/store/Memory",
"dijit/Toolbar",
"dijit/Dialog",
"dojo/io/script",
"dojo/query",
"dojo/_base/lang",
"dijit/layout/ContentPane",
"dojox/mobile/Button",
"dojox/mobile/deviceTheme",
"dojox/mobile/compat",
"dojox/mobile/Heading",
"dojox/mobile/TextBox",
"dojox/mobile/Opener",
"dijit/form/TextBox",
"dijit/form/HorizontalSlider",
"dijit/form/ValidationTextBox",
"dijit/Calendar",
"dojox/mobile/ScrollableView",
"dojo/dom",
"dojo/domReady!",
"dojox/mobile"],
function (dom, domReady ,mobile, ScrollableView,
parser, query, domClass, domStyle, on, event, xhr,Form,
lang, Button, deviceTheme, compat, Heading) {
dojox.mobile.parser.parse();
});
</script>
From my understanding is that the way I have the code above is that my interface will load correctly and all widgets in the body of html will be displayed and it works fine. The problem is that I have a form that gets input from the user and on a button click event calls a function that handles the webrequests. I could not get this to work and it is merely a problem with where I am placing this function. I have added a simplified version:
What I have done is add that function to a script file to separate it from the rest of the code:
var dojoXhr;
function correctInput(div, td, msg) {
dojo.domStyle.set(div, 'display', '');
td.innerHTML = msg;
}
require(["dojo/_base/declare", "dojo/parser", "dojo/query", "dojo/dom-class",
"dojo/dom-style", "dojo/on",
"dojo/_base/event",
"dojo/request/xhr", "dijit/form/ValidationTextBox", "dojo/domReady!"],
function chklogin(declare, parser, query, dom-class, dom-style,
on, event, xhr,ValidationTextBox, domReady) {
var lname = dijit.byId('login').get('value');
var psswd = dijit.byId('password').get('value');
var feedback = document.getElementById('feedback');
var feedbackTD = dojo.query('td.feedback')[0];
if (lname == '' || psswd == '') {
correctInput(feedback, feedbackTD, 'Please enter a valid login!');
dojo.domStyle.set(feedback, 'display', '');
dojo.domStyle.set(document.getElementById('msgBodyOutter'), 'display', 'none');
feedbackTD.innerHTML = "Please enter a valid login!";
return;
}
if (!(lname == 'login') || !(psswd == 'password')) {
correctInput(feedback, feedbackTD, 'Please enter a valid login!');
return;
}
else {
dojo.domStyle.set(feedback, 'display', '');
dojo.domStyle.set(document.getElementById('msgBodyOutter'), 'display', 'none');
feedbackTD.innerHTML = "THATS IT BRO!";
return;
}
});
I got advice on the dojo forum to put my function in a define function and then use a require to call it all. I could not figure out how to do this.
It seems that all the modules that initially load are not available to
the rest of the code.
You are using a CDN to load the dojo toolkit. When you use CDN you are required to define the location of the module packages. You need to edit the dojoConfig for the code to work.
See this article about Using Custom Modules with a CDN. The important part is the packages object.
<script data-dojo-config="async: 1, dojoBlankHtmlUrl: '/blank.html',
packages: [ {
name: 'custom',
location: location.pathname.replace(/\/[^/]+$/, '') + '/js/custom'
} ]"
src="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js">
</script>
Edit: Below is a simple dojo application.
So in my case create a module called chklogin, then require it, and
when the user clicks the button it will call that module chklogin from
within the main require[] function. Correct?
I would say yes. You are correct. I think your concept is a viable option. I hope this example helps with implementing define() to create your own modules. I will try to help where I can as you develop your idea. You can download the project here while available.
Directory Structure:
/index.html
/js/config.js
/js/controller/Controller.js
/js/modules/MyFirstModule.js
/index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css">
<script src="js/config.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"></script>
<script>
require(["app/Controller", "dojo/domReady!"], function(Controller) {
//Initiate the entire application by calling main method of our Controller class.
Controller.main();
//Call our getter method of the Controller class to show how to access a private variable.
console.log(Controller.getWelcomeMessage());
});
</script>
</head>
<body class="claro" id="appBody"></body>
</html>
/js/config.js
We use packages to reference the CDN dojo files. Now we can call dojo classes by our package name
For example, "dojo/domReady!", "dijit/form/Button", "dojox/app/main". The dojo files
are stored on the google servers, which is referenced by the
<script src='http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js'>< /script>
in the index.html file.
Here we create our own custom packages. This could be for your modules, widgets, etc. The package
locations will map to the javascript directory that you store your custom dojo files in.
For example, myModules can be found in the /js/modules directory. You will reference any custom
dojo files via "myModules/MyModule", which locates and loads "/myModules/MyModule.js" file.
For an explanation of the baseURL, see: http://dojotoolkit.org/documentation/tutorials/1.9/hello_dojo/
"Defining and Requiring Modules". This code registers the correct location of our own packages so
we can load Dojo from the CDN whilst still being able to load local modules.
I created a package called "app" as you can see below. This is how I initialize my app in my project.
This was designed to allow me to keep the separation of code the best I know how. It is loaded and
called in the index.html page. So i give it a package name of app. It is physically located in the
js/controller/Controller.js file.
This dojoConfig object is used in the index.html and must be loaded prior to < script src='...dojo.js' > tag.
var dojoConfig = {
async: true,
tlmSiblingOfDojo: false,
baseUrl: location.pathname.replace(/\/[^/]*$/, ''),
packages: [
{ name: "myModules", location: "js/modules" },
{ name: "app", location: "js/controller", main: "Controller" }
]
};
if you choose to host the dojo files on your own server, you can reference them like below. Assuming the dojo js files are located in the "/js/dojo/*" directory.
packages: [
{ name: "dojo", location: "dojo/dojo" },
{ name: "dijit", location: "dojo/dijit" },
{ name: "dojox", location: "dojo/dojox" },
{ name: "myModules", location: "js/modules" },
{ name: "app", location: "js/controller", main: "Controller" }
]
/js/controller/Controller.js
Here is the controller which I use to initialize the web app.
define(["myModules/MyFirstModule"], function(MyFirstModule) {
//Private Variables...
var privateVariable1 = "Welcome to my Dojo Application!";
var privateVariable2;
/**
* init. This is a private function that is only available within this object.
*/
init = function() {
// proceed directly with startup
console.log("Startup functions are firing...");
//Render our "form" which only contains a single text box.
renderForm();
},
renderForm = function() {
MyFirstModule.createForm("appBody");
}
/**
* Enclose all public methods in the return object
*/
return {
/**
* main. This is a public function that can be called from other code.
*/
main: function() {
//Run init() method.
init();
},
/**
* getWelcomeMessage. This public function returns the value of the privateVariable1.
* This mimics a getter method.
*/
getWelcomeMessage: function() {
return privateVariable1;
}
};
}); //end define
/js/modules/MyFirstModule.js
This is an example of a custom Module. It is required by the Controller class as a dependency.
define([
//The required dependencies for this module.
"dojo/dom", "dojo/on", "dijit/form/TextBox", "dijit/form/Button"
], function(dom, on, TextBox, Button){
// Once all modules in the dependency list have loaded, this
// function is called to define the myModules/myFirstModule module.
//
// The dojo/dom module is passed as the first argument to this
// function; additional modules in the dependency list would be
// passed in as subsequent arguments (on, TextBox, and Button).
// Private variables
var firstNameTextBox;
var submitButton;
privateFunction = function() {
console.log("I am a private function. I can only be called from this class.");
};
// This returned object becomes the defined value of this module when called elsewhere.
return {
/**
* createForm. This method creates a simple form. Textbox and button.
* #param placeMeHere This is where to place the form elements. In this demo, the are placed in the
* body of the html document. This is executed in the Controller class.
*/
createForm: function(placeMeHere) {
//Create new TextBox.
firstNameTextBox = new TextBox({
name: "firstname",
value: "" /* no or empty value! */,
placeHolder: "type in your name"
}, "firstname");
//Place me in the DOM.
firstNameTextBox.placeAt(placeMeHere);
//Render
firstNameTextBox.startup();
//Create Button
submitButton = new Button({
label: "Say Hi"
}, "submitButton");
submitButton.placeAt(placeMeHere);
submitButton.startup();
//Greet the user.
on(submitButton, "click", function(evt){
console.log("Hi there, " + firstNameTextBox.get("value"));
});
}
};
});
Im creating the below MVC view that has got some jquery script in it.
However this script is not getting executed. Getting jQuery undefined error.
I want to write including script directly in view instead of using layout page.
Can somebody advise what am I doing wrong here?
#{
ViewBag.Title = "FileUpload";
}
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title - What up boyeez!</title>
<meta name="viewport" content="width=device-width" />
<script src="~/Scripts/jquery-ui-1.10.0.min.js"></script>
<h2>FileUpload</h2>
#using (Html.BeginForm("UploadFile", "FileUpload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary();
<ol>
<li class="lifile">
<input type="file" id="fileToUpload" name="file" />
<span class="field-validation-error" id="spanfile"></span>
</li>
</ol>
<input type="submit" id="btnSubmit" value="Upload" />
}
</body>
</html>
<script type="text/jscript">
(function ($) {
function GetFileSize(fileid) {
try {
var fileSize = 0;
if ($.browser.msie) {
var objFSO = new ActiveXObject("Scripting.FileSystemObject");
var filePath = $("#" + fileid)[0].value;
var objFile = objFSO.getFile(filePath);
var fileSize = objFile.size;
fileSize = fileSize / 1048576;
}
else {
fileSize = $("#", fileid)[0].files[0].size
fileSize = file / 1048576;
}
return fileSize;
}
catch (e) {
alter("Error is: " + e);
}
}
function getNameFromPath(strFilepath) {
debugger;
var objRE = new RegExp(/([^\/\\]+)$/);
var strName = objRE.exec(strFilepath);
if (strName == null) {
return null;
}
else {
return strName[0];
}
}
$("#btnSubmit").live("click", function () {
debugger;
if ($('#fileToUpload').val == "") {
$("#spanfile").html("Please upload file");
return false;
}
else {
return checkfile();
}
});
function checkfile() {
var file = getNameFromPath($("#fileToUpload").val());
if (file != null) {
var extension = file.subst((file.last('.') + 1));
switch (extension) {
case 'jpg':
case 'png':
case 'gif':
case 'pdf':
flag = true;
break;
default:
flag = false;
}
}
if (flag == false) {
$("#spanfile").text("You can upload only jpg, png, gif, pdf extension file");
return false;
}
else {
var size = GetFileSize('fileToUpload');
if (size > 3) {
$("#spanfile").text("You can upload file up to 3 MB");
return false;
}
else {
$("#spanfile").text("");
}
}
}
$(function () {
debugger;
$("#fileToUpload").change(function () {
checkfile();
});
});
})(jQuery);
You are missing a reference to jquery itself. You probably also want a css file for jquery ui:
<link rel="stylesheet" href="css/themename/jquery-ui.custom.css" />
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.custom.min.js"></script>
See the "Basic Overview: Using jQuery UI on a Web Page" section on the jquery-ui learning docs for full details of how to use and customise jquery ui.
Razor techniques for jquery files
To make your life easier in your view template, you could use the scripts render function:
#Scripts.Render("~/Scripts/jquery-1.9.1.min.js")
#Scripts.Render("~/Scripts/jquery-ui-1.10.0.min.js")
In itself, not too impressive: the syntax is slightly more expressive and 5 characters shorter, that's all.
But it leads you into bundles (references at the end), which are really what you should be using.
Bundles are awesome
Bundles allow you to:
Group dependent files: grouping js and/or css files together reduces the chances of this happening, and also means you can "modularise" your own scripts into multiple files in one folder.
Increase performance: Send out everything inside a single Bundle in a single file - speeding up load times for clients by reducing the number of http requests from the browser
Help development: Use non-minified javascripts (and css) for debugging during development
Publish without changes to code: Use the minified scripts for live deployment
Use in-built minifying for your own scripts
Optimise client experience: Use CDNs for standard scripts like jquery (which is better for your users)
Upgrade easily: Not have to change code when you update your version numbers for things like jquery through NuGet by use of the {version} wildcard (as below)
Example:
// This is usually in your MVC 4 App_Start folder at App_Start\BundleConfig
public class BundleConfig {
public static void RegisterBundles(BundleCollection bundles) {
// Example with full use of CDNs in release builds
var jqueryCdnPath = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js";
bundles.Add(new ScriptBundle("~/bundles/jquery", jqueryCdnPath).Include("~/Scripts/jquery-{version}.js"));
And in your razor file you only need a tiny change:
#Scripts.Render("~/bundles/jquery");
This will:
send out the full jquery script during debugging
send out the minified script for a release build
even minify your own bundles such as #Scripts.Render("~/bundles/myScripts"); for live builds
Bundle details
Under the hood bundles will use the CDNs, or minify your own scripts as well, or send already minified files (like jquery-1.9.1.min.js) during release builds, but you can control this by using bundles.UseCdn and BundleTable.EnableOptimizations inside your RegisterBundles method. By using this along with AppSettings in your web.config you can have very close control so that you could even send out debugging scripts for certain users on a live site.
Also note the use of {version} in the bundle configuration.
You can include multiple scripts in a bundle as well:
bundles.Add(new ScriptBundle("~/bundles/jqueryWithUi")
.Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-ui-{version}.js"
));
This razor command will now send out both files for you:
#Scripts.Render("~/bundles/jquery");
And you can use bundles for css:
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
References:
www.asp.net - Bundling and Minification
You're loading the jQuery UI library without loading the jQuery library.
<script src="~/Scripts/path/to/jquery"></script
<script src="~/Scripts/jquery-ui-1.10.0.min.js"></script
I was having the same problem of client side validation not working. I brought up the JavaScript console in Chrome and saw I was receiving an error stating "JQuery was not defined.".
Turns out I had some code in my View that was causing problems with jQuery not loading.
Recommendation to others who come across this, check your JS console in your browser to ensure you are not getting a JQuery error.