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 !

2013-05-20

SharePoint 2013 – Intégration de vos applicatifs existants

La démo est prête ! Rendez-vous jeudi à 14h50 en salle 2 de la ConfSharePoint France 2013 pour y découvrir comment intégrer sans douleur (ou presque) ni péridurale vos applicatifs HTML / JS et ASP.NET existant dans votre portail SharePoint 2013 ; une heure sans trucage (de toutes façons, j’aurai des manches courtes).

Une démo de CMS “kienvoiduboi” par Lionel LIMOZIN et moi–même !

Et si vous cherchez où vous vous trouvez, rendez-vous en salle 1 la veille même heure pour suivre Benoit et Lionel dans le périple mobilité “kidémontelodyssée”.

2013-04-24

Conf’SharePoint – Rendez-vous le 23 mai chez Mickey !

Quel honneur ! J’aurai le plaisir de vous présenter comment “Développer pour SharePoint 2013 sans intrusion” jeudi 23 mai à 14h50 lors de la première Conf’SharePoint française !

Pour ce baptême du feu, je serai bizuthé par Lionel LIMOZIN et j’espère vous apporter les clés d’un développement d’App réussi.

Tous les détails de la conf ici : http://www.confsharepoint.com/

Je dispose également de places à tarif préférentiel. N’hésitez pas à me solliciter en commentaire.

2013-03-23

Office Web Apps–Le prérequis KB2592525 refuse de s’installer

Envie de voir vos miniatures dans la recherche et d’exploiter tout le potentiel de SharePoint 2013 “On Premise”, vous préparez votre serveur OWA sour Windows Server 2008 R2 SP1… Et Patatras : OWA vous réclame l’installation de la KB2592525 mais l’installation de celle-ci vous martèle que “This update is not applicable to your computer.”

Et merci le forum Technet : http://social.technet.microsoft.com/Forums/en-US/winserversetup/thread/9827c20c-09ae-4963-bd82-7aabc6cd7565

Après téléchargement / décompression du MSU dans un répertoire local (ici : C:\Temp), il suffit de lancer l’installation “à la main” :

PS C:\Temp> .\Windows6.1-KB2592525-x64.msu /extract:C:\Temp\PKG
PS C:\Temp> Set-Location PKG
PS C:\Temp\PKG> pkgmgr.exe /n:Windows6.1-KB2592525-x64.xml

L’installation d’OWA se déroule ensuite sans problème !

2013-03-06

Perfidie du moteur XSL et déstructuration avec apply-templates

Un effet “amusant” découvert il y a peu et qui m’a couté quelques heures de recherche et de tests (avec un cache pour augmenter le “plaisir”) :

Considérons la transformation XSL suivante :

...

<div class="Items">
    <div class="Level0">
        <xsl:apply-templates select="..."/>
    </div>
    <div class="Level1">
        <xsl:apply-templates select="..."/>
    </div>
</div>

...

Lorsque les sélections contiennent toutes des éléments dans le flux d’entrée, tout se passe bien mais si la source du premier sélecteur (Level0) ne rencontre aucun candidat : patatras ! Le sous-flux retourné ressemble à :

<div class="Items">
    <div class="Level0">
        <div class="Level1">
            Des éléments...
        </div>
    </div>
</div>

La balise fermant le div “Level0” n’est injectée dans le flux de sortie qu’à la fin de celui-ci, causant éventuellement un décalage visuel sur un affichage HTML…

Pour revenir à une situation cohérente, il faut ajouter la directive précisant la méthode sortie en tête de flux XSL :

<xsl:stylesheet version="1.0" ...>
    <xsl:output method="html"/>
    ...

Et tout rentre dans l’ordre :

<div class="Items">
    <div class="Level0">
    </div>
    <div class="Level1">
        Des éléments...
    </div>
</div>

Si l’un d’entre vous a un début d’explication, je prends !

Mots clés Technorati : ,,,

2013-03-04

Appels aux Services Web SharePoint – Ne négligez aucune piste !

Un petit retour personnel sur un problème rencontré lors de l’appel aux services Web. Lorsque les détails de l’exception remontée semblent bien sibyllins :

Exception of type 'Microsoft.SharePoint.SoapServer.SoapServerException' was thrown.

… et que l’accès au client pour augmenter le niveau de détail est difficile, il reste les logs SharePoint !

SharePoint_Blog_ULS_Threshold

Et le calvaire prend fin…

Mots clés Technorati : ,,

2013-02-14

Redéfinition des styles riches et CSS multiples

Dans un souci permanent de la satisfaction client… Et pour respecter la charte, vous avez redéfini les styles d’édition de vos champs de texte riche (par exemple, en suivant : http://www.sharepointblues.com/2010/10/27/custom-styles-for-sharepoint-2010-rich-html-field/).

MAIS vous avez défini plusieurs feuilles de style CSS. Par exemple, pour pouvoir “compresser” vos feuilles CSS tout en gardant la possibilité d’utiliser la surcharge de thème proposée par SharePoint et pour laquelle il ne faut pas supprimer les commentaires, ce que la compression fait :) !

Et là, Ô rage, Ô désespoir, le titre de vos styles disparait !

Capture_20130214_094715

Quelques tests tendent à montrer que c’est le premier qui parle qui a raison ! Donc, la première occurrence de votre classe personnalisée DOIT définir l’attribut –ms-name !

Vérification, en plaçant le même nom partout (pas très pratique mais évite les effets de bord en cas de modification ultérieure…) :

Capture_20130214_095702

TADAAAAAAA

Ca marche aussi pour le renommage des styles SharePoint par défaut : On peut chercher longtemps pourquoi le style semble s’appliquer alors que le nom n’est pas modifié :)

A vos marques, prêts, concevez !

Mots clés Technorati : ,,,,

2013-02-12

SharePoint Foundation 2013 - The SDDL string contains an invalid sid or a sid that cannot be translated

Vous installez votre serveur standalone pour vos inavouables desseins personnels et la vie vous semble rose. Quand tout à coup, lors de l’exécution du SharePoint Product Configuration Tool

The SDDL string contains an invalid sid or a sid that cannot be translated

Enfer, damnation, il n’y a pourtant rien de compliqué à cette installation basique !?

Mais une installation SharePoint sans surprise n’a pas vraiment de saveur alors c’est parti pour la recherche des blogs… Et on tombe sur cet article : http://kancharla-sharepoint.blogspot.fr/2012/07/the-sddl-string-contains-invalid-sid-or.html

Service running under Network Service account in workgroup environment is not supported.

Qu’à cela ne tienne, on y va pour les manipulations, au cas où :

  1. Exécution avec PowerShell sous contexte SharePoint de psconfig.exe -cmd Configdb create SkipRegisterAsDistributedCacheHost dans le répertoire BIN de la ruche SharePoint.
  2. Création d’un compte de Service (exemple : svcSPSearch) appartenant au moins au groupe WSS_ADMIN_WPG.
  3. Attacher un compte géré à ce compte de service, soit par la Central Admin, soit par powerShell : New-SPManagedAccount –Credential (Get-Credential).
  4. Associer le compte au service de recherche. ATTENTION ! N’utilisez pas pour cela les propriétés de l’application de service car le compte n’est pas modifié. Utilisez la page Service Accounts de la rubrique Security.
  5. Relancez le Configuration Wizard qui devrait se terminer correctement.

NB : Si une NullReferenceException se produit lors de l’appel à Microsoft.SharePoint.SPEvaluatorModeProvisioner.IsProvisioned(), assurez-vous que vos applications web comportent au moins une collection de site.