Standaryzacja struktury danych dla układu zawartości

Programista tworzący układ zawartości powinien ustandaryzować strukturę danych otrzymywanych przez układ zawartości.

Jeśli wszystkie dane są obecne, układ zawartości może bezproblemowo wyrenderować składnik. Jeśli nie wszystkie dane są obecne, może wystąpić wykonania dodatkowych zapytań. We wszystkich przypadkach układ zawartości nie po9wnien nigdy zakładać określonego formatu danych, a zamiast tego wymuszać dane w formacie umożliwiającym renderowanie.

Trzeba zapewnić obecność wszystkich oczekiwanych danych. Jeśli pewne dane nie istnieją, trzeba wykonać dodatkowe zapytania. W danych może potencjalnie brakować:

  • Wpisu "fields" dla pól, do których występuje odwołanie

  • Dużych pól tekstowych

Ponieważ układy zawartości są projektowane dla określonych typów zawartości, programista układu wie, które pola są wymagane. Aby układ zawartości mógł zostać wyrenderowany, dla każdego z tych pól muszą zostać pobrane dane. Są dostępne dwa rozwiązania: pobrać brakujące dane i renderować układ z użyciem kompletnych danych albo renderować od razu, a następnie pobrać brakujące dane w celu wypełnienia pustych miejsc.

Rozwiązanie 1: Pobieranie brakujących danych i renderowanie z użyciem kompletnych danych

Utworzyć obiekt "Promise" do pobrania wymaganych danych, a następnie kontynuować renderowanie, gdy zostaną zwrócone wszystkie dane "Promise".

Na przykład mamy następujące typy zawartości z odpowiadającymi im polami:

  • starter-blog-author

    • pola

      • starter-blog-author_name — pole tekstowe

      • starter-blog-author_bio — pole tekstowe

  • starter-blog-post

    • pola

      • starter-blog-post_title — pole tekstowe

      • starter-blog-post_content — duże pole tekstowe

      • starter-blog-post_author — odwołanie do elementu starter-blog-author

Układ zawartości ma następujący szablon do renderowania wartości tych oczekiwanych pól:

{{#fields}}
<div class="blog_container">
    <div class="blog-post-title">{{starter-blog-post_title}}</div>
    {{#starter-blog-post_author.fields}}
    <div class="blog-author-container">
        <div class="blog-author-details">
            <div class="blog-author-name">{{starter-blog-author_name}}</div>
            <div class="blog-author-bio">{{{starter-blog-author_bio}}}</div>
            <span class="more-from-author">More articles from this author</span>
        </div>
    </div>
    {{/starter-blog-post_author.fields}}
    <div class="blog-post-content">{{{starter-blog-post_content}}}</div>
</div>
{{/fields}}

Układ zawartości można wywołać z danymi z następujących zapytań:

  • Zapytanie (o element) z właściwością "expand" — dostarczone wszystkie dane

    • /content/published/api/v1.1/items/{id}?expand=fields.starter-blog-post_author&channelToken=8dd714be0096ffaf0f7eb08f4ce5630f

    • Jest to format danych wymaganych do pomyślnego wypełnienia wszystkich wartości w szablonie. Jeśli którekolwiek z zapytań zostanie użyte, wymagane jest pobranie danych i przekształcenie ich do tego formatu.

    • "fields": {    
          "starter-blog-post_title": "...",
          "starter-blog-post_summary": "...",
          "starter-blog-post_content": "...",
          "starter-blog-post_author": {
              "id": "CORE386C8733274240D0AB477C62271C2A02",
              "type": "Starter-Blog-Author"
              "fields": {
                  "starter-blog-author_bio": "...",
                  "starter-blog-author_name": "..."
              }
          }
      }
  • Zapytanie (o element) bez właściwości "expand" — brak pól elementu, do których występuje odwołanie "starter-blog-post_author.fields":

    • /content/published/api/v1.1/items/{id}?channelToken=8dd714be0096ffaf0f7eb08f4ce5630f
    • "fields": {    
          "starter-blog-post_title": "...",
          "starter-blog-post_summary": "...",
          "starter-blog-post_content": "...",
          "starter-blog-post_author": {
              "id": "CORE386C8733274240D0AB477C62271C2A02",
              "type": "Starter-Blog-Author"
          }
      }
  • Zapytanie SCIM — brak dużego pola tekstowego "starter-blog-post_content" oraz brak pól "starter-blog-post_author.fields" elementu, do których występuje odwołanie:

    • /content/published/api/v1.1/items?q=(type eq "Starter-Blog-Post")&fields=ALL&channelToken=8dd714be0096ffaf0f7eb08f4ce5630f

    • "fields": {
          "starter-blog-post_title": "...",
          "starter-blog-post_summary": "...",
          "starter-blog-post_author": {
              "id": "CORE386C8733274240D0AB477C62271C2A02",
              "type": "Starter-Blog-Author"
          }
      }

Aby można było spójnie renderować z użyciem dowolnego z tych zapytań, render.js z układu zawartości musi się upewnić, że pola, do których występują odwołanie, zostały rozwinięte oraz że są obecne duże pola tekstowe.

Jeśli tak nie jest, musi ponownie uruchomić zapytanie, poprawić dane, po czym renderować z użyciem kompletnych danych.

Przykładowa funkcja render():

render: function (parentObj) {
    var self = this,
        template,
        contentClient = self.contentClient,
        content = self.contentItemData;

     var getRefItems = function (contentClient, ids) {
        // Calling getItems() with no "ids" returns all items.
        // If no items are requested, just return a resolved Promise.
        if (ids.length === 0) {
            return Promise.resolve({});
        } else {
            return contentClient.getItems({
                "ids": ids
            }); 
        }
     };
   
     var fetchIDs = [], // list of items to fetch
         referedFields = ['starter-blog-post_author'], // names of reference fields
         largeTextFields = ['starter-blog-post_content'], // large text fields in this asset
         fieldsData = content.fields;
     // See if we need to fetch any referenced fields
     referedFields.forEach(function (fieldName) {
         if(fieldsData[fieldName] && fieldsData[fieldName].fields) {
            // got data already, nothing else to do
         } else { 
             // fetch this item
             fetchIDs.push(fieldsData[fieldName].id);
         }
     });

     // See if we need to fetch any large text fields
     for(var i = 0; i < largeTextFields.length; i++) {
        if(!fieldsData[largeTextFields[i]]) {
           // need to fetch this content item directly to get all the large text fields
            fetchIDs.push(content.id);
            break;
        }
     }
    // now we have the IDs of all the content items we need to fetch, get them all before continuing
    getRefItems(contentClient, fetchIDs).then(function (referenceData) {
        var items = referenceData && referenceData.items || [];

        // add the data back in
        items.forEach(function (referencedItem){
            // check if it's the current item
            if(referencedItem.id === content.id) {
               // copy across the large text fields 
               largeTextFields.forEach(function (fieldName) {
                   fieldsData[fieldName] = referencedItem.fields[fieldName];
                });
            } else{
                // check for any referenced fields
                for (var i = 0; i < referedFields.length; i++) {
                    if(referencedItem.id === fieldsData[referedFields[i]].id){
                       // copy across the fields values
                       fieldsData[referedFields[i]].fields = referencedItem.fields;
                       break;
                    }
                }
            }
        });

        // now data is fixed up, we can continue as before
        try{
           // Mustache
           template = Mustache.render(templateHtml, content);

             if(template) {
                $(parentObj).append(template);
             }

        } catch (e) {            
            console.error(e.stack);
        }    
    });
}

Rozwiązanie 1: Natychmiastowe renderowanie i późniejsze pobranie brakujących danych w celu wypełnienia pustych miejsc

Wydajność operacji można poprawić, oddzielając elementy, których może nie być, i renderując je w drugim przebiegu. W tym celu są potrzebne dwa szablony Mustache: pierwszy do wykonania pierwszego renderowania z pozostawieniem pustych miejsc, które następnie zostaną wypełnione podczas drugiego renderowania, kiedy będą obecne wszystkie dane.

Wymaga to skonfigurowania szablonu Mustache do obsługi więcej niż jednego przebiegu przez używanie albo osobnych szablonów dla pustych miejsc, albo modelu z makrami zwracającymi szablon zamiast faktycznych wartości. W obu przypadkach trzeba ukryć puste miejsca do chwili pobrania danych, wstawienia ich i pokazania za pomocą odpowiedniej animacji UI (aby uniknąć zbytniego „przeskakiwania”).