2019-08-05

Python dans Power BI, pourquoi faire ?

L'ajout du visuel Python dans Power BI date de quelques mois déjà... Je vous propose un cas concret justifiant d'y passer quelques heures.

On peut effectivement se poser la question de la pertinence de ces visuels tant les possibilités en termes de traitement (Power Query) et restitution ont drastiquement évolué sur les dernières versions de Power BI. Alors je vous propose un cas concret pour résoudre en quelques heures une problématique qui nécessiterait autrement le développement d'un visuel spécifique.

La problématique


Il est facile de créer un histogramme dont la couleur des barres est soumise à un affichage conditionnel. C'est en revanche impossible à partir du moment où plusieurs séries sont présentes.

La solution

Dans ce cas, l'utilisation du visuel Python permet d'obtenir le résultat attendu :

Préalablement, il est bien entendu indispensable d'avoir installé Python et d'activer les scripts dans les options. Pour déployer matplotlib :
pip install -U matplotlib
Une fois l'environnement prêt, il est temps de configurer le visuel.
Importer le module :
from pylab import *
Créer la fonction permettant de déterminer la couleur de la barre d'histogramme :
def getcolor(data, typecode, mini, maxi):
if typecode == "c":
return (0.9 * ((maxi - data)/(maxi - mini)), 0.8, 0.3 * ((maxi - data)/(maxi - mini)))
elif typecode == "d":
return (0.75 * ((maxi - data)/(maxi - mini)), 0.75 * ((maxi - data)/(maxi - mini)), 0.8)
else:
return (0.8, 0.75 * ((maxi - data)/(maxi - mini)), 0.75 * ((maxi - data)/(maxi - mini)))

 

Gérer les données. Première série, avec affectation de la couleur via la fonction :
groupedSet = dataset.where(dataset.Yogurt == "Chobani Total").groupby("Date").Prix.sum()
colorSet = [getcolor(data, "c", groupedSet.min(), groupedSet.max()) for data in groupedSet]
plt.bar(groupedSet.index, groupedSet, color = colorSet)


Seconde série, avec l'attribut bottom permettant de gérer l'empilement :
groupedSetAlt = dataset.where(dataset.Yogurt == "Danone Total").groupby("Date").Prix.sum()
colorSet = [getcolor(data, "d", groupedSetAlt.min(), groupedSetAlt.max()) for data in groupedSetAlt]
plt.bar(groupedSetAlt.index, groupedSetAlt, color = colorSet, bottom = groupedSet)


Gestion de l'axe des abscisses :
plt.xticks(groupedSet.index[::4], [i[0:4]+"-"+i[5:7]+"-"+i[8:10] for i in groupedSet.index[::4]])
 

Et affichage :
plt.show()
Et voilà !

J'ai évidemment simplifié mon code pour ne gérer que ces 2 séries avec un filtrage "en dur", libre à vous d'imbriquer une boucle pour traiter un nombre variable de séries...

2016-02-04

Perdus / Trouvés – Les chemins et URL SharePoint 2013

Encore un post à propos des trucs que je cherche souvent… Je l’agrémenterai au fur et à mesure…

Chemins

Suivi de synchro FIM : C:\Program Files\Microsoft Office Servers\15.0\Synchronization Service\UIShell\miisclient.exe

URL

/_layouts/viewlsts.aspx –> Tout le contenu du site.

/_layouts/settings.aspx –> Paramètres du site

/_layouts/searchresults.aspx?k=Truc –> Recherche

/_layouts/closeConnection.aspx?loginasanotheruser=true –> Se connecter en tant que…

/_layouts/savetmpl.aspx –> Sauvegarder le site en tant que Template (utile après avoir activé l’infrastructure de publication)

/_layouts/ChangeSiteMasterPage.aspx –> Accès aux pages maitres et à l’alternate CSS.

2016-01-04

SharePoint Online - 404 Not Found - https://static.sharepointonline.com/…

Ô rage ! Ô désespoir ! Ô vieillesse ennemie… Après des heures de combat acharné, impossible de faire fonctionner ce satané JSLink dans SharePoint Online. Il est pourtant vérifié (16 fois, on n’est jamais trop prudent), relu, testé, réduit à sa portion congrue… Fi, la console continue de cracher son sibyllin message :
404 Not Found - https://static.sharepointonline.com/bld/_layouts/15/ 16.0.4719.1219/

Toutes les combinaisons de chemin relatif y passent : silence radio. Dès que l’URL complète du fichier est fournie… Saccage du moteur qui tente de le récupérer dans les ressources serveur, sans succès évidemment.
Et si ? Oui ! La solution est d’utiliser l’alias de la collection de sites :
~sitecollection/…/MonFichier.js
Et paf ! Ca fait ce qui est attendu ! (En l’occurrence ici, un champ Chocapic).

2015-09-02

Helper – Loopback check

Parce qu’on en a besoin pour les serveurs de dev, de prod, de tout, le paramétrage du loopback check est un incontournable.

Serveur de DEV

On désactive directement en PowerShell :
New-ItemProperty HKLM:\System\CurrentControlSet\Control\Lsa
 -Name "DisableLoopbackCheck" -value "1" -PropertyType dword

Serveur de PROD


A venir…

2015-07-16

Visual Studio–Expressions régulières

Plus pour moi-même, pour rappeler un groupe de capture dans la recherche avec Expressions Régulières de Visual Studio, c’est $1… qu’il faut utiliser. Là où les expressions typées Unix utiliseront le backslash.

image

2015-05-02

Analytics avec SharePoint 2013 – La fin des souffrances ?

Si vous avez comme moi des clients SharePoint 2013 dans le marketing et la communication (interne ou externe)… Vous avez forcément subi les foudres des décideurs lorsque vous avez présenté l’état des lieux des solutions de statistiques de consultation, avec insulte compte triple si vos interlocuteurs étaient aguerris à SharePoint 2010 ou 2007.

Analytics n’existe pas sous SharePoint 2013 ? Mais ils étaient presque corrects sous 2010 !

Ces 2 excels ne me suffisent pas !

Même google le fait gratuitement ! (Là-dessus, nonobstant la qualité des productions de Mountain View, j’aurais je l’espère l’occasion de revenir sur leur “gratuité” et leur “perfection”)

Et bien, vous voici avec une carte “cloud” à jouer, une pierre, deux coups : Préparez le terrain de l’hybride ou consolidez un abonnement existant en utilisant la brique Azure “Application Insights” dans vos pages maitres SharePoint :

http://azure.microsoft.com/blog/2015/04/22/understand-your-sharepoint-usage-with-application-insights-2/

2015-03-13

SharePoint - La recherche d’utilisateurs est toujours infructueuse

Votre recherche est fonctionnelle pour tous vos documents, vidéos et autres contenus… Mais d’utilisateurs point ! Votre belle démonstration d’annuaire basé sur la recherche tombe allo (Annuaire, allo… désolé.)

Vérifiez donc votre crawl log. Il peut contenir l’erreur suivante sur le STS :

Error in the Microsoft SharePoint Server People Protocol Handler. ( Object reference not set to an instance of an object.; SearchID = F9CC7134-A517-4E17-A81A-7419B5572B6D )

Capture_20150313_143013

Dans ce cas, vérifiez qu’une collection de sites racine existe à la base de votre application web. Dans mon cas, il y avait les collections de sites : http://devXXX.tomain.fr/sites/Coll1 et http://devXXX.tomain.fr/sites/Coll2. En ajoutant la collection http://devXXX.tomain.fr/ (un team site sans contenu ni droit), puis en lançant un nouveau crawl, mes utilisateurs sont désormais disponibles dans la recherche !

La recherche dans “ce site” ne renvoie rien alors que la recherche globale fonctionne…

Après vos jonglages sur cette GED intranet, paramétrage aux petits oignons des AAM et une mise en prod de haut vol, impossible de retrouver un quelconque document depuis la boite de recherche “ce site” / “this site” du coin supérieur droit de vos pages… Et pourtant, en étendant la recherche ou en recherchant dans “tout” / “All”, avec les mêmes critères, des documents apparaissent !

Quelle que soit votre version de SharePoint, cet article est en effet toujours valable : http://support.microsoft.com/fr-fr/kb/2000365 : La recherche “this site” ne retourne rien si le chemin crawlé n’est pas celui “Par défaut” de vos AAM. Une simple permutation à chaud des mappages et tout rentre dans l’ordre.

2015-03-10

SharePoint dans Azure–Paramétrer son DNS / son AD

Après avoir déployé votre ferme SharePoint dans Azure avec l’assistant, vous voici bien perdu sur le contrôleur de domaine : De console de paramétrage de DNS point, alors que vous êtes admin… Palsambleu !
Tenter de l’activer via l’ajout de features ne vous mènera pas très loin… Mais la piste du salut débute bien là.
Ouvrez une invite de commandes en tant qu’administrateur et entrez cette instruction :
dism /online /Enable-Feature /FeatureName:dns-server-tools
Et PAF, voici votre console ! (Merci Sylvain, pour la commande :) )

Capture_20150310_153629 

Et pour l'annuaire :
dism /online /Enable-Feature /FeatureName:RSAT-ADDS-Tools-Feature

dism /online /Enable-Feature /FeatureName:DirectoryServices-DomainController-Tools

2014-12-27

Perdus / Trouvés – Les chemins et URL SharePoint 2013

Encore un post à propos des trucs que je cherche souvent… Je l’agrémenterai au fur et à mesure…

Chemins

Suivi de synchro FIM : C:\Program Files\Microsoft Office Servers\15.0\Synchronization Service\UIShell\miisclient.exe

URL

/_layouts/viewlsts.aspx –> Tout le contenu du site.

/_layouts/settings.aspx –> Paramètres du site

/_layouts/searchresults.aspx?k=Truc –> Recherche

/_layouts/closeConnection.aspx?loginasanotheruser=true –> Se connecter en tant que…

2014-12-23

Accès anonyme sous SharePoint 2013 – Accéder aux listes

Vous avez activé l’accès anonyme à la Web App et à tout le site, malheureusement, impossible pour un utilisateur non authentifié d’accéder à une simple bibliothèque de documents alors même que la page d’accueil du site est parfaitement fonctionnelle…
Capture20141223_144306
Il vous manque une information, surtout si vous venez du monde SharePoint 2010 : une fonctionnalité de collection de sites désormais activée par activée par défaut est la source de vos malheurs : le verrouillage de l’accès aux formulaires pour les lecteurs restreints !
Capture20141223_144622
Si vous désactivez cette fonctionnalité, vos lecteurs anonymes auront de nouveau accès aux listes et bibliothèques… Mais également à la page “tout le contenu du site” et d’autres formulaires qu’on aimerait pouvoir cacher ! Alors, il nous faut changer les usages. Conservons la fonctionnalité sécuritaire activée et tentons quelques manœuvres.

Concevoir un portail pour l’accès anonyme

Nos pistes sont les suivantes : Les listes et bibliothèques sont inaccessibles sans fournir une identité valide (Et le log IIS présente la 401 sur la page aspx de l’affichage concerné) mais la page d’accueil du site l’est, même si elle n’affiche pas les liens vers les-dites listes. Testons les éléments suivants :
  • Utilisation d’un lien direct vers un document d’une bibliothèque (sur laquelle les utilisateurs anonymes ont des droits de lecture restreinte) : OK, ça fonctionne
  • Accès à un affichage de liste / bibliothèque : Impossible
  • Affichage d’une page de site : OK
  • Affichage d’une page de site contenant un lien vers un doc de liste ou une webpart d’affichage de liste : OK !
Donc on va favoriser des scénarios de restitution par page de site.
image
Préparons une page de site présentant simplement une vue d’une liste : Nous avons notre affichage de liste. En plus, on bénéficie du contrôle fin des affichages car l’utilisateur anonyme ne peut voir que ceux que nous lui mettons à disposition.
image
Préparons ensuite nos pages de contenu. Comme ici, ma page d’accueil, avec des voues vers des listes et bibliothèques, du contenu, des liens… (Remarquez que le menu de gauche présente pour un utilisateur authentifié des listes et bibliothèques du site) L’utilisateur anonyme dispose d’un rendu correct :
image
Et bien sûr, il ne peut pas accéder aux autre affichages des listes rendues dans la page :
image

En conclusion

A l’instar d’un site de publication, favorisez le rendu par pages et non par menus et listes pour les sites à vocation d’accès anonyme !

2014-10-17

SQL Server 2008 R2–Report Builder n’aime pas monter du SP2 vers le SP3…

Il est possible que votre Report Builder n’apprécie pas votre mise à jour. Si c’est le cas, vous pouvez désinstaller vos SP 3 et 2 puis réinstaller le SP3 directement sur votre partie serveur. Sinon, une réinstallation des systèmes Client est possible.

Les détails : http://blogs.msdn.com/b/sqlreleaseservices/archive/2014/10/16/report-builder-of-sql-server-2008-r2-service-pack-3-does-not-launch.aspx

2013-07-11

Les Display Templates par le menu… (1/2)

Ca fait un bout de temps que je n’ai pas publié. Réparons l’outrage. Aujourd’hui, on se coltine les Display Templates. Une nouveauté introduite par SharePoint 2013 d’importance proportionnelle au carré du nombre de XSL que vous avez debuggé pour vos Content Query WebParts.

Là vous me dites “T’es bien intéressant Merly avec tes circonvolutions lexicales mais on vient ici pour du code, pas pour les belles lettres”… Je vous comprends, mais je pense que vous manquez quelque chose Clignement d'œilBref :

Les display templates, qu’est-ce donc ?

Il s’agit de modules de rendu d’items en JavaScript (et HTML / CSS) permettant une restitution des éléments issus des modules de recherche : Content Search Web Parts, Filtres et affineurs ainsi que la restitution de la recherche : par exemple les résultats, les extraits au survol…

On les trouve dans le catalogue des master pages. Donc soit dans les paramètres de site, soit avec SharePoint Designer :

image

image

Note : Dans SharePoint Designer, utilisez la rubrique “All Files” plutôt que “Master Pages” car le filtrage d’affichage de celui-ci ne vous permettra pas de voir les fichiers js.

La description de tous les templates fournis se trouve sur Technet : http://technet.microsoft.com/fr-fr/library/jj944947.aspx

Utilisation des display templates

Les display templates sont affectés soit par paramétrage des webparts, soit par association à des Result Types . Ici, l’exemple d’une Content Search WebPart :

image

Et les associations des result types d’un site de recherche (Paramètres de Site) :

image

Premier display template custom

Pour montrer le principe de personnalisation par Display Template, un exemple concret : Je veux réaliser un affichage au format “carte de visite” de mes éléments avec une image / miniature / icône, un titre et un descriptif.

On commence par créer une page et on insère la WebPart de Content Search. Pour tester les données, je vous conseille d’utiliser le template “Diagnostic” et de le mapper sur les propriétés que vous voulez. Affinez votre recherche jusqu’à obtenir les données souhaitées :

image

Maintenant qu’on a le paramétrage, y’a plus qu’à Sourire

Créer le template

On ne va pas se fatiguer : A l’aide de SharePoint Designer, on copie un template existant (je vous conseille le “2 lines”, facile à interpréter) et on l’adapte à notre situation après l’avoir renommé. Dissection :

   1:  // Initialisation de la collecte de statistiques (cf. http://stackoverflow.com/questions/4177001/what-does-this-javascript-code-do/7543257#7543257)
   2:  function ULS16h(){var o=new Object;o.ULSTeamName="Search Server";o.ULSFileName="Item_TwoLines.js";return o;}
   3:   
   4:  // Extraction des paramètres et génération de l'élément (Note : Remplacer le Guid par votre identifiant propre partout dans la source).
   5:  function DisplayTemplate_dee7d9226aa44ed7b709d342fec837ee(ctx) {ULS16h:;
   6:      var ms_outHtml=[];
   7:      var cachePreviousTemplateData = ctx['DisplayTemplateData'];
   8:      ctx['DisplayTemplateData'] = new Object();
   9:      DisplayTemplate_dee7d9226aa44ed7b709d342fec837ee.DisplayTemplateData = ctx['DisplayTemplateData'];
  10:      
  11:      // Initialisation des paramètres du template avant mise en cache.
  12:      ctx['DisplayTemplateData']['TemplateUrl']='~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fContent Web Parts\u002fItem_TwoLines.js';
  13:      ctx['DisplayTemplateData']['TemplateType']='Item';
  14:      ctx['DisplayTemplateData']['TargetControlType']=['Content Web Parts'];
  15:      
  16:      // Initialisation :
  17:      this.DisplayTemplateData = ctx['DisplayTemplateData'];
  18:      
  19:      // Initialisation des paramètres du tempalte avant mise en cache.
  20:      ctx['DisplayTemplateData']['ManagedPropertyMapping']={'Link URL':['Path'], 'Line 1':['Title'], 'Line 2':[], 'FileExtension':null, 'SecondaryFileExtension':null};
  21:      
  22:      var cachePreviousItemValuesFunction = ctx['ItemValues'];
  23:      ctx['ItemValues'] = function(slotOrPropName) {ULS16h:;
  24:          return Srch.ValueInfo.getCachedCtxItemValue(ctx, slotOrPropName)
  25:      };
  26:   
  27:      ms_outHtml.push('','');
  28:      
  29:      // Génération d'un ID prefixant les sous-éléments
  30:      var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + "_2lines_");
  31:   
  32:      // Récupération effective des paramètres de la WebPart "Display Template"-ée.
  33:      var linkURL = $getItemValue(ctx, "Link URL");
  34:      linkURL.overrideValueRenderer($urlHtmlEncode);
  35:      var iconURL = Srch.ContentBySearch.getIconSourceFromItem(ctx.CurrentItem);
  36:      var line1 = $getItemValue(ctx, "Line 1");
  37:      var line2 = $getItemValue(ctx, "Line 2");
  38:      line1.overrideValueRenderer($contentLineText);
  39:      line2.overrideValueRenderer($contentLineText);
  40:      
  41:      // Préparation des Id des différents éléments. Ils peuvent ainsi être adressés directement en JS par la suite.
  42:      var containerId = encodedId + "container";
  43:      var pictureLinkId = encodedId + "pictureLink";
  44:      var pictureId = encodedId + "picture";
  45:      var dataContainerId = encodedId + "dataContainer";
  46:      var line1LinkId = encodedId + "line1Link";
  47:      var line1Id = encodedId + "line1";
  48:      var line2Id = encodedId + "line2";
  49:      
  50:      // Envoi de la source générée pour le rendu.
  51:      ms_outHtml.push(''
  52:          ,'        <div class="cbs-Item" id="', containerId ,'" data-displaytemplate="Item2Lines">'
  53:          ,'            <a class="cbs-ItemLink" title="', $htmlEncode(line1.defaultValueRenderer(line1)) ,'" id="', pictureLinkId ,'">'
  54:          ,'                <img class="cbs-Thumbnail" src="', $urlHtmlEncode(iconURL) ,'" alt="', $htmlEncode(line1.defaultValueRenderer(line1)) ,'" id="', pictureId ,'" />'
  55:          ,'            </a>'
  56:          ,'            <div class="cbs-Detail" id="', dataContainerId ,'">'
  57:          ,'                <a class="cbs-Line1Link ms-noWrap ms-displayBlock" href="', linkURL ,'" title="', $htmlEncode(line1.defaultValueRenderer(line1)) ,'" id="', line1LinkId ,'">', line1 ,'</a>'
  58:      );
  59:      if(!line2.isEmpty)
  60:      {
  61:          ms_outHtml.push(''
  62:              ,'                <div class="cbs-Line2 ms-noWrap" title="', $htmlEncode(line2.defaultValueRenderer(line2)) ,'" id="', line2Id ,'">', line2 ,'</div>'
  63:          );
  64:      }
  65:      ms_outHtml.push(''
  66:          ,'                </div>'
  67:          ,'        </div>'
  68:          ,'    '
  69:      );
  70:      
  71:      // "Nettoyage" de l'objet contexte emprunté.
  72:      ctx['ItemValues'] = cachePreviousItemValuesFunction;
  73:      ctx['DisplayTemplateData'] = cachePreviousTemplateData;
  74:      return ms_outHtml.join('');
  75:  }
  76:   
  77:  // Inscription du template pour affichage (permet l'affichage. Le template reste visible mais ne fonctionnera pas si cette fonction est incorrecte).
  78:  function RegisterTemplate_dee7d9226aa44ed7b709d342fec837ee() {ULS16h:;
  79:      if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {
  80:          Srch.U.registerRenderTemplateByName("TwoLines", DisplayTemplate_dee7d9226aa44ed7b709d342fec837ee);
  81:      }
  82:   
  83:      if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {
  84:          Srch.U.registerRenderTemplateByName("~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fContent Web Parts\u002fItem_TwoLines.js", DisplayTemplate_dee7d9226aa44ed7b709d342fec837ee);
  85:      }
  86:   
  87:      $includeLanguageScript("~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fContent Web Parts\u002fItem_TwoLines.js", "~sitecollection/_catalogs/masterpage/Display Templates/Language Files/{Locale}/CustomStrings.js");
  88:  }
  89:   
  90:  RegisterTemplate_dee7d9226aa44ed7b709d342fec837ee();
  91:   
  92:  // Etape d'initialisation.
  93:  if (typeof(RegisterModuleInit) == "function" && typeof(Srch.U.replaceUrlTokens) == "function") {
  94:      RegisterModuleInit(Srch.U.replaceUrlTokens("~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fContent Web Parts\u002fItem_TwoLines.js"), RegisterTemplate_dee7d9226aa44ed7b709d342fec837ee);
  95:  }

Faisons donc une première passe d’adaptation : On remplace “TwoLines” et “2line” par des noms adaptés. et les liens / noms de fichier par ceux de notre nouveau template. Ne touchez pour l’instant pas au code généré ou aux variables récupérées.


Premier test


Sauvegardez, modifiez le Titre de l’élément pour qu’il corresponde à ce que vous souhaitez et retournez sur votre page de test. Rafraichissez et affichez les propriétés de la WebPart : Votre Template doit apparaitre dans la liste et avoir le même comportement que le template “TwoLines” existant.


imageTADA…


Et c’est le moment d’ajouter de la valeur à notre template : Modifier les paramètres et le contenu.


Modifier les paramètres


Reprenons la source du template. Modifions d’abord le mapping des propriétés :

ctx['DisplayTemplateData']['ManagedPropertyMapping']={'Link URL':['Path'], 'Thumbnail':[], 'Title Line':['Title'], 'Summary':['Description'], 'FileExtension':null, 'SecondaryFileExtension':null};









Le format est un tableau associatif de ‘Nom de Propriété déclaré’:[‘Champ associé par défaut’]


Ensuite, on modifie l’extraction des données :


   1:  var thumbnail = $getItemValue(ctx, "Thumbnail");
   2:  var titleLine = $getItemValue(ctx, "Title Line");
   3:  var summary = $getItemValue(ctx, "Summary");
   4:  thumbnail.overrideValueRenderer($contentLineText);
   5:  titleLine.overrideValueRenderer($contentLineText);
   6:  summary.overrideValueRenderer($contentLineText);




Notez que le 2e paramètre de $getItemValue est le nom de propriété déclaré. Enfin, modifiez le rendu. Par exemple, en affichant les propriétés directement :


   1:  // Rendu poussé dans ms_outHtml :
   2:  ms_outHtml.push(
   3:      '<p>linkURL : ' + linkURL + '</p>',
   4:      '<p>iconURL : ' + iconURL + '</p>',
   5:      '<p>thumbnail : ' + thumbnail + '</p>',
   6:      '<p>titleLine : ' + titleLine + '</p>',
   7:      '<p>summary : ' + summary + '</p>',
   8:      '<hr />');



Sauvegardez et dans les paramètres de site, n’oubliez pas de mettre à jour les propriétés du template et notamment “Managed Property Mappings” pour propager les propriété personnalisées.


'Link URL'{Link URL}:'Path','Thumbnail'{Thumbnail}:'','Title Line'{Item Title}:'Title','Summary'{Summary}:'','FileExtension','SecondaryFileExtension'


Attention, contrairement à ce que la documentation prétend, le découpage est ‘Nom déclaré de la propriété (cf. ci-dessus)’{Libellé affiché}:’Champ par défaut’ mais vous le verrez rapidement pendant ce test pour peu que vous preniez la peine de faire varier les valeurs… On peut désormais valider le rendu :


image


On retourne travailler le rendu et avec quelques modification du style et autres (OK, dans cet exemple j’ai fait le goret en injectant mon style directement sur les balises mais vous n’êtes pas obligé de faire moche en m’imitant Clignement d'œil) :


ms_outHtml.push(
    '<div class="cbsCardItem" id="', containerId ,'" data-displaytemplate="ItemCard" style="border: 1px solid #FFBB99;width: 500px;height: 200px;padding: 0;margin: 0;">',
    '    <a class="cbsCardLink" title="Cliquez pour afficher l\'élément" href="', $urlHtmlEncode(linkURL), '" style="color: inherit;">',
    '        <img class="cbs-Thumbnail" src="', $urlHtmlEncode((thumbnail.isEmpty) ? iconURL : thumbnail) ,'" alt="', $htmlEncode(titleLine.defaultValueRenderer(titleLine)) ,'" id="', pictureId ,'" style="height: 200px;width: auto;float: left;display: inline-block; margin-right: 5px;" />',
    '        <div class="cbs-TextContainer" id="', dataContainerId ,'" style="background-color: #99DDFF; height: 100%;">',
    '            <h1>', titleLine ,'</h1>',
    '            <p>', summary ,'</p>',
    '        </div>',
    '    </a>',
    '</div>',
    '<hr />');



Et hop, j’ai mon premier Template un tant soit peu “pêchu” :


image


Le control : un peu plus de personnalisation


Bien. Si vous m’avez suivi jusqu’ici. Déjà je vous en remercie, de plus, ça fait chaud au cœur et enfin, sachez qu’on n’a vu que 25 % du truc mais que pour le reste, ça en découle grandement.


Donc, on sait mettre en forme plutôt aisément un élément en mappant un template qu’on génère en JS sur des propriétés assignables à la volée sur une WebPart. Finissons le travail en traitant du groupe d’éléments. En effet, un détail n’aura pas échappé à votre perspicacité légendaire (ne rougissez pas, c’est sincère) :


image










Et oui, on s’est occupé de “Item”, mais pas encore de cet attribut “Control”, qui permet pour l’instant de faire une liste, paginée ou non, ou un diaporama basique et dont on retrouve trace dans le catalogue “Display Templates > Content Web Parts” : Control_List.js et consorts…


Bingo ! On s’y attèle de suite (à la différence de celle de maintien qui n’aurait sa place que sur un blog médical, loin de mon propos… Bref.) !


Scénario cible


J’ai donc un affichage en cartes, et bien, je vais les faire afficher en forme de pile avec possibilité de les consulter par survol à la souris.


Retour dans SharePoint Designer ou je duplique le template de Contrôle de liste et en fait un Control_Stack.js avec modification des propriétés comme on en a désormais l’habitude. Je vous épargne la dissection étant donné que le contenu est proche de celui de l’item à la différence majeur qu’on adresse ici l’ensemble des éléments et qu’il faut appeler les template d’items dans le push de génération.


Superposition des cartes


Première manipulation sur le contenu : Transformer la liste ul / li en un esemble de div se chevauchant. Attention, là aussi, j’ai fait vite en utilisant une variable globale qui causera un conflit si plusieurs contrôles de ce type sont utilisés sur a même page. Vous pouvez utiliser l’objet ctx pour générer un id unique et traiter ainsi proprement votre empilement.


   1:  var g_ItemCount = 0;
   2:  var ListRenderRenderWrapper = function(itemRenderResult, inCtx, tpl)
   3:  {ULS9sP:;
   4:      var iStr = [];
   5:      iStr.push('<div class="itemToPop" style="position: absolute; top: ', "" + (g_ItemCount*20), 'px; left: ', "" + (g_ItemCount*20), 'px">');
   6:      iStr.push('<!--', g_ItemCount++ , '-->');
   7:      iStr.push(itemRenderResult);
   8:      iStr.push('</div>');
   9:      return iStr.join('');
  10:  }
  11:  ctx['ItemRenderWrapper'] = ListRenderRenderWrapper;
  12:  ms_outHtml.push(''
  13:  ,'    <div class="cbs-List" style="position: relative;">'
  14:  ,''
  15:  ,'            ', ctx.RenderGroups(ctx) ,''
  16:  ,'        </div>'
  17:  );



Un petit test pour valider le comportement. Ca semble pas mal :


image



Astuce : Utilisation de jQuery dans les Display Templates


OK, à ce stade, pour gérer les survols et autres, j’aimerais bien utiliser jQuery… Qu’à cela ne tienne : J’ajoute les liens dans l’en-tête (au passage, inclusion d’une CSS personnalisée, ça ne mange pas de pain)


   1:  var script = document.createElement('script');
   2:  script.src = 'http://ajax.microsoft.com/ajax/jQuery/jquery-1.9.1.min.js';
   3:  script.type = 'text/javascript';
   4:  document.getElementsByTagName('head')[0].appendChild(script);
   5:  var link_tag = document.createElement('link');
   6:  link_tag.setAttribute('rel', 'stylesheet');
   7:  link_tag.setAttribute('type', 'text/css');
   8:  link_tag.setAttribute('href', '/_layouts/TME/SimpleLinks.css');
   9:  document.getElementsByTagName('head')[0].appendChild(link_tag);



Et j’ajoute benoitement l’appel :


   1:  // Cette ligne est déjà présente dans le template...
   2:  ctx['DisplayTemplateData'] = cachePreviousTemplateData;
   3:   
   4:  // Gestion du passage au premier plan :
   5:  $(document).ready(function() {
   6:      // find the div elements and hook the hover event
   7:      $('.itemToPop').hover(function() {
   8:          $(this).css('z-index',2)
   9:      }, function () {
  10:          $(this).css('z-index',1)
  11:      });
  12:  });




Note : Il est possible que ce code ne fonctionne pas directement (erreur jQuery inconnu ou $ est null ou n’est pas objet…). Dans ce cas, on peut utiliser la propriété “OnPostRender” de l’objet de contexte :


   1:  // Cette ligne est toujours là pour situer l'action :)
   2:  ctx['DisplayTemplateData'] = cachePreviousTemplateData;
   3:   
   4:  ctx.OnPostRender = [];
   5:  ctx.OnPostRender.push(function(){ /* invoke your plugin code here to ensure the plugin has the content to work against */
   6:  $(document).ready(function() {
   7:      // find the div elements and hook the hover event
   8:      $('.itemToPop').hover(function() {
   9:          $(this).css('z-index',2)
  10:      }, function () {
  11:          $(this).css('z-index',1)
  12:      });
  13:   })
  14:  });



Et zou, on vide les caches, on ouvre ses chakras et on contemple avec béatitude le rendu dynamique Sourire


image


Ceci clôture la première partie du dossier “Display Templates”. Vous avez matière à expérimenter. Rendez-vous d’ici quelques jours pour la 2e partie plus axée sur la recherche : affineurs, filtres et résultat de recherche au menu avec quelques pépites surprises…

2013-05-25

1e ConfSharePoint Française–Ce qu’il faut en retenir…

Les pontes SharePoint avaient rendez-vous du 22 au 24 mai 2013 à Disneyland Paris pour cette première. Les événements marquants vus par le prisme de la twittosphère :
image
Ah ben oui, on vous l’avait pas dit ? Office Web Apps, maintenant, c’est sur un serveur dédié : Si vous ne répartissiez pas vos infra sur plusieurs serveurs avant, c’était déjà mal… Maintenant, ça se verra dans votre portail de recherche qui ne comportera pas les miniatures dynamiques des docs Sourire.
image
Le ton est donné dès la première journée : En parcours “D”, de l’apps, tu vas en bouffer (et notre session n’y dérogera pas). Abandonnez le modèle Sandbox (deprecated nous confirmait le 3e jour @Michaeltnoel) et passez aux apps (ou appelez-nous Sourire ) !
image
Copinage : La session présentée par Ben et Lio s’est bien déroulée, avec de l’effet wow sur la géolocalisation “out-of-the-box”.
imageimage
Le message est passé : Notre culture française est trop timorée vis-à-vis des outils tiers : AvePoint, NewsGator, Yammer… Nos cousins Québecois ou Belges nous ont d’ailleurs martelé le message : Que ce soit en migration ou en contenu avancé (RBS et consors, social…), les outils tiers sont présents et performants.
image
Un framework qui vous facilite la vie, couplé à des apps Auto-Hosted : 20 minutes de NAPA, SkyDrive Pro, votre éditeur JS préféré et c’est Byzance. (Désolé pour la relance Guillaume, mais il m’intéresse aussi grandement ce FWK Clignement d'œil )
imageimage
LA session à ne pas rater : la nôtre ! Les apps Provider-Hosted (et la comparaison aux autres) pour déployer sans interrompre le portail et migrer “en moins de 5 minutes” votre applicatif ASP.NET “kivabien” Sourire Défi relevé.
image
Pas de secrets : Pour une mécanique bien huilée, Dev et IT (ou l’inverse) doivent travailler main dans la main. Pour ça l’industrialisation des procédés (par scripting massif), la séparation des rôles et une bonne dose de café et de bière sont nécessaires !
imageimage
La BI à l’honneur sur la J3 avec les "wow effects” des rendus clients direct sous Excel mais Performance Point Services et SSRS ont encore leur mot à dire avec des intégrations UI plus poussées et des temps de développement ultra-réduits grace au tabulaire qui supplante le cube dans les complexités moindres.
image
Du grand michael avec des chiffres mais pas trop, des exemples mais pas trop, des règles mais pas trop et un débit anglais rapide mais pas trop (sauf quelques fois Clignement d'œil ) Dimensionnez correctement vos plateformes, virtualisez, Always-Onisez mais faites-le intelligemment.
image
Pendant ce temps-là… On fait des apps pour le plaisir…
imageimageimageimage
Avalanche d’outils et de tips sur les 3 dernières sessions… (“Mais pourquoi n’y ai-je pensé avant ?”)
Au fait, je vous ai parlé du cloud ? (à part les apps Auto-Hosted) L’autre vedette de cette 3e journée :
image
C’est le moment de s’y pencher si ce n’est pas déjà fait.
Bref, une conférence d’un beau niveau technique, des intervenants de qualité ET sympas et pas mal d’infos à collecter, assimiler, creuser… Rendez-vous l’année prochaine !