Tags: Egenskaper

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:

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; }

En liste en string-egenskaper

Int

Tekstbokser for heltall, med knapper for å endre tallverdien pluss/minus 1.

public virtual IList<int> ListOfInts { get; set; }

En liste med heltall

Double

Tekstbokser for desimaltall.

public virtual IList<double> ListOfDoubles { get; set; }

En liste med desimaltall

DateTime

Tekstbokser med datovelger.

public virtual IList<DateTime> ListOfDateTimes { get; set; }

En liste med dato-tid-velgere

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.

PropertyValueListEditorDescriptor

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>

EditorDescriptor for string-egenskaper

…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.

Innholdet i en zip-fil

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:

Server Error: could not be mapped to a PropertyDefinitionType

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.

CollectionOfBools tekstområde

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:

En liste av checkboxer, inkludert noen som ikke er avkrysset

Men når jeg publiserer siden skjer det noe uventet! Alle sjekkboksene som ikke er avhuket forsvinner!

En liste med checkboxer

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:

Fem checkboxer

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:

Et tall, både binært og desimalt

Mye jobb for lite? Tja?
Spesielt praktisk? Nei.
Lærerikt? Ja!