Die Wissensecke zum Stöbern

Building an Autocomplete Field in SharePoint 2013 Look and Feel

Von Kunden werden Autocomplete Fields geliebt und von Entwicklern werden diese gehasst. Denn bis SharePoint 2010 konnten diese Optisch und funktional nur schwer integriert werden.

Doch SharePoint 2013 selber setzt nun erstmalig auf diese Felder. Ein netter Anwendungsfall ist der Peoplepicker.

Von daher sollten eigene Anwendungen oder Apps sich optisch nahtlos in SharePoint 2013 integrieren. Und das dafür keine externen Bibliotheken (jQuery UI etc) mehr benötigt werden, soll dieser Artikel zeigen ;-) .

Building your own SPClientAutoFill Control

Ich werde an Hand einer SharePoint Hosted App zeigen, wie solch ein Autocomplete Field in SharePoint 2013 mit wenigen Schritten erstellt wird. Zunächst erzeuge ich die notwendige minimale HTML-Struktur.

<div style="position: relative;">
    <input type="text" id="inputField" />
    <div class="sp-peoplepicker-autoFillContainer" id="resultContainer"></div>
</div>

Danach muss sichergestellt sein, dass folgende JavaScript Datei geladen wird.

<script type="text/javascript" src="/_layouts/15/autofill.js"></script>

Damit sind alle Grundvoraussetzungen gegeben und es könnte solch ein Feld erstellt werden. Ich habe mir jedoch gleich ein Helper erstellt, um den Code nicht jedes mal von einem Projekt ins andere kopieren zu müssen.

Die dazu von mir erstellte JavaScript Datei Msscorner.ClientAutoFill.js sieht wie folgt aus:

Type.registerNamespace('Msscorner.ClientAutoFill');

Msscorner.ClientAutoFill = function () {
    // private
    var current = {
        AutoFillElem: null,
        AutoFillMinTextLength: 1,
        VisibleItemCount: 10,
        CurrentFocusOption: 0,
        SelectCallback: null,
        Init: function (inputId, outputId, selectCallback) {
            /// <summary>Initialize the html Field</summary>
            /// <param name="inputId" type="String">Id of the input Field</param>
            /// <param name="outputId" type="String">Id of the output Field</param>
            /// <param name="selectCallback" type="Function">Optional Callback for handle the OnSelect Event</param>
            current.autoFillElem = new SPClientAutoFill(inputId, outputId, current.OnPopulateItems)
            current.autoFillElem.AutoFillMinTextLength = current.AutoFillMinTextLength;
            current.autoFillElem.CurrentFocusOption = current.CurrentFocusOption;
            current.autoFillElem.VisibleItemCount = current.VisibleItemCount;
            if (typeof (selectCallback) != null && typeof (selectCallback) != 'undefined')
                current.SelectCallback = selectCallback;
        },
        OnPopulateItems: function (inputElem) {
            var value = inputElem.value.toLowerCase().trim();
            var items = [];
            var results = 0;

            for (var i = 0; i < Msscorner.ClientAutoFill.countries.length; i++) {
                if (Msscorner.ClientAutoFill.countries[i].toLowerCase().indexOf(value) == 0) {
                    items.push(current.GetOptionItem(Msscorner.ClientAutoFill.countries[i], i));
                    results++;
                }
            }

            items.push(current.GetSeperatorItem());

            if (results == 0)
                items.push(current.GetFooterItem("No results were found"));
            else
                items.push(current.GetFooterItem("Showing " + results + " result"));

            current.autoFillElem.PopulateAutoFill(items, current.OnSelectItem);
        },
        OnSelectItem: function (inputId, item) {
            var inputElem = document.getElementById(inputId);
            inputElem.value = item[SPClientAutoFill.DisplayTextProperty];
            inputElem.setAttribute("selectedKey", item[SPClientAutoFill.KeyProperty]);
            if (typeof (current.SelectCallback) != null && typeof (current.SelectCallback) != 'undefined')
                current.SelectCallback(item[SPClientAutoFill.DisplayTextProperty], item[SPClientAutoFill.KeyProperty]);
        },
        GetOptionItem: function (name, id) {
            var item = {};

            item[SPClientAutoFill.KeyProperty] = id;
            item[SPClientAutoFill.DisplayTextProperty] = name;
            item[SPClientAutoFill.TitleTextProperty] = name;
            item[SPClientAutoFill.MenuOptionTypeProperty] = SPClientAutoFill.MenuOptionType.Option;

            return item;
        },
        GetFooterItem: function (value) {
            var item = {};

            item[SPClientAutoFill.DisplayTextProperty] = value;
            item[SPClientAutoFill.MenuOptionTypeProperty] = SPClientAutoFill.MenuOptionType.Footer;

            return item;
        },
        GetSeperatorItem: function () {
            var item = {};

            item[SPClientAutoFill.MenuOptionTypeProperty] = SPClientAutoFill.MenuOptionType.Separator;

            return item;
        }
    };
    // private static
    Msscorner.ClientAutoFill.countries = ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Anguilla", "Antigua & Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas"
        , "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia & Herzegovina", "Botswana", "Brazil", "British Virgin Islands"
        , "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Cape Verde", "Cayman Islands", "Chad", "Chile", "China", "Colombia", "Congo", "Cook Islands", "Costa Rica"
        , "Cote D Ivoire", "Croatia", "Cruise Ship", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea"
        , "Estonia", "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "French West Indies", "Gabon", "Gambia", "Georgia", "Germany", "Ghana"
        , "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India"
        , "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kuwait", "Kyrgyz Republic", "Laos", "Latvia"
        , "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Mauritania"
        , "Mauritius", "Mexico", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia"
        , "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal"
        , "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", "Saint Pierre & Miquelon", "Samoa", "San Marino", "Satellite", "Saudi Arabia", "Senegal", "Serbia", "Seychelles"
        , "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sri Lanka", "St Kitts & Nevis", "St Lucia", "St Vincent", "St. Lucia", "Sudan"
        , "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor L'Este", "Togo", "Tonga", "Trinidad & Tobago", "Tunisia"
        , "Turkey", "Turkmenistan", "Turks & Caicos", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam", "Virgin Islands (US)"
        , "Yemen", "Zambia", "Zimbabwe"];
    //public interface
    Msscorner.ClientAutoFill.prototype.AutoFillMinTextLength = current.AutoFillMinTextLength;
    Msscorner.ClientAutoFill.prototype.VisibleItemCount = current.VisibleItemCount;
    Msscorner.ClientAutoFill.prototype.CurrentFocusOption = current.CurrentFocusOption;
    Msscorner.ClientAutoFill.prototype.Callback = current.SelectCallback;
    Msscorner.ClientAutoFill.prototype.Init = current.Init;
};

Auch bei dieser JavaScript Datei muss sichergestellt sein, dass diese geladen wird. Schließlich kann mit wenigen Zeilen das Autocomplete Field beispielsweise in der App.js instanziiert werden.

$(document).ready(function () {
    var autoFill = new Msscorner.ClientAutoFill();
    autoFill.Init("inputField", "resultContainer", function (value, key) {
        console.log("key: " + key + " | value: " + value);
    });
});

Wird diese App im SharePoint deployed so erscheint folgendes Ergebnis:

Wird ein Callback mit übergeben, so kann auf das Event SelectionChanged reagiert werden. Im Beispiel wird der Name sowie die Id des selektierten Elements ausgegeben.

Fazit

Dieser Artikel hat gezeigt und hoffentlich den Entwicklern auch die Angst genommen, dass Autocomplete Fields in SharePoint 2013 sehr schnell und mit wenigen Schritten erstellt werden können. Der große Vorteil dieser Variante besteht allerdings darin, dass die Lösung sich zu 100% in das Look and Feel von SharePoint 2013 integriert. Und das finde ich persönlich sehr wichtig! Und all das wäre ohne die 3.8 Kilometer JavaScript in SharePoint 2013 definitiv nicht möglich ;-) !

About the Author

Falco Ostermann is a software developer and deals with Microsoft Technologies with the focus on Microsoft SharePoint. He is one of the developer who like Javascript and CSS in addition to C#. Falco works at QSC AG in Dresden and is an active member of the SharePoint Usergroup Dresden. You can meet Falco online via Twitter (@Falco_Ostermann), Google+ or here.
  1. Hi Falco,

    sehr knackiger Artikel.

    Wenn ich es richtig verstanden habe, ist der eigentliche Clou die Realisierung einer Autocomplete-Funktionalität, unter Zuhilfenahme des SharePoint-spezifischen JavaScript Elementes “SPClientAutoFill” (aus dem SharePoint-Standard-JavaScript “autofill.js”), welches das Look&Feel vom SharePoint oob übernimmt. – Das erspart natürlich den Entwicklungsaufwand signifikant.

    Kann man dies eigentlich nur autark (bspw. innerhalb einer Autohosted-App) nutzen oder ist auch eine Bindung an ein Listen-Feld möglich (bspw. im List-Item-Edit-Modus)?

    Was ich mich auch gerade frage: Es gibt neben dem SPClientAutoFill sicherlich noch viele weitere SharePoint-eigene JavaScript-Controls, die in den 11kg SP-JavaScripts versteckt sind. Hast Du eine Empfehlung, wann man diese nutzen sollte und wann lieber nicht? Kennst Du eine Übersicht dieser SharePoint-eigenen JavaScript-Controls?

    Beste Grüße vom nördlichen Teil der Elbe ;-)
    Axel

  2. Stefan said:

    Hi Falco,

    Wie immer ein toller Artikel!

    Gerade habe ich diese Anforderung für eine SP2010-Umgebung auf dem Tisch. Dabei soll einem Textfeld in einem EditForm eine Autovervollständigung verpasst werden, welche die Werte aus einer weiteren Liste bezieht.

    Die technische Umsetzung war relativ einfach, die optische Anpassung hingegen ist, wie Du richtig beschreibst, sehr umständlich.

    Da hier in naher Zukunft ein Wechsel auf SP2013 zu erwarten ist, danke ich Dir schon jetzt für diesen Artikel!!

    Herzliche Grüße vom Ostufer der Leine ;-)
    Stefan

    • Hallo Stefan,

      freut mich, dass ich dir eine kleine Anregung für SharePoint 2013 geben konnte. Ich wünsche Dir, dass der Wechsel auf SP2013 schnell vollzogen ist!