Hey Cortana, do you speak JavaScript?

In this article I will try to describe every steps to use Cortana with your JavaScript UWP app.

The first step is to create an xml file that represents the Voice Command Definition :

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="fr-fr" Name="VDM_fr-fr">
    <CommandPrefix> VDM, </CommandPrefix>
    <Example> Affiche les dernières VDM </Example>
    
    <Command Name="showlast">
      <Example> Affiche les dernières VDM </Example>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Affiche [les] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Affiche [mes] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> Ouvre [les] dernières </ListenFor>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> montre [moi] [les] dernières </ListenFor>
      <Feedback> affichage des dernières VDM </Feedback>
      <Navigate />
    </Command>
    <Command Name="showcategorie">
      <Example> Affiche les VDM de travail</Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> ouvre les {builtin:AppName} [de] {cat}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> affiche les {builtin:AppName} [de] {cat}</ListenFor>
      <Feedback> ouverture des VDM de {cat}</Feedback>
      <Navigate />
    </Command>

    <PhraseList Label="cat">
    </PhraseList>
  </CommandSet>
  <CommandSet xml:lang="en-us" Name="VDM_en-us">
    <CommandPrefix> FML </CommandPrefix>
    <Example> Show me the latest </Example>

    <Command Name="showlast">
      <Example> Show me the latest </Example>
      <ListenFor RequireAppName="AfterPhrase"> show [me] the latest </ListenFor>
      <ListenFor RequireAppName="AfterPhrase"> open the latest </ListenFor>
      <ListenFor RequireAppName="AfterPhrase"> display the latest </ListenFor>
      <Feedback>  display of the last FML</Feedback>
      <Navigate />
    </Command>

    <Command Name="showcategorie">
      <Example> Displays the FML of love </Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> Opens the  {builtin:AppName} [of] {cat}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> Displays the {builtin:AppName} [of] {cat}</ListenFor>
      <Feedback> opening FML of {cat}</Feedback>
      <Navigate />
    </Command>

    <PhraseList Label="cat">
    </PhraseList>
  </CommandSet>

</VoiceCommands>
  • In this file the root element is the VoiceCommands Element, it’s contains a list of commandSet elements. Each commandSet is for a language.
    • An commandSet contains a list of command (and others things …)
      • A command is a “command” and contains an example, and multiple elements of ListenFor, a feedback element, and an instruction element (navigate in the first sample) that explicitly specifies that this command will navigate to your app.
        • ListenFor is the command phrase, it has a RequireAppName attribute that specifies where the app name can appear in the voice command.
      • A PhraseList that contains multiple item, each Item specifies a word or phrase that can be recognized to initiate the command that references the PhraseList (optional). You can optionnaly load dynamically a list of items from your code.

You have to be very careful, when you write this file! If you don’t respect the structure or If you miss an element, the Cortana API will fall in error without any information*.

The final step.
Instantiate your new XML file of VCD, in your JavaScript code.

In this following code, I call initCortana function to initialize the VCD file using de VoiceCommandDefinitionManager API located in the Windows.ApplicationModel.VoiceCommands namespace.

You have to pass your xml file to the « installCommandDefinitionsFromStorageFileAsync » function. If everything it’s not OK the callback of the promise returns a error and you pass by:

console.error(‘error file vdmvoicecommands.xml’, er);

In this case: check and re check an re re re check your VCD file 🙂

If the file was initialized correctly, you could add a list of items to populate the phrase lists.



    var wap = Windows.ApplicationModel.Package;
    var voiceCommandManager = Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager;

    var initCortana = function (categories) {
        categories = categories || [];
        return wap.current.installedLocation.getFileAsync("vdmvoicecommands.xml").then(function (file) {
            return voiceCommandManager.installCommandDefinitionsFromStorageFileAsync(file);
        }, function (er) {
            console.error('error file vdmvoicecommands.xml', er);
        }).then(function () {
           var language = window.navigator.userLanguage || window.navigator.language;

            var commandSetName = "VDM_" + language.toLowerCase();
            var commansets = Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.installedCommandDefinitions;
            if (commansets.hasKey(commandSetName)) {
                var vcd = commansets.lookup(commandSetName);
                var phraseList = [];
                categories.forEach(function (c) {
                    phraseList.push(c.title);
                });
                return vcd.setPhraseListAsync("cat", phraseList).then(function () {
                    console.log("VCD loaded !");
                 }, function (er) {
                    console.error('error set phraseList', er);
                })
            } else {
                console.warning("VCD not installed yet?");
            }
        }, function (ee) {
            console.error("installCommandDefinitionsFromStorageFileAsync error", ee);
        });
    }

Now you have to handle the activation event that will get sent to your app, and parse arguments.

  app.addEventListener("activated", function (args) {
        var appLaunchVoiceCommand = activation.ActivationKind.voiceCommand || 16;
        if (args.detail.kind === appLaunchVoiceCommand) {
            return handleVoiceCommand(args);
        }
    });

The handleVoiceCommand function parse the args passed from the activated app event and do the navigation to the right place

    var app = WinJS.Application;
    var commands = {
        "showlast": function (commandresult) {
            return WinJS.Navigation.navigate(VDM.Pages.VDMList, { viewType: 'last', viewLabel: WinJS.Resources.getString('appbar_views_last') });
        },
        "showcategorie": function (commandresult) {
            var categorie = commandresult.semanticInterpretation.properties.cat[0];
            return WinJS.Navigation.navigate(VDM.Pages.VDMList, { viewType: categorie.toLowerCase(), viewLabel: categorie });
        }
    }
    var handleVoiceCommand = function(args) {
        if (args.detail && args.detail.detail) {
            var voicecommand = args.detail.detail[0];
            if (voicecommand) {
                var result = voicecommand.result;

                if (result) {
                    var commandname = result.rulePath ? result.rulePath[0] : '';
                    var properties = result.semanticInterpretation ? result.semanticInterpretation.properties : {};
                    console.log("voice activation (" + commandname + ") confidence: " + result.rawConfidence, result);
                    var cmd = commands[commandname];
                    if (cmd) {
                        return cmd(result).then(function () {
                            VDM.Utils.loadMainAds();
                        });
                    }
                }

            }
        }
    }
    app.addEventListener("activated", function (args) {
        var appLaunchVoiceCommand = activation.ActivationKind.voiceCommand || 16;
        if (args.detail.kind === appLaunchVoiceCommand) {
            return handleVoiceCommand(args);
        }
    });

Let’s talk more with the app (with appService)

If you need a deeper integration with Cortana, you could also « talk » with her using an app service.

An app service is a Background task that Cortana could call when you use a command. You will have to explicitely declare which service Cortana must call in your command file.

    <Command Name="getFML">
      <Example> tell me a FML </Example>
      <ListenFor RequireAppName="ExplicitlySpecified"> tell me a {builtin:AppName} </ListenFor>
      <Feedback> Here an FML </Feedback>
      <VoiceCommandService Target="FMLVoiceCommandService"/>
    </Command>

Now let’s implement the Application Service. You must add it to your application manifest by pointing to JavaScript file, and give it the name you use in the command file :

 <uap:Extension Category="windows.personalAssistantLaunch"/>
 <uap:Extension Category="windows.appService" StartPage="js/voiceCommandService.js">
    <uap:AppService Name="FMLVoiceCommandService"/>
 </uap:Extension>

Beware the visual studio appxmanifest editor, it removes this entry if anything changes in it (like a upgrade of version when you generate a new appx package) this bug will certainly be corrected in the update 1 of Visual Studio.

Now let’s create the javascript file and implement the service itself.

When you are using JavaScript App Services are a lot like independant web workers. You can import all the JS file you need to run your code by using importScripts

importScripts("/WinJS/js/base.js");
importScripts("/js/FML.API.js");

The service is loaded by cortana, so when is loaded the doWork function is called.
If the Windows.UI.WebUI.WebUIBackgroundTaskInstance.current.triggerDetails is an instance of Windows.ApplicationModel.AppService.AppServiceTriggerDetails, we can get voice command used to launch this task and do things like:

  • Send an waiting response message
  • displays items
  • Send a final response message
var appService = Windows.ApplicationModel.AppService;
var backgroundTaskInstance = Windows.UI.WebUI.WebUIBackgroundTaskInstance.current;
var details = backgroundTaskInstance.triggerDetails;
var deferral = backgroundTaskInstance.getDeferral();

if (details instanceof appService.AppServiceTriggerDetails) {
    voiceServiceConnection = voiceCommands.VoiceCommandServiceConnection.fromAppServiceTriggerDetails(details);
    voiceServiceConnection.addEventListener("voiceCommandCompleted", onVoiceCommandCompleted);

    voiceServiceConnection.getVoiceCommandAsync().then(function completed(voiceCommand) {

    // here you can check the voiceCommand, call an API (or read a file) and send messages to Cortana UI

            var userMessage = new voiceCommands.VoiceCommandUserMessage();
                    userMessage.spokenMessage = "I'm Cortana and I read this awesome message";
                    userMessage.displayMessage = "I'm Cortana and I read this awesome message";
            var response = voiceCommands.VoiceCommandResponse.createResponse(userMessage);
            return voiceServiceConnection.reportSuccessAsync(response);

    });
}

The displayMessage string must not exceed 256 character

And now, with this, you can ask Cortana: « hey cortana, tell me a FML »

Les références dans les projets UWP en JavaScript

Quand on développe des applications UWP en JavaScript avec la version 2015 RC de visual studio, nous avons la possiblité d’ajouter une référence que vers un projet de la solution courante et aucune vers « l’extérieur » comme par exemple le player framework ou le sdk de bing.

Mais cela est possible via une astuce assez simple: La modification à la main du fichier jsproj et l’ajout de la référence.

Ici par exemple, j’ajoute une référence vers le SDK de bing Maps

  <ItemGroup>
    <SDKReference Include="Bing.Maps.JavaScript, Version=1.313.0825.1" />
  </ItemGroup>

Le XML complet du jsproj:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|AnyCPU">
      <Configuration>Debug</Configuration>
      <Platform>AnyCPU</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|ARM">
      <Configuration>Debug</Configuration>
      <Platform>ARM</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x86">
      <Configuration>Debug</Configuration>
      <Platform>x86</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|AnyCPU">
      <Configuration>Release</Configuration>
      <Platform>AnyCPU</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|ARM">
      <Configuration>Release</Configuration>
      <Platform>ARM</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x86">
      <Configuration>Release</Configuration>
      <Platform>x86</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <ProjectGuid>4126a496-99c2-4185-b2ac-ad2d4c8b47a0</ProjectGuid>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0'">
    <VisualStudioVersion>14.0</VisualStudioVersion>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\$(WMSJSProjectDirectory)\Microsoft.VisualStudio.$(WMSJSProject).Default.props" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\$(WMSJSProjectDirectory)\Microsoft.VisualStudio.$(WMSJSProject).props" />
  <PropertyGroup>
    <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
    <TargetPlatformVersion>10.0.10069.0</TargetPlatformVersion>
    <TargetPlatformMinVersion>10.0.10069.0</TargetPlatformMinVersion>
    <MinimumVisualStudioVersion>$(VersionNumberMajor).$(VersionNumberMinor)</MinimumVisualStudioVersion>
    <DefaultLanguage>en-US</DefaultLanguage>
    
    <PackageCertificateKeyFile>App4_TemporaryKey.pfx</PackageCertificateKeyFile>
    
  </PropertyGroup>
  <ItemGroup>
    <AppxManifest Include="package.appxmanifest">
      <SubType>Designer</SubType>
    </AppxManifest>
    <Content Include="default.html" />
    <Content Include="images\logo.scale-100.png" />
    <Content Include="images\smalllogo.scale-100.png" />
    <Content Include="images\splashscreen.scale-100.png" />
    <Content Include="images\storelogo.scale-100.png" />
    <Content Include="js\default.js" />
    <Content Include="css\default.css" />
    <Content Include="WinJS\css\ui-dark.css" />
    <Content Include="WinJS\css\ui-light.css" />
    <Content Include="WinJS\fonts\Symbols.ttf" />
    <Content Include="WinJS\js\en-US\ui.strings.js" />
    <Content Include="WinJS\js\WinJS.js" />
    <Content Include="WinJS\js\WinJS.intellisense.js" />
    <Content Include="WinJS\js\WinJS.intellisense-setup.js" />   
    
    <None Include="App4_TemporaryKey.pfx" />
    
  </ItemGroup>
  <ItemGroup>
    <SDKReference Include="Bing.Maps.JavaScript, Version=1.313.0825.1" />
  </ItemGroup>
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\$(WMSJSProjectDirectory)\Microsoft.VisualStudio.$(WMSJSProject).targets" />
  <!-- To modify your build process, add your task inside one of the targets below then uncomment
       that target and the DisableFastUpToDateCheck PropertyGroup. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  <PropertyGroup>
    <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
  </PropertyGroup>
  -->
</Project>

Bouton précédent (Back Button) avec le System Navigation Manager dans les applications (UWP) Windows 10

Dans les nouvelles applications Windows 10, nous avons la possibilité dans la version desktop d’une application d’utiliser le bouton de navigation (bouton précédent) du système qui est placé à coté du nom de l’application dans la barre de titre (comme celui qu’on trouve dans l’application de paramètres:

back

Ce bouton est nativement géré par WinJSContrib, il suffit pour cela d’activer l’option « enableSystemBackButton » :

 WinJSContrib.UI.enableSystemBackButton = true;

Un sample est disponible ici 🙂

Vous pouvez aussi l’implémenter manuellement. Il suffit d’utiliser la classe SystemNavigationManager et de modifier la propriété appViewBackButtonVisibility pour afficher ou cacher le bouton.

// il faut vérifier la disponibilité de l'api (disponible que sur desktop)
if (window.Windows && window.Windows.UI && Windows.UI.Core && Windows.UI.Core.SystemNavigationManager) {
    Windows.UI.Core.SystemNavigationManager.getForCurrentView().appViewBackButtonVisibility = Windows.UI.Core.AppViewBackButtonVisibility.visible;
    // ou pour cacher le bouton
    Windows.UI.Core.SystemNavigationManager.getForCurrentView().appViewBackButtonVisibility = Windows.UI.Core.AppViewBackButtonVisibility.collapsed;
}

Et pour gérer le clique il suffit de se brancher sur l’événement onbackrequested:

if (window.Windows && window.Windows.UI && Windows.UI.Core && Windows.UI.Core.SystemNavigationManager) {
    var systemNavigationManager = Windows.UI.Core.SystemNavigationManager.getForCurrentView();
    systemNavigationManager.onbackrequested = function (args) {
        if (WinJS.Navigation.canGoBack) {
            WinJS.Navigation.back();
            args.handled = true;
        } else {
            systemNavigationManager.appViewBackButtonVisibility = Windows.UI.Core.AppViewBackButtonVisibility.visible;
        }
    }
}

Pour la version en C# (et ma source) c’est par ici

How to use WinJS resources with a Cordova App

If you need to use WinJS resources in a cordova app, you have to load them manually and that is very simple !

You only have to read the ‘good’ resjson (the file that matches your language) before you call WinJS.UI.processAll  and set the global « strings » variable.

In our apps we use this method to load resources:

var loadLocalRessources = function (language) {
   language = language || 'en';
    return new WinJS.Promise(function (complete, error) {
        var url = './strings/' + language + '/resources.resjson';
        console.log('trying to load resources from ' + url);

        WinJS.xhr({ url: url }).then(function (res) {
            console.log('resource ok for ' + language);
            try {
                window.strings = JSON.parse(res.responseText);
            } catch (exception) {
                console.error('resource parse error ' + exception.message);
            }
            complete(window.strings);
        }, function (err) {
            console.error('resource load error for ' + language + ' ' + err.statusText);
            if (language != 'en') {
                data.loadLocalRessources('en').then(complete, error);
            }
            else {
                error(err);
            }
        });
    });

}

Débuguer une Universal Windows app et d’une application Cordova dans Visual Studio

Petit rappel : Vu l’architecture du projet (Applications natives en Universal App + application cordova) il faut bien penser à ajouter les fichiers copiés depuis le shared project en as link dans l’application cordova. C’est le seule moyen de débuguer/modifier le bon fichier.

 

Dans les applications universelles aucune configuration spéciale n’est nécessaire, Visual Studio gère parfaitement le debug. Il suffit de lancer l’application en mode debug et avoir accès dès le lancement au dom ainsi qu’aux points d’arrêts. Les choses se compliquent légèrement avec les applications cordova. En effet Visual studio met plusieurs secondes (voir minutes) avant d’attacher correctement et d’une façon stable le debugger. Et si on souhaite débuguer les premières lignes qui s’exécutent lors du lancement de l’application, cela devient rapidement compliqué, voir impossible.

Deux solutions existent :

  • La première (mais qui ne marche que si l’application s’initialise correctement = pas de crash au démarrage) c’est simplement via la console JavaScript de Visual Studio d’appeler la méthode window.location.reload() la page index.html est rechargée = les scripts se ré exécutent et on a donc accès aux debug des premières lignes de code.
  • La deuxième c’est de créer un mode debug avec une étape intermédiaire qui consistera à avoir un fichier index.html qui charge le stricte minimum de fichiers qui contient un lien (button) qui va nous permettre de naviguer vers une deuxième page qui va charger le reste des fichiers. Avec cette formule on s’assure du démarrage de l’application, on peut attendre que le debugger Visual Studio soit correctement attaché pour pourvoir charger les autres fichiers.

 

Le débogage/déploiement sous iOS requière un mac et l’installation des outils de build:

  • (Mac) Depuis le terminal lancer la commande désinstallation: sudo npm install -g vs-mda-remote –user=$USER ($user = l’utilisateur courant)
  • (Mac) Après l’installation lancer la commande: vs-mda-remote –secure false
  • Configurer Visual Sutdio: Tools => Options => Tools for Apache Cordova => remote Agent configuration:
    • Enable remote iOS processing = True
    • Host : adresse ip du mac de build.

Le déploiement peut se faire avec le simulateur ou un device.

Avec ces quelques notes vous avez maintenant les outils pour pourvoir débugger facilement votre nouvelle application.

Universal Windows app et Cordova avec WinJS

L’architecture que je conseil pour cibler les trois (quatres) plateformes, c’est de partir principalement d’un projet natif Universal app (Windows 8.1/ Windows Phone) en HTML5 et de créer un projet Cordova à l’aide du plugin Visual studio.

Ensuite pour brancher l’application Cordova avec le projet shared, il faut écrire un script (robocopy) à exécuter avant le build pour copier les fichiers du projet shared vers le projet cordova:

(robocopy ..\UniversalApp.Shared\ . /E /XF UniversalApp.Shared.*)^& IF %ERRORLEVEL% LEQ 1 exit 0

Et modifier le fichier de projet (jsproj) pour lancer cette exécution:

<Target Name= »BeforeBuild »>
<Message Text= »### BeforeBuild ### » Importance= »high » />
<Exec Command= »prebuild.bat » />
</Target>

Mais pour pouvoir débugguer les fichiers copiers, il faut aussi créer la structure des dossiers et ajouter les fichiers en as link.

Pour ajouter WinJS à vos projets vous pouvez passer par nuget (qui ne marche pas avec les projets shared pour le moment) ou récupérer directement la dernière version stable disponible depuis le repo github. Vous pourrez ensuite ajouter les fichiers WinJS.js, css ui-light.css ou ui-dark .css ainsi que la typo Symbols.ttf dans le dossier scripts du projet shared (cette version de WinJS sera aussi utilisée avec les Universel Apps)

Enfin il reste à inclure les références dans les fichiers default.html et index.html. Votre projet est ainsi prêt et on peut lancer le développement de notre application cross-plateforme.

Note importante: l’initialisation de l’application est différente en WinRT et en Cordova. Sous Windows on va conserver le fichier default.js, et pour le projet Cordova, on va modifier le fichier équivalent, index.js, pour initialiser correctement notre application.

Un exemple de solution est disponible ici

WinJS dans une application cross plateforme Cordova

Depuis avril 2014, WinJS le Framework SPA de Microsoft est devenu libre (disponible sur github) et  surtout cross-platform.
C’est avec ces nouvelles possibilités qu’on peut maintenant l’utiliser sur d’autres plateformes que Windows 8/Phone 8.1.

Dans cette série de posts, je vais donc aborder le développement multiplateforme d’application avec WinJS.

Windows 10: The Next Chapter

terry_myerson_silhouette_web

 

Petit récapitulatif des annonces :

  • Windows 10 gratuit pour ceux qui possèdent une licence Win 7 (Oui 7 !) et supérieur (la première année, formule avec abonnement ?)
  • L’intégration de Cortana au cœur de l’OS.
  • Plusieurs apps universel (office aussi) .
  • Nouveau navigateur, nom de code : Spartan
  • Une nouvelle appli Xbox Windows 10, la possibilité de streamer (et donc jouer) les jeux Xbox one sur PC.
  • Petite démo de DirectX12

Les surprises de la keynote:

  • Surface Hub : un écran 84’’ 4K qui tourne avec Windows 10. Vidéo de présentation:
  • Microsoft HoloLens (waaaaw !) . Vidéo de présentation:

La nouvelle release de Windows 10 sera disponible la semaine prochaine. Pour les téléphone il faut attendre février.

Pour revoir la keynote c’est par ici => http://news.microsoft.com/windows10story/

Mehdi