Including related model data in Page API request
Asked Answered
C

1

6

I've got a relatively simple setup running using SilverStripe 3.2.1 with the restfulserver addon and using a variety of widgets which are associated to a Page using the elemental addon.

When I make a GET request via the API to retrieve some of Page #1's data, I can see the associated ElementAreaID:

# GET /api/v1/Page/1.json?fields=Title,URLSegment,Content,ElementArea 
{
  "Title": "Welcome",
  "URLSegment": "home",
  "Content": "A bunch of HTML here from all the widgets in the page...",
  "ElementArea": {
    "className": "ElementalArea",
    "href": "http://ss.local:3000/api/v1/ElementalArea/11.json",
    "id": "11"
  }
}

If I follow the links through the ElementalArea API calls it will list out all of the elements in my page:

# GET /api/v1/ElementalArea/11.json
{
  "ID": "11",
  "Widgets": [
    {
      "className": "Widget",
      "href": "http://ss.local:3000/api/v1/Widget/9.json",
      "id": 9
    },
    {
      "className": "Widget",
      "href": "http://ss.local:3000/api/v1/Widget/8.json",
      "id": 8
    },
    ...
  ]
}

And if I follow those API paths it will serve up the contents of the latest version of each of the Widgets.

My question is how can I include certain fields from the Widget DataObjects within the original Page field list?

I'd like ideally to have the Content field from each Widget be returned in an array with the initial Page API request.


For reference:

  • A Page has one ElementArea
  • An ElementArea has many Widgets
  • A Widget contains content that I want for my Page
Czech answered 25/2, 2016 at 22:7 Comment(2)
What would that additional array contain? Just the Contents? Or objects, with Widget-ID, href and Content field?Spense
Not too fussed really. Ultimately I need the Content from the widgets in the page via the ElementalArea pivot table to be returned with the Page.Czech
S
2

Preamble: It doesn't seem there's currently a way to output array-like datastructures with the RESTful server module (except for relations of course). The proposed solution is a hack that abuses how JSONDataFormatter formats output.

Since JSONDataFormatter uses forTemplate to render a field before converting it to JSON, we can create our own Object renderer that returns an array instead of a string via forTemplate. This might look like this:

class FlatJSONDataList extends ViewableData
{
    protected $list;

    public function __construct(array $list)
    {
        parent::__construct();
        $this->list = $list;
    }

    public function forTemplate()
    {
        return $this->list;
    }
}

Then in your Page, it should be sufficient to have an additional method, like so:

public function getWidgetContents()
{
    return FlatJSONDataList::create(
        $this->ElementArea()->Widgets()->column('Content')
    );
}

Then you can include WidgetContents in your Field-list to get all widget Content fields in an array:

GET /api/v1/Page/1.json?fields=Title,URLSegment,Content,WidgetContents 
Spense answered 1/3, 2016 at 20:22 Comment(3)
Thanks - I'll give this a go later today. I assume that because the Widget itself isn't a relation of Page (ElementalArea is instead) that it's not available in the field list for Page by default?Czech
@RobbieAverill Yeah, it seems like relation depths greater than one level aren't implemented yet, otherwise something like this would probably be a valid field-name: ElementArea.Widgets. For now I think you're stuck with a hack or you could implement your own subclass of DataFormatter. The latter will mean that you'll have to write more code, but is probably less prone to break with a future update.Spense
Thanks - your answer was incredibly helpful. Unfortunately as it turns out the Elemental addon uses different field names for different data types, so I've had to add a static property to define which fields to return and loop the Elements retrieving each field before formatting it. It's working a charm though - very helpful answer!Czech

© 2022 - 2024 — McMap. All rights reserved.