Skip to content

8: Ajax Output

Sebastian Schendel edited this page Oct 31, 2021 · 5 revisions

Advanced sites with a lot of user interaction often have complex Javascript components that have to communicate with the backend. Some pages should also be connected to an app, or an external server needs secure access to special services. Sooner or later you will reach the point where you have to build your own API interface.

Good thing we have our Twack components! Twack comes with the ability to render JSON output instead of HTML views. So with Twack you can develop an API that is fully integrated into the rest of the site. If you write HTML output for a component, you can also define the appropriate JSON output.

I have written a second module for the definition of access routes and the administration of API accesses: AppApi works perfectly together with Twack. And there's more: AppApi also handles user authentication for you. The Double-JWT-Authentication is for example very well suited to build a login and session system into a connected app.

Since version 2.2.0 Twack will automatically check if AppApi is installed and will add an tpage endpoint! You can get the Twack-JSON response of any page in your page-tree, either by its id or by its path:

Route Description
/api/tpage/ Calls the root page of the page-tree
/api/tpage/42 Will call the page with id=42
/api/tpage/my/test/page Calls your page with path my/test/page

You can add the parameter lang with a language code to return contents for a non-default language in a multi language environment.

We will now, as announced, take care of the JSON outputs by our components. First of all, you should know that the following command, called before the render process, forces Twack to initiate the Ajax output:

wire('twack')->enableAjaxResponse();

Each component already has an implicit getAjax() function through its parent component TwackComponent, but by default it returns only an empty array of data:

public function getAjax($ajaxArgs = []) {
  return array();
}

You can overwrite this function in each component with your own variant that delivers correct data. Let's try this using our HelloWorld component from the beginning as an example:

class HelloWorld extends TwackComponent {
  public function __construct($args) {
    parent::__construct($args);

    $this->title = 'Hello World!';
    if(isset($args['title'])) {
      $this->title = $args['title'];
    }
  }

  public function getAjax($ajaxArgs = []) {
    return [
      'title' => $this->title;
    ];
  }
}

It is that simple! If you strictly follow the rule not to include any data processing and logic in the (HTML-)view, you can simply use all data in the getAjax() function for output. Calling render() on this component will lead to the following output:

{
  "title": "Hello World!"
}

You can also call child components in the getAjax() function to merge or nest the outputs. Just the way you need it.

class HelloWorld extends TwackComponent {
  public function __construct($args) {
    parent::__construct($args);

    $this->title = 'Hello World!';
    if(isset($args['title'])) {
      $this->title = $args['title'];
    }

    // Add and initialise a child-component
    $testChildArgs = [
      'myTestValue'
    ];
    $this->addComponent('TestChild', $testChildArgs);
  }

  public function getAjax($ajaxArgs = []) {
    // Basic output: data of the current page:
    $output = $this->getAjaxOf($this->page);

    // We collect every output of the child-components in $output['children']
    if ($this->childComponents) {
      $output['children'] = [];
      foreach ($this->childComponents as $component) {
        $ajax = $component->getAjax($ajaxArgs);
        if (empty($ajax)) { continue; }
        $output['children'][] = $ajax;
      }
    }

    return $output;
  }
}

The output of this component will be something like that:

{
  "id": 1,
  "name": "home",
  "title": "Homepage",
  "created": 1494796565,
  "modified": 1494796588,
  "url": "/",
  "httpUrl": "https://my-website.dev/",
  "template": {
    "id": 1,
    "name": "home",
    "label": "Home-Template"
  },
  "children": [
    [
      "text" => "This is the getAjax()-output of or TestChild component"
    ]
  ]
}

You see, that you can collect the outputs of all sub-components in an array. Another approach could be to merge the data, to get output that is not nested. You are free to do anything you want.

As you might have noticed, I also took the chance to demonstrate a little helper function. The function getAjaxOf() is included in every TwackComponent and delivers simple output from some very common Processwire classes. It can transform objects of ProcessWire's Page, PageArray, Template, PageImage, PageFile and PageFiles to arrays with the basic data that they contain.


➡️ Continue with 9: Configuration
⬅️ Back to 7: Global components