Episerver og de nye listeegenskapene
Episerver kommer med ukentlige oppdateringer, men det er som oftest bare mindre feilrettinger, og det kan gå uker eller måneder mellom hver virkelige godbit. Godt skjult i Episerver – update 189, som ble sluppet 21. november 2017, var det en ny feature: «CMS-8190: List properties». Jeg overså først denne selv, men nå er tiden inne for å finne ut hva dette egentlig er.
Med update 189 fikk vi EPiServer.CMS.UI 11.1.0, og mulighet til å opprette egenskaper som kan holde på lister av tekster, tall og datoer. Mer presist kan vi ha lister av følgende typer:
- String
- Int
- Double
- DateTime
Alle fire variantene fungerer på samme måte. Man kan legge til, endre og slette elementer i listen. I tillegg kan man bytte om på rekkefølgen, enten ved å flytte enkeltelementer opp og ned, eller ved hjelp av «drag and drop».
String
Helt vanlige tekstbokser der du kan skrive inn tekster.
public virtual IList<string> ListOfStrings { get; set; }
Int
Tekstbokser for heltall, med knapper for å endre tallverdien pluss/minus 1.
public virtual IList<int> ListOfInts { get; set; }
Double
Tekstbokser for desimaltall.
public virtual IList<double> ListOfDoubles { get; set; }
DateTime
Tekstbokser med datovelger.
public virtual IList<DateTime> ListOfDateTimes { get; set; }
Validering
For å begrense antall elementer i en liste, kan vi bruke attributtet ListItems.
[ListItems(10)]
public virtual IList<string> NotMoreThan10Items { get; set; }
For å validere de enkelte elementene i en liste, kan du ikke bruke de ordinære attributtene som brukes på andre enkeltstående egenskaper, men det finnes tre egne attributter som fungerer kun for listeelementer:
[ItemRangeAttribute(1, 10)]
public virtual IList<int> ItemsBetween1And10 { get; set; }
[ItemRegularExpression("[a-zA-Z]*")]
public virtual IList<string> LettersOnly { get; set; }
[ItemStringLength(3)]
public virtual IList<string> ListOfAcronyms { get; set; }
Under overflaten
Hvis vi skal dykke litt ned under overflaten, kan vi åpne dotPeek og ta en titt på den abstrakte klassen PropertyValueListEditorDescriptor
som ligger i EPiServer.Cms.Shell.UI.ObjectEditing.EditorDescriptors.PropertyValueList
.
Den abstrakte klassen har fire implementasjoner, og dermed én editordescriptor for hver av de fire listetypene.
Hvis vi ser på EditorDescriptoren for en liste av stringer, ser vi at den er registrert for IList<string>
…og siden IList arver fra ICollection og IEnumerable, kan vi velge mellom følgende varianter:
public virtual IList<string> ListOfStrings { get; set; }
public virtual ICollection<string> ListOfStrings { get; set; }
public virtual IEnumerable<string> ListOfStrings { get; set; }
Hvis man har lyst til å studere selve editorene, ligger både javascript (dojo/dijit) og html i zip-filen CMS.zip som du finner under \packages\EPiServer.CMS.UI.11.1.1\content\modules\_protected\CMS
. Videre inne i zip-filen ligger PropertyValueList-editoren under \11.1.1.0\ClientResources\epi-cms\contentediting\editors\propertyvaluelist
.
Litt dypere
La oss si at vi ønsker å bruke lister av en annen type enn det Episerver har lagt til rette for, f.eks boolske verdier. Kanskje vi kunne ha bruk for en liste med sjekkbokser. Vi prøver!
Jeg starter med å legge til en egenskap med en liste over bool på en sidetype.
public virtual IList<bool> CollectionOfBools { get; set; }
Men Episerver er raskt ute med å si at dette ikke gir mening:
Neste skritt på veien blir å informere Episerver om at vi faktisk ønsker oss en ny egenskapstype, som er en liste med boolske verdier.
using EPiServer.Core;
using EPiServer.PlugIn;
using System;
namespace Alloy.Business.Properties
{
[PropertyDefinitionTypePlugIn]
[Serializable]
public class PropertyBooleanList : PropertyList
{
}
}
Episerver godtar den nye egenskapen, men velger å vise den som en tekstboks. Ikke helt ideelt.
Jeg forsøker videre å fortelle Episerver at vi gjerne kan bruke det samme redaktørgrensesnittet som for de andre listeegenskapene. TargetType er datatypen for boolske lister, og ClientEditingClass er javascriptet jeg fant under overskriften under overflaten.
using EPiServer.Cms.Shell.UI.ObjectEditing.EditorDescriptors.PropertyValueList;
using EPiServer.Framework.Localization;
using EPiServer.Shell.ObjectEditing;
using EPiServer.Shell.ObjectEditing.EditorDescriptors;
using EPiServer.Shell.UI.Rest;
using System.Collections.Generic;
using System.Web;
namespace Alloy.Business.EditorDescriptors
{
[EditorDescriptorRegistration(TargetType = typeof(IList<bool>))]
public class BooleanListEditorDescriptor : PropertyValueListEditorDescriptor<bool>
{
public BooleanListEditorDescriptor(LocalizationService localizationService, IMetadataStoreModelCreator metadataStoreModelCreator, EPiServer.ServiceLocation.ServiceAccessor httpContextServiceAccessor, EPiServer.ServiceLocation.ServiceAccessor metadataHandlerRegistryAccessor)
: base(localizationService, metadataStoreModelCreator, httpContextServiceAccessor, metadataHandlerRegistryAccessor)
{
this.ClientEditingClass = "epi-cms/contentediting/editors/propertyvaluelist/PropertyValueList";
}
}
}
Nå får jeg muligheten til å legge til sjekkbokser i redaktørmodus:
Men når jeg publiserer siden skjer det noe uventet! Alle sjekkboksene som ikke er avhuket forsvinner!
Det er litt dumt, hvis vi nå later som at jeg ønsker å ta vare disse verdiene også. Etter å ha snoket litt i Episerver sitt javascript, finner jeg synderen i PropertyValueListViewModel.js
.
Følgende metode brukes for å hente ut verdiene fra redaktørmodus, og den filtrerer vekk sjekkbokser som ikke har noen verdi:
getFilteredValue: function () {
// summary:
// Returns value excluding empty items
// tags:
// public
return this.get("value").filter(function (item) {
return item === 0 || !!item;
});
}
Denne metoden kalles fra javascriptfilen vi spesifiserte i vår EditorDescriptor: epi-cms/contentediting/editors/propertyvaluelist/PropertyValueList.js.
Jeg tar en kopi av den filen, kaller den ModifiedPropertyValueList.js og skriver om koden slik at vi ikke lenger bruker den problematiske metoden i PropertyValueListViewModel.js
.
_getValueAttr: function () {
// summary:
// Gets the values from the model.
// tags:
// private
var listOfValues = this.model.get("value");
for (var i = 0; i < listOfValues.length; i++){
if (listOfValues[i] == null)
{
listOfValues[i] = false;
}
}
return listOfValues;
}
EditorDescriptoren vi opprettet må oppdateres til å peke på den nye javascriptfilen:
this.ClientEditingClass = "alloy/editors/modifiedpropertyvaluelist/ModifiedPropertyValueList";
I tillegg må vi gjøre noen andre mindre endringer i javascriptet. Episerver har allerede definert:
define("epi-cms/contentediting/editors/propertyvaluelist/PropertyValueList", [
...og godtar ikke at vår variant heter det samme, så jeg endrer navn til:
define("alloy/editors/modifiedpropertyvaluelist/ModifiedPropertyValueList", [
I tillegg ber jeg Episerver bruke sin egen variant av de andre filene som brukes til redaktørgrensesnittet for lister. Urlene er relative i vår kopi, så jeg gjør de absolutte, og endrer fra:
"./viewmodels/PropertyValueListViewModel",
"./PropertyValueListItem",
"./command/AddPropertyValue",
// resources
"dojo/text!./templates/PropertyValueList.html"
til:
"epi-cms/contentediting/editors/propertyvaluelist/viewmodels/PropertyValueListViewModel",
"epi-cms/contentediting/editors/propertyvaluelist/PropertyValueListItem",
"epi-cms/contentediting/editors/propertyvaluelist/command/AddPropertyValue",
// resources
"dojo/text!epi-cms/contentediting/editors/propertyvaluelist/templates/PropertyValueList.html"
Nå blir verdiene lagret riktig, uansett om sjekkboksene er avhuket eller ikke, og vi kan bruke listen av boolske verdier programmatisk. Kanskje vi ønsker å vise frem verdien direkte i et view også? Jeg forsøker:
@Html.DisplayFor(x => x.CurrentPage.CollectionOfBools)
Jeg vet ikke helt hva jeg hadde forventet, men resultatet ble dette:
Jeg tenkte kanskje vi kunne vise verdien som et binært tall isteden, og lager en DisplayTemplate som jeg plasserer i /Views/Shared/DisplayTemplates/:
@model IEnumerable<bool>
@if (Model != null && Model.Any())
{
@NumberHelper.GetNumberBase2(@Model)2
(@NumberHelper.GetNumberBase10(@Model)10)
}
Da må jeg samtidig legge på et UIHint (med samme navn som TemplateDescriptor) der jeg legger egenskapen på sidetypen:
[UIHint("BoolCollection")]
public virtual IList CollectionOfBools { get; set; }
Og en liten hjelpeklasse for å skrive ut sjekkboksverdiene som binærtall, og som tall i titall-systemet:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Alloy.Helpers
{
public static class NumberHelper
{
public static string GetNumberBase2(IEnumerable<bool> listOfBools)
{
return string.Join("", listOfBools.Select(x => x ? "1" : "0"));
}
public static string GetNumberBase10(IEnumerable<bool> listOfBools)
{
var binary = GetNumberBase2(listOfBools);
return Convert.ToInt32(binary, 2).ToString();
}
}
}
Og nå vises egenskapen (som egentlig er en liste med sjekkbokser) på denne måten:
Mye jobb for lite? Tja?
Spesielt praktisk? Nei.
Lærerikt? Ja!