콘텐츠 레이아웃을 위한 데이터 구조 표준화

콘텐츠 레이아웃 개발자는 콘텐츠 레이아웃이 수신하는 데이터 구조를 표준화해야 합니다.

모든 데이터가 있으면 콘텐츠 레이아웃이 간단히 구성요소를 렌더링할 수 있습니다. 모든 데이터가 없으면 콘텐츠 레이아웃이 추가 질의를 실행해야 할 수도 있습니다. 모든 경우 콘텐츠 레이아웃은 특정 데이터 형식을 가정해서는 안되며, 대신 렌더링할 형식으로 데이터를 강제 변환해야 합니다.

필요한 모든 데이터가 있는지 확인해야 합니다. 데이터가 없으면 추가 질의를 실행해야 합니다. 다음 필드는 잠재적으로 데이터에서 누락될 수 있습니다.

  • 참조된 필드의 "fields" 항목

  • 큰 텍스트 필드

콘텐츠 레이아웃은 특정 콘텐츠 유형에 맞게 설계되기 때문에 콘텐츠 레이아웃 개발자는 필요한 필드 목록을 알고 있습니다. 각 필드에 대해 콘텐츠 레이아웃이 렌더링할 수 있도록 데이터를 인출해야 합니다. 두 가지 옵션이 있습니다. 하나는 누락된 데이터를 인출한 후 완전한 데이터로 렌더링하는 것이고, 다른 하나는 즉시 렌더링한 후 누락된 데이터를 인출하여 공란을 채우는 것입니다.

옵션 1: 누락된 데이터를 인출한 후 완전한 데이터로 렌더링

Promise를 생성하여 필요한 데이터를 검색한 후 모든 Promise가 반환될 때 렌더링을 계속합니다.

예를 들어, 다음 콘텐츠 유형과 해당 필드가 있습니다.

  • starter-blog-author

    • 필드

      • starter-blog-author_name - 텍스트 필드

      • starter-blog-author_bio - 텍스트 필드

  • starter-blog-post

    • 필드

      • starter-blog-post_title - 텍스트 필드

      • starter-blog-post_content - 큰 텍스트 필드

      • starter-blog-post_author - starter-blog-author 항목에 대한 참조

콘텐츠 레이아웃은 다음 템플리트를 가지고 필요한 필드 값을 렌더링합니다.

{{#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}}

콘텐츠 레이아웃은 다음 질의를 통해 데이터를 호출할 수 있습니다.

  • "expand"를 사용한 항목 질의 - 모든 제공된 데이터

    • /content/published/api/v1.1/items/{id}?expand=fields.starter-blog-post_author&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"
              "fields": {
                  "starter-blog-author_bio": "...",
                  "starter-blog-author_name": "..."
              }
          }
      }
  • "expand"가 없는 항목 질의 - 누락된 참조 항목 필드 "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"
          }
      }
  • SCIM 질의 - 누락된 큰 텍스트 필드 "starter-blog-post_content", 누락된 참조 항목 필드 "starter-blog-post_author.fields":

    • /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"
          }
      }

이 질의로 일관되게 렌더링할 수 있으려면 콘텐츠 레이아웃의 render.js에서 모든 참조된 필드가 확장되고 큰 텍스트 필드가 있는지 확인해야 합니다.

그렇지 않으면 다시 질의하여 데이터를 수정한 후 완전한 데이터로 렌더링해야 합니다.

샘플 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);
        }    
    });
}

옵션 2: 즉시 렌더링한 후 누락된 데이터를 인출하여 공란 채우기

표시되지 않을 항목을 분리했다가 두번째 패스로 렌더링하면 성능을 향상시킬 수 있습니다. 두 개의 Mustache 템플리트가 필요합니다. 첫번째는 초기 렌더링을 수행하고, 데이터가 완료되면 두번째 렌더링을 사용하여 "구멍"으로 남겨둔 부분을 채웁니다.

이를 위해 "구멍"마다 별도의 템플리트를 사용하거나 모델이 실제 값이 아닌 템플리트 매크로를 반환하는 방법으로 다중 패스를 지원하도록 Mustache 템플리트를 설정해야 합니다. 어느 경우든 데이터가 검색될 때까지 이 구멍을 "숨겼다가" 채우고 적절한 UI 애니메이션으로 표시해야 페이지가 너무 많이 "튀어오르는" 것을 피할 수 있습니다.