Ga naar de inhoud

Sjablonen achter een knop – URI schema – PnP Modern Search

Introductie

Van een klant kreeg ik de vraag of ik ervoor kon zorgen dat office sjablonen centraal beheerd konden worden. Dat gebruikers niet na hoeven denken of ze het juiste sjabloon te pakken hebben. En dat het ook nog eens eenvoudig in het gebruik is. Liefst via het intranet.

De oplossing lag volgens mij in de Organizational Assets Library (OAL) waarbij je een SharePoint bibliotheek inricht als centrale opslag voor sjablonen. Wanneer je dan in Office een nieuw document wil maken kies je in Word voor het juiste sjabloon. Word kijkt altijd of er een OAL is. Is die er, dan toont die alle voor jouw beschikbare sjablonen.

Waarom is dit handig voor gebruikers?

Gebruikers hoeven niet na te denken en kunnen er zeker van zijn dat ze de laatste versie van het sjabloon hebben.

Waarom dan toch een andere oplossing / wat ging er mis

Een van de belangrijkste redenen was dat bij deze klant niet iedereen over de vereiste Microsoft licentie beschikte en dus geen gebruik kon maken van de OAL. 

Wat gaan we bereiken?

In dit blog ga ik een alternatief uitleggen. Zoals je kunt zien in het screenshot start je een nieuw document door te klikken op het juiste sjabloon. Word, Excel of PowerPoint zal openen en een nieuw document maken op basis van het gekozen sjabloon.

Voorbereiding

Toch een OAL?

Omdat ik voorbereid wil zijn voor de toekomst heb ik een centrale biblitoheek gemaakt en die ook als OAL ingericht. Dit is de plek waar alle sjablonen (in het .dotx .xltx, .potx formaat) komen staan. 

SharePoint lijst voor sjabloon knoppen

Een SharePoint lijst waar per sjabloon een item is aangemaakt. De lijst die ik heb gemaakt heeft de volgende eigenschappen.

  • Title → Tekst
  • Bestandstype → Keuze veld (Word, Excel, PowerPoint)
  • Afdeling → Keuze veld (Algemeen, Training, HR, Projectmanagement, Sales, Finance)
  • URLvanSjabloon → Tekst

Plak de url van het sjabloon in het veld URLvanSjabloon. Hier hoef je geen URI (ms-word:) toe te voegen; dat doen we later automatisch.


De iconen bij bestandstype kun met deze JSON tonen (maar is niet nodig voor de oplossing)

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json",
  "elmType": "div",
  "style": {
    "display": "flex",
    "align-items": "center",
    "gap": "8px"
  },
  "children": [
    {
      "elmType": "img",
      "attributes": {
        "src": "=if(@currentField == 'Word', 'https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/assets/item-types/32/docx.svg', if(@currentField == 'Excel', 'https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/assets/item-types/32/xlsx.svg', if(@currentField == 'PowerPoint', 'https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/assets/item-types/32/pptx.svg', '')))"
      },
      "style": {
        "width": "32px",
        "height": "32px"
      }
    }
  ]
}

Plak de JSON bij de kolom formattering door bij de kolom te klikken op Kolom instellingen, formatteer deze kolom.


PnP-Modern Search

Ik heb PnP-Modern Search geinstalleerd via de officiele Github site. Daar vind je ook de uitleg over hoe je die moet installeren.

RefinableString

In het PnP-Modern Search searchresults web part moeten we de kolom URLvanSjabloon beschikbaar stellen. Die gaan ik gebruiken om de knop klikbaar te maken. Maar die kolom is niet standaard beschikbaar en daarom configureer ik eerst een RefinableString en koppel die aan de juiste kolom. Waarom dit zo werkt gaat te ver voor dit blog maar google er maar eens naar (of beter nog, Kagi er maar eens naar 😉)

Ga in het SharePoint admin (tenant-admin.SharePoint.com) menu naar Search schema. 


Vul in RefinableString en klik op de pijl of druk op enter om te zoeken. Klik vervolgens op de eerste  RefinableString waar nog geen Mapped Crawled Property achter staat. Blader helemaal naar beneden en klik op Een mapping toevoegen.


Het kan even duren voordat de RefinableString gevuld wordt met de waarde uit de lijst. Soms wel een hele dag. Maar om het te versnellen kun je de items in de Sjabloon knoppen lijst bewerken en een nieuwe url in opslaan of de huidige bewerken. Dat genereert een trigger waardoor de kolom sneller zal worden geïndexeerd door search.

Net niet standaard

Als je in SharePoint op een .dotx document klikt wordt Word opgestart en zul je het sjabloon gaan bewerken. En dat is nou net niet wat je wilt. Daarom gebruiken we een URI om Word op te starten (voor Excel en PowerPoint is dit hetzelfde.)

Een URI? Wat is dat en hoe werkt het?

Een URI‑schema (of protocol handler) is het eerste deel van een link dat aangeeft welk soort actie een app of service moet uitvoeren. Je kent er vast al een paar:

  • https:// → opent een webpagina
  • mailto: → opent een nieuwe e‑mail

En voor Office is er ook een.

  • ms-word:, ms-excel:, ms-powerpoint: → openen Microsoft 365‑apps of starten specifieke acties

Voordelen van deze methode

Op deze manier kun je bv Word opstarten en een aantal opties meegeven. De URI die ik gebruikt heb om een nieuw document te maken op basis van een sjabloon gebruik ik deze link:

ms-word:nft|u|https://tenantnaam.sharepoint.com/Sjablonen/AeroWeave_Template.dotx

waarbij:

ms-word:Word zal openen
nft(New From Template) Maak een nieuw document van een sjabloon
|u|Welk sjabloon wordt gebruikt (Dit moet een http of https adres zijn)
|s| (optioneel)Waar wordt het nieuwe document opgeslagen

Ook hier geef je een http of https adres op (moet op dezelfde omgeving zijn)

(Deze optie heb ik hier niet gebruikt).

De Uitdaging in SharePoint

Waarom is dit standaard niet zo eenvoudig?

In SharePoint is het gemakkelijk om een koppeling op te nemen. Daarvoor gebruik je een Button, Call to Action of gewoon een link in een tekstblok. 

Beperkingen van SharePoint

Maar in SharePoint mag je alleen links opnemen die beginnen met http://, https:// of mailto://. Daar loop je dus vast. 

Hoe dan?

Mensen die me een beetje kennen weten dat ik fan ben van search en dan in het bijzonder PnP-Modern Search. En laat die nou net de oplossing bieden voor het probleem van de ms-word: uri. Het search results web part van PnP-Modern Search maakt het mogelijk om de weergave van de zoekresultaten aan te passen. Dit zijn de zogenaamde Display Templates en zijn JSON scripts zoals je die gebruikt bij een kolom- of weergave formattering. In dit blog deel ik het display template script wat ik heb gebruikt en wat je ziet in het screenshot. Als je meer wil weten van de display template adviseer ik je om te kijken op de PnP Modern Search pagina’s.

Stap-voor-stap: Knop maken in SharePoint

Een knop toevoegen aan je pagina

Wat we nu doen is knoppen maken van de items in de Sjabloon knoppen lijst. En dit doen we door Display templates te maken met PnP-Modern search. Hier komen alle onderdelen dus bij elkaar.

Zorg ervoor dat de PnP-Modern search app is geinstalleerd en is toegevoegd aan de site waar je de Sjabloon knoppen wil tonen. 

Voeg op de pagina het PnP-Modern seach web part toe en klik op configureren. In de volgende screenshots laat ik zien welke instellingen je moet maken. 


Dit is de query die ik gebruik:

* path:"https://<tenant>.sharepoint.com/sites/Communicatie/Lists/Sjablonen" contenttype:"Item"

Zoek en selecteer de gebruikte RefinableString (in mijn geval RefinableString00). 

Als die niet in de lijst staat, komt hij later maar dat kan ook weer even duren. Je mag hem ook intypen in het veld “Selected properties” typen. 

Ga door naar het 2e configuratie scherm.

Kies voor een Custom layout en klik op de accolades. Er verschijnt dan een bewerk venster waar je alle html code mag selecteren en verwijderen. 

Plak vervolgens onderstaande code en klik op “Opslaan”

<content id="data-content">
    <style>
        .dashboard-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 16px;
            padding: 16px 0;
        }

        .dashboard-card {
            background: #ffffff;
            border-radius: 8px;
            padding: 20px 24px;
            text-decoration: none;
            color: #323130;
            display: flex;
            align-items: center;
            gap: 16px;
            transition: box-shadow 0.2s ease, transform 0.15s ease;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
            border-left: 4px solid #e1e1e1;
        }

        .dashboard-card:hover {
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            transform: translateY(-2px);
            text-decoration: none;
            color: #323130;
        }

        .dashboard-card:focus {
            outline: 2px solid #0078d4;
            outline-offset: 2px;
        }

        .dashboard-card--word  { border-left-color: #185ABD; background: #f0f4fb; }
        .dashboard-card--excel { border-left-color: #107C41; background: #f0f8f4; }
        .dashboard-card--ppt   { border-left-color: #C43E1C; background: #fdf3f0; }

        .dashboard-card--word:hover  { background: #e4ecf7; }
        .dashboard-card--excel:hover { background: #e2f2ea; }
        .dashboard-card--ppt:hover   { background: #f8e8e3; }

        .dashboard-card-icon {
            flex-shrink: 0;
            width: 40px;
            height: 40px;
        }

        .dashboard-card-title {
            font-size: 16px;
            font-weight: 600;
            line-height: 1.4;
            margin: 0;
            word-break: break-word;
        }

        .dashboard-section {
            margin-bottom: 32px;
        }

        .dashboard-section:last-child {
            margin-bottom: 0;
        }

        .dashboard-divider {
            display: flex;
            align-items: center;
            gap: 12px;
            margin-bottom: 12px;
        }

        .dashboard-divider-icon {
            flex-shrink: 0;
            width: 28px;
            height: 28px;
        }

        .dashboard-divider-label {
            font-size: 18px;
            font-weight: 700;
            margin: 0;
        }

        .dashboard-divider-label--word  { color: #185ABD; }
        .dashboard-divider-label--excel { color: #107C41; }
        .dashboard-divider-label--ppt   { color: #C43E1C; }

        .dashboard-divider-line {
            flex-grow: 1;
            height: 1px;
        }

        .dashboard-divider-line--word  { background: #185ABD; }
        .dashboard-divider-line--excel { background: #107C41; }
        .dashboard-divider-line--ppt   { background: #C43E1C; }
    </style>

    <!-- Word sjablonen -->
    <div class="dashboard-section">
        <div class="dashboard-divider">
            <svg class="dashboard-divider-icon" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
                <rect width="48" height="48" rx="8" fill="#185ABD"/>
                <text x="24" y="33" text-anchor="middle" font-family="Segoe UI, sans-serif" font-size="26" font-weight="700" fill="#ffffff">W</text>
            </svg>
            <p class="dashboard-divider-label dashboard-divider-label--word">Word</p>
            <div class="dashboard-divider-line dashboard-divider-line--word"></div>
        </div>
        <div class="dashboard-grid">
            {{#each data.items as |item|}}
                {{#if (contains (slot item @root.slots.Link) ".dotx")}}
                    <a class="dashboard-card dashboard-card--word"
                       href="ms-word:nft|u|{{slot item @root.slots.Link}}"
                       title="{{slot item @root.slots.Title}}">
                        <p class="dashboard-card-title">{{slot item @root.slots.Title}}</p>
                    </a>
                {{/if}}
            {{/each}}
        </div>
    </div>

    <!-- Excel sjablonen -->
    <div class="dashboard-section">
        <div class="dashboard-divider">
            <svg class="dashboard-divider-icon" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
                <rect width="48" height="48" rx="8" fill="#107C41"/>
                <text x="24" y="33" text-anchor="middle" font-family="Segoe UI, sans-serif" font-size="26" font-weight="700" fill="#ffffff">X</text>
            </svg>
            <p class="dashboard-divider-label dashboard-divider-label--excel">Excel</p>
            <div class="dashboard-divider-line dashboard-divider-line--excel"></div>
        </div>
        <div class="dashboard-grid">
            {{#each data.items as |item|}}
                {{#if (contains (slot item @root.slots.Link) ".xltx")}}
                    <a class="dashboard-card dashboard-card--excel"
                       href="ms-excel:nft|u|{{slot item @root.slots.Link}}"
                       title="{{slot item @root.slots.Title}}">
                        <p class="dashboard-card-title">{{slot item @root.slots.Title}}</p>
                    </a>
                {{/if}}
            {{/each}}
        </div>
    </div>

    <!-- PowerPoint sjablonen -->
    <div class="dashboard-section">
        <div class="dashboard-divider">
            <svg class="dashboard-divider-icon" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
                <rect width="48" height="48" rx="8" fill="#C43E1C"/>
                <text x="24" y="33" text-anchor="middle" font-family="Segoe UI, sans-serif" font-size="26" font-weight="700" fill="#ffffff">P</text>
            </svg>
            <p class="dashboard-divider-label dashboard-divider-label--ppt">PowerPoint</p>
            <div class="dashboard-divider-line dashboard-divider-line--ppt"></div>
        </div>
        <div class="dashboard-grid">
            {{#each data.items as |item|}}
                {{#if (contains (slot item @root.slots.Link) ".potx")}}
                    <a class="dashboard-card dashboard-card--ppt"
                       href="ms-powerpoint:nft|u|{{slot item @root.slots.Link}}"
                       title="{{slot item @root.slots.Title}}">
                        <p class="dashboard-card-title">{{slot item @root.slots.Title}}</p>
                    </a>
                {{/if}}
            {{/each}}
        </div>
    </div>
</content>

<content id="placeholder-content">
    <style>
        .dashboard-grid-ph {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 16px;
            padding: 16px 0;
        }

        .dashboard-card-ph {
            background: #f3f2f1;
            border-radius: 8px;
            padding: 20px 24px;
            animation: shimmer 1.5s infinite;
        }

        .dashboard-card-ph .line {
            height: 16px;
            width: 70%;
            background: #e1dfdd;
            border-radius: 4px;
        }

        @keyframes shimmer {
            0%   { opacity: 0.6; }
            50%  { opacity: 1; }
            100% { opacity: 0.6; }
        }
    </style>

    <div class="dashboard-grid-ph">
        {{#times 6}}
            <div class="dashboard-card-ph">
                <div class="line"></div>
            </div>
        {{/times}}
    </div>
</content>

Pro tip: Ik heb in de assets library een html file geplaatst met de html code daarin. De url (pad) naar dit html bestand heb ik geplakt in de configuratie van het Search results web part. — Als je per ongeluk een andere layout kiest (bv Lijst of debug) dan ben je de code kwijt. (vraag me niet hoe ik dit weet 😉) 

Sla de pagina op en her-publiceer. Nu zul je zien dat de knoppen automatisch worden getoond en gegroepeerd.

Tips & Troubleshooting


Een nadeel

Een nadeel van deze oplossing is dat wanneer je het document klaar hebt, je het ergens moet gaan opslaan. Meestal vertel ik gebruikers tijden het adoptie traject: Begin je document waar het moet eindigen; Dan hoef je niet meer na te denken waar je het document nadat het gereed is, gaat opslaan. Nu moet je het document zelf naar de juiste plek brengen. Dat kan door het op te slaan op je buroblad en vervolgens te uploaden naar SharePoint, of in je OneDrive of je slaat het op via de verkenner naar een gesynchroniseerde map of bibliotheek of je had al een snelkoppeling gemaakt naar die bibliotheek waar je vaak werkt.

Tip

Meestal heeft een organisatie een aantal sjablonen die niet door iedereen gebruikt mogen worden. Die knoppen moeten dan ook niet worden getoond op de pagina. 

Dit kun je op verschillende manieren oplossen. 

  1. Maak twee of meer search results web parts en koppel er een doelgroep (audience) aan. Ben je bv geen lid van de afdeling HR dan zul je dat web part niet zien.
  2. Geef de items in de Sjabloon knoppen lijst unieke rechten → koppel security groepen aan de items. Search respecteerd permissies en zal nooit items tonen waar je als gebruiker geen rechten voor hebt. 
  3. Zet ook rechten op de originele sjablonen of plaats ze in mappen met speciale rechten. Doe ik wel in combinatie met de permissies op de sjablonen knoppen lijst anders klikt iemand op een sjabloon knop krijgt een foutmelding omdat het sjabloon niet gelezen mag worden. 
  4. De sjabloon knoppenlijst mag best op een andere site staan, en je kunt het search result web part op verschillende sites plaatsen. Zo lang je de path: parameter in de search query laat wijzen naar de plek waar de sjabloonknoppen staan. 
  5. Je kunt deze oplossing ook maken voor een site waar je de documenten ook direct wil opslaan. In dat geval kun je gebruik maken van de 2e |s| parameter en een opslag locatie meegeven.

Conclusie

In dit blog heb ik beschreven hoe je knoppen als alternatief kunt gebruiken voor het starten van een Office document. Dit kan handig zijn in voorkomende situaties en kan ook bijdragen aan adoptie. Let wel op: Begin je document waar het moet eindigen → gaat in deze oplossing niet op. Daarvoor kunnen we altijd nog de inhoudstypes gebruiken of een sjabloon toevoegen aan de “Nieuw” knop in een biblitoheek.

Mocht je meer willen weten, zoek me eens op tijdens een van de SharePoint bijeenkomsten zoals Collabdays, Diwug, enz. Ik spreek er regelmatig over search.

Of stuur me een berichtje. 

Ik zou het leuk vinden om te horen of dit je heeft geïnsprieerd en of je het hebt kunnen toepassen in jouw omgeving. 

Gepubliceerd inSearchSharePoint

Wees de eerste om te reageren

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *