Spring til indholdet

Driftsmeddelelser på forsiden med PnP Modern Search

En kompakt liste over nedbrud og driftsarbejde, hvor kritiske sager får et rødt skjold og løste et grønt flueben — bygget med en egen skabelon i PnP Modern Search.

Opdateret PnP Modern SearchSharePoint Online 5 min. læsning

Jeg har bygget den samme lille løsning et par gange: en liste med driftsmeddelelser — nedbrud, planlagt arbejde, ting der driller — vist som en kort liste på intranettets forside. Kritiske meddelelser, der ikke er løst, får et rødt skjold. Løste meddelelser får et grønt flueben, og er de ikke kritiske, også teksten “(Løst)”. Resuméet er foldet sammen, så listen kan læses på et øjeblik, og den ansvarlige står nederst, når man folder ud.

Det er PnP Modern Search, der gør arbejdet. Resten er en indholdstype og en Handlebars-skabelon.

Fremgangsmåde#

  1. Installer PnP Modern Search-webdelene.
  2. Opret en indholdstype til meddelelserne. Skabelonen forventer en startdato, et emne, et ja/nej-felt for kritisk, et ja/nej-felt for løst, en titel, et resumé og en ansvarlig (et personfelt).
  3. Sæt en Search Results-webdel op, der henter meddelelserne, og giv felterne slots med de navne, skabelonen bruger: StartDate, Topic, Critical, Solved, Title, Summary og Responsible.
  4. Vælg et custom layout, og indsæt skabelonen herunder.
  1<content id="data-content">
  2
  3    <style>
  4
  5       /* Insert your CSS overrides here */
  6       .example-themePrimary a {
  7            color: {{@root.theme.palette.themePrimary}};
  8        }
  9
 10        /* .icon {
 11            width: 20px;
 12            height: 16px;
 13        } */
 14
 15        ul.template--itdrift {
 16            list-style: none;
 17            padding-left: 0px;
 18            margin-block-start: -2em;
 19            margin-left: 3px;
 20        }
 21
 22        ul.template--itdrift li {
 23            display: inline-grid;
 24            padding: 15px 0px;
 25            border-bottom: 1px solid rgb(11, 36, 64);
 26            min-width: 100%;
 27        }
 28        ul.template--itdrift .themeMeta{
 29            font-size: 12px;
 30            font-weight: 600;
 31            margin-bottom:5px;
 32            color: {{@root.theme.palette.themeblack}};
 33        }
 34        ul.template--itdrift .themeTitle{
 35            color: {{@root.theme.palette.themeDarker}};
 36        }
 37        ul.template--itdrift .themeTitleRed{
 38            display: flex;
 39            margin-bottom: 5px;
 40            color: {{@root.theme.palette.redDark}};
 41        }
 42        ul.template--itdrift .themeTitleGreen{
 43            display: flex;
 44            /* font-size: 16px;
 45            font-weight: bold; */
 46            margin-bottom: 5px;
 47            color: {{@root.theme.palette.green}};
 48        }
 49        ul.template--itdrift .themeTitleIcon {
 50            margin-top: 3px;
 51            margin-right: 7px;
 52        }
 53        ul.template--itdrift .themeContent {
 54            margin-bottom: 5px;
 55            color: {{@root.theme.palette.themeDarker}};
 56        }
 57        span.themeTitleRed + pnp-collapsible div[role="rowgroup"] span.themeContent, span.themeTitleRed + pnp-collapsible span.themeUser,
 58        span.themeTitleGreen + pnp-collapsible div[role="rowgroup"] span.themeContent, span.themeTitleGreen + pnp-collapsible span.themeUser
 59        {
 60            margin-left:-24px;
 61            display: block;
 62        }
 63        span.themeTitleRed + pnp-collapsible {
 64            margin-left: 24px;
 65            margin-top: -24px;
 66        }
 67        span.themeTitleGreen + pnp-collapsible {
 68            margin-left: 70px;
 69            margin-top: -26px;
 70        }
 71        span.themeTitleGreen + pnp-collapsible span.themeContent, span.themeTitleGreen + pnp-collapsible span.themeUser {
 72            margin-left: -70px;
 73        }
 74        span.themeTitleRed, span.themeTitleRed + pnp-collapsible{
 75            color: {{@root.theme.palette.redDark}};
 76        }
 77        
 78        span.themeTitleGreen, span.themeTitleGreen + pnp-collapsible{
 79            color: {{@root.theme.palette.green}};
 80        }
 81        div[role="menubar"] > span, .solvedprefix {
 82            font-size: 16px;
 83            font-weight: bold;
 84        }
 85        div[role="rowgroup"] span.themeContent, span.themeUser{
 86            font-size: 14px;
 87            font-weight: normal;
 88        }
 89        .template--header{
 90            font-size: 20px;
 91            color: {{@root.theme.palette.themePrimary}};
 92            font-weight: 600;
 93        }
 94
 95
 96    </style>
 97<!--<h4 class="template--header">Aktuelt på kontoret</h4>-->
 98
 99    <div class="template">
100
101        {{#if @root.properties.showSelectedFilters}}
102            <pnp-selectedfilters
103                data-filters="{{JSONstringify filters.selectedFilters 2}}"
104                data-filters-configuration="{{JSONstringify filters.filtersConfiguration 2}}"
105                data-instance-id="{{filters.instanceId}}"
106                data-operator="{{filters.filterOperator}}"
107                data-theme-variant="{{JSONstringify @root.theme}}"
108            ></pnp-selectedfilters>
109        {{/if}}
110<!-- 
111        <div class="template--header">
112            {{#if @root.properties.showResultsCount}}
113                <div class="template--resultCount">
114                    <label class="ms-fontWeight-semibold">{{getCountMessage @root.data.totalItemsCount @root.inputQueryText}}</label>
115                </div>
116            {{/if}}
117
118            <div class="template--sort">
119                <pnp-sortfield
120                    data-fields="{{JSONstringify @root.properties.dataSourceProperties.sortList}}"
121                    data-default-selected-field="{{sort.selectedSortFieldName}}"
122                    data-default-direction="{{sort.selectedSortDirection}}"
123                    data-theme-variant="{{JSONstringify @root.theme}}">
124                </pnp-sortfield>
125            </div>
126        </div> -->
127
128        
129        <ul class="template--itdrift">
130            {{#each data.items as |item|}}
131
132                <pnp-select
133                    data-enabled="{{@root.properties.itemSelectionProps.allowItemSelection}}"
134                    data-index="{{@index}}"
135                    data-is-selected="{{isItemSelected @root.selectedKeys @index}}">
136
137                    <template id="content">
138                        <li>
139                            <span class="themeMeta">{{getDate (slot item @root.slots.StartDate) "D. MMMM HH:mm" 0 true}} | {{slot item @root.slots.Topic}}</span>
140                            
141                            {{#eq (slot item @root.slots.Critical) 'True;1'}} 
142                                {{#eq (slot item @root.slots.Solved) '0'}} 
143                                    <span class="themeTitleRed"><span class="themeTitleIcon"> <pnp-icon data-name="shieldalert" aria-hidden="true"></pnp-icon></span></span>
144                                    
145                                {{/eq}}
146                                {{#eq (slot item @root.slots.Solved) '1'}} 
147                                    <span class="themeTitleGreen"><span class="themeTitleIcon"> <pnp-icon data-name="SkypeCircleCheck" aria-hidden="true"></pnp-icon></span></span>
148                                {{/eq}}
149                            {{/eq}}
150                            
151                            {{#eq (slot item @root.slots.Critical) 'False;0'}} 
152                                {{#eq (slot item @root.slots.Solved) '0'}} 
153                                    <span class="themeTitle"> </span>
154                                {{/eq}}
155                                {{#eq (slot item @root.slots.Solved) '1'}} 
156                                    <span class="themeTitleGreen"><span class="themeTitleIcon"> <pnp-icon data-name="SkypeCircleCheck" aria-hidden="true"></pnp-icon></span><span class="solvedprefix">(Løst)</span></span>
157                                {{/eq}}
158                            {{/eq}}
159                            <pnp-collapsible data-group-name="{{slot item @root.slots.Title}}" data-default-collapsed="true">
160                                <template id="collapsible-content">
161                                        <span class="themeContent">{{{slot item @root.slots.Summary}}}</span>
162                                </template>
163                                <template id="collapsible-footer">
164                                        <span class="themeUser"><strong>Ansvarlig: </strong> {{itemAt (split (slot item @root.slots.Responsible) "|") 1}}</span>
165                                </template>
166                            </pnp-collapsible>
167                            <!-- <span class="themeContent">{{slot item @root.slots.Summary}}</span>
168                            <span class="themeUser"><strong>Ansvarlig: </strong> {{itemAt (split (slot item @root.slots.Responsible) "|") 1}}</span> -->
169                        </li>
170                        <!-- {{#> resultTypes item=item}}
171                            {{!-- The block below will be used as default item template if no result types matched --}}
172                            <li>
173                                <pnp-iconfile class="icon" data-extension="{{slot item @root.slots.FileType}}" data-theme-variant="{{JSONstringify @root.theme}}"></pnp-iconfile>
174                                <span class="example-themePrimary">{{slot item @root.slots.Title}}</span>
175                            </li>
176                        {{/resultTypes}} -->
177                    </template>
178
179                </pnp-select>
180
181            {{/each}}
182        </ul>
183    </div>
184
185    {{#if @root.properties.paging.showPaging}}
186        <pnp-pagination
187            data-total-items="{{@root.data.totalItemsCount}}"
188            data-hide-first-last-pages="{{@root.properties.paging.hideFirstLastPages}}"
189            data-hide-disabled="{{@root.properties.paging.hideDisabled}}"
190            data-hide-navigation="{{@root.properties.paging.hideNavigation}}"
191            data-range="{{@root.properties.paging.pagingRange}}"
192            data-items-count-per-page="{{@root.properties.paging.itemsCountPerPage}}"
193            data-current-page-number="{{@root.paging.currentPageNumber}}"
194        >
195        </pnp-pagination>
196    {{/if}}
197
198</content>
199
200<content id="placeholder-content">
201
202    <style>
203
204        .placeholder .icon {
205            width: 20px;
206            height: 16px;
207            margin-right: 5px;
208        }
209
210        .placeholder ul {
211            list-style: none;
212        }
213
214        .placeholder ul li {
215            display: flex;
216            align-items: center;
217            padding: 8px;
218        }
219    </style>
220
221    <div class="placeholder">
222        <ul>
223            {{#times @root.properties.paging.itemsCountPerPage}}
224                <li>
225                    <div class="icon placeholder--shimmer"></div>
226                    <span class="placeholder--shimmer placeholder--line" style="width: 60%"></span>
227                </li>
228            {{/times}}
229        </ul>
230    </div>
231</content>

Det der driller#

  • Ja/nej-felter kommer ikke ud som true og false. Skabelonen sammenligner Critical med 'True;1' og 'False;0' og Solved med '1' og '0', fordi det var de værdier, mine slots gav. Kommer der ingen ikoner frem, er det værd at se efter, hvad slottene faktisk returnerer.
  • Et personfelt er én lang streng. Værdierne er adskilt af |, og visningsnavnet er andet led. Derfor itemAt (split ... "|") 1.
  • En ikke-kritisk, uløst meddelelse får intet ikon — bare titlen. Det er med vilje: den skal ikke råbe.
  • Margenerne er skruet på plads i hånden. De negative margin-left på -24px og -70px passer til ikonet og til teksten “(Løst)”. Skiftes ikon eller tekst ud, flytter teksten sig.

Alle noter