Developer

This chapter will explain different usecases for developer working with headless extension.

Internal Extbase plugins

To integrate a custom frontend plugin which return its data inside the JSON object, we have to do the following:

Follow the standard proceeding to register and configure extbase plugins:

Create the DemoController.php:

class DemoController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
  public function indexAction() {
    return json_encode([
       'foo' => 'bar',
       'settings' => $this->settings
    ]);
  }
}

Use the plugin through TypoScript:

tt_content.list =< lib.contentElementWithHeader
tt_content.list {
  fields {
    content {
      fields {
        data = CASE
        data {
          key.field = list_type
          demoplugin_type = USER
          demoplugin_type {
            userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
            vendorName = Vendor
            extensionName = ExtName
            pluginName = DemoPlugin
            controller = Demo
            settings {
              test = TEXT
              test.value = The demo is working
            }
          }
        }
      }
    }
  }
}

Clear the cache and in the response we will see the following JSON output (shortened):

{
  "content": {
    "colPos0": [{
      "type": "demoplugin_type",
      "appearance": {...},
      "content": {
        "data": {
          "foo": "bar",
          "test": {
            "value": "The demo is working",
            "_typoScriptNodeValue": "TEXT"
          }
        }
      }
    }]
  }
}

Integrating external plugins

The integration of other extension plugins is pretty simple. We're providing the headless_news extension as an example of how it works.

Main part is a user function definition to run a plugin from TypoScript:

tt_content.list =< lib.contentElementWithHeader
tt_content.list {
  fields {
    content {
      fields {
        data = CASE
        data {
          key.field = list_type
          news_pi1 = USER
          news_pi1 {
            userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
            vendorName = GeorgRinger
            extensionName = News
            pluginName = Pi1
            controller = News
            view < plugin.tx_news.view
            persistence < plugin.tx_news.persistence
            settings < plugin.tx_news.settings
          }
        }
      }
    }
  }
}

For any other plugin, just change the vendorName, extensionName, pluginName and controller options, and import needed constant and setup values (like for view, persistence and settings in this case).

Than use the constants of that extension to overwrite the paths to the fluid templates:

plugin.tx_news {
  view {
    templateRootPath = EXT:headless_news/Resources/Private/News/Templates/
    partialRootPath = EXT:headless_news/Resources/Private/News/Partials/
    layoutRootPath = EXT:headless_news/Resources/Private/News/Layouts/
  }
}

As last step we need to re-implement the template logic to generate JSON instead of HTML structure. We do this by creating Fluid templates at the location specified in the previous step.

Because we don't enforce any standard for the JSON structure, we are pretty free here to adjust the structure to our needs (or to the requests of our frontend developer).

Here is the shortened List.html template which generates news items into a JSON array:

<f:spaceless>
  {"list": [<f:for each="{news}" as="newsItem" iteration="newsIterator">
  <f:if condition="{settings.excludeAlreadyDisplayedNews}">
    <f:then>
      <n:format.nothing>
        <n:excludeDisplayedNews newsItem="{newsItem}"/>
      </n:format.nothing>
    </f:then>
  </f:if>
  <f:render section="NewsListView" arguments="{newsItem: newsItem,settings:settings,iterator:iterator}" />
    {f:if(condition: newsIterator.isLast, else: ',')}
  </f:for>],
  "settings":
  <f:format.raw>
    <f:format.json value="{
      orderBy: settings.orderBy,
      orderDirection: settings.orderDirection,
      templateLayout: settings.templateLayout,
      action: 'list'
    }"/>
  </f:format.raw>
  }
</f:spaceless>

Create custom content elements

To add custom content elements we can straight follow the native approach of TYPO3 and fluid_styled_content.

The only difference to make it work with headless is the configuration of the frontend template in TypoScript. There is an overwritten content object reference in lib.contentElement which we can use, as well as an extended object with a header definition lib.contentElementWithHeader:

tt_content.demo >
tt_content.demo =< lib.contentElementWithHeader
tt_content.demo {
  fields {
    content {
      fields {
        demoField = TEXT
        demoField.value = This is a demo content-element
        bodytext = TEXT
        bodytext {
          field = bodytext
          parseFunc =< lib.parseFunc_links
        }
        demoSubfields {
          fields {
            demoSubfield = TEXT
            demoSubfield.value = Nested field
          }
        }
      }
    }
  }
}

The definition of fields can be nested until various depth to reflect our desired JSON structure. Also the use of dataProcessing is possible the native way like in any other content elements (see content element definitions of this extension).

Create custom TypoScript

To add a default TypoScript object (such as CONTENT) to the fields of your page object you need to make sure to render it a valid JSON.

Here's an example of how you can create a JSON array of multiple objects from a custom DB table:

lib.page {
  fields {
    related = CONTENT
    related {
      table = tx_myextension_domain_model_things
      select {
        pidInList = this
      }
      renderObj = JSON
      renderObj {
        fields {
          title = TEXT
          title.field = title
          link = TEXT
          link.typolink.parameter.field = uid
          link.typolink.returnLast = url
        }
        # Add recognizable token at the end of this item
        stdWrap.wrap = |###BREAK###
      }
      stdWrap {
        # Wrap items into square brackets
        innerWrap = [|]

        # Replace 'inner tokens' by comma, remove others
        split {
          token = ###BREAK###
          cObjNum = 1 |*|2|*| 3
          1 {
            current = 1
            stdWrap.wrap = |
          }

          2 < .1
          2.stdWrap.wrap = ,|

          3 < .1
        }
      }
    }
  }
}

Meta data override

Here's an example of how to override the meta object by data from a DB record:

lib.meta.stdWrap.override.cObject = JSON
lib.meta.stdWrap.override.cObject {
  stdWrap.if.isTrue.data = GP:tx_news_pi1|news
  dataProcessing.10 = FriendsOfTYPO3\Headless\DataProcessing\DatabaseQueryProcessor
  dataProcessing.10 {
    table = tx_news_domain_model_news
    uidInList.data = GP:tx_news_pi1|news
    uidInList.intval = 1
    pidInList = 0
    max = 1
    as = records
    fields < lib.meta.fields
    fields {
      title = TEXT
      title.field = title
      subtitle = TEXT
      subtitle.field = teaser
      description = TEXT
      description.field = bodytext
    }

    returnFlattenObject = 1
  }
}

EXT:form & form output decorators

EXT:headless out of box provides for developers:

  • FriendsOfTYPO3\Headless\Form\Decorator\FormDefinitionDecorator

  • FriendsOfTYPO3\Headless\Form\Decorator\AbstractFormDefinitionDecorator

  • FriendsOfTYPO3\Headless\Form\Decorator\DefinitionDecoratorInterface

FormDefinitionDecorator is default decorator and outputs

form: {
  id: "ContactForm-1",
  api: {
    status: null,
    errors: null,
    actionAfterSuccess: null,
      page: {
        current: 0,
        nextPage: null,
        pages: 1
      }
  },
  i18n: {},
  elements: []
}

You can anytime extend & customize for your needs simply by create custom decorator which implements DefinitionDecoratorInterface or extend provided AbstractFormDefinitionDecorator which provides you ability to override definition of each element or whole form definition.

After creating custom decorator you can attach to your form simply by setting formDecorator in rendering options of form, see more

Snippets

See issue #136