{
    "href": "/post/2017/12/28/solving-the-widget-problem-in-adr/",
    "relId": "2017/12/28/solving-the-widget-problem-in-adr",
    "title": "Solving The \"Widget Problem\" In ADR",
    "author": "pmjones",
    "markup": "html",
    "tags": [
        {
            "href": "/tag/adr/",
            "relId": "adr",
            "title": "Action Domain Responder",
            "author": null,
            "created": "2020-08-17 21:07:42 UTC",
            "updated": [
                "2020-08-17 21:07:42 UTC",
                "2020-09-22 15:41:16 UTC",
                "2020-10-14 18:20:29 UTC",
                "2020-10-14 18:36:31 UTC",
                "2020-10-14 18:36:53 UTC",
                "2020-10-14 18:37:08 UTC",
                "2020-10-14 18:37:48 UTC",
                "2020-10-14 18:39:26 UTC",
                "2020-10-14 19:03:17 UTC",
                "2020-10-14 19:03:35 UTC",
                "2020-10-26 18:12:53 UTC"
            ],
            "markup": "markdown"
        },
        {
            "href": "/tag/php/",
            "relId": "php",
            "title": "PHP",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        },
        {
            "href": "/tag/programming/",
            "relId": "programming",
            "title": "Programming",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        }
    ],
    "created": "2017-12-28 16:15:42 UTC",
    "updated": [
        "2017-12-28 16:15:42 UTC"
    ],
    "html": "<p>The \u201cwidget problem\u201d is when you have several panels or content areas on an HTML page that have different data sources. You might have a main content area, then a calendar off to the side, with perhaps a list of recent news items or blog posts, a todo or reminder widget, and maybe other information panels.  The problem is that they each have different data sources, and may not always be displayed in every circumstance -- perhaps they are only shown to some users based on their preferences, or under certain conditions.</p>\n<p>So how, in <a href=\"http://pmjones.io/adr\">Action-Domain-Responder</a>, do we get the \u201cright\u201d data for the set of widgets that are actually going to be displayed? (We\u2019ll presume here that the entire page is being rendered server-side, for delivery as a whole to the client.)</p>\n<p>The answer is \u201cthe same as with anything else\u201d \u2013 we just have more kinds of data to get from the domain.  The domain has all the data needed, and the knowledge necessary to figure out which data elements to return.</p>\n<p>Let\u2019s start with the Action, which is intentionally very spare: it only collects input, calls the Domain with that input, then invokes the Responder:</p>\n<pre><code class=\"php\">&lt;?php\nclass PageAction\n{\n    public function __construct(\n        PageService $domain,\n        PageResponder $responder\n    ) {\n        $this-&gt;domain = $domain;\n        $this-&gt;responder = $responder;\n    }\n\n    public function __invoke(HttpRequest $request)\n    {\n        $payload = $this-&gt;domain-&gt;fetchPageData(\n            $request-&gt;getAttribute('sessionId'),\n            $request-&gt;getAttribute('pageName')\n        );\n        return $this-&gt;responder-&gt;respond($request, $payload);\n    }\n}\n</code></pre>\n<p>The domain work is where the heavy lifting happens. The example below returns the domain objects and data wrapped in a <a href=\"https://github.com/auraphp/Aura.Payload\">Domain Payload</a> object.</p>\n<pre><code class=\"php\">&lt;?php\nclass PageService\n{\n    // presume $userService, $sessionService, and $database\n    // dependencies are injected via constructor\n\n    public function fetchPageData($sessionId, $pageName)\n    {\n        $session = $this-&gt;sessionService-&gt;resume($sessionId);\n        $user = $this-&gt;userService-&gt;fetch($session-&gt;userId);\n\n        // the main page data\n        $mainData = $this-&gt;fetchMainData($pageName);\n        if (! $mainData) {\n            return new Payload('NOT_FOUND');\n        }\n\n        // an array of widgets to show\n        $widgets = [];\n\n        // different users might prefer to see different widgets\n        foreach ($user-&gt;getWidgetsToShow() as $widgetName) {\n            $method = \"fetch{$widgetName}Data\";\n            $widgets[$widgetName] = $this-&gt;$method();\n        }\n\n        $this-&gt;sessionService-&gt;commit($session);\n\n        return new Payload('FOUND', [\n            'user' =&gt; $user,\n            'page_name' =&gt; $pageName,\n            'main_data' =&gt; $mainData,\n            'widgets' =&gt; $widgets\n        ]);\n    }\n\n    protected function fetchMainData($page_name)\n    {\n        return $this-&gt;database-&gt;fetchRow(\n            \"SELECT * FROM pages WHERE page_name = ? LIMIT 1\",\n            $page_name\n        );\n    }\n\n    protected function fetchTodoData() { ... }\n\n    protected function fetchRemindersData() { ... }\n\n    protected function fetchUpcomingEventsData() { ... }\n\n    protected function fetchCalendarData() { ... }\n\n}\n</code></pre>\n<p>Finally, the Responder work becomes as straightforward as: \u201cIs there Todo data to present? Then render the Todo widget via a Todo helper using the Todo data.\u201d It could be as simple as this:</p>\n<pre><code class=\"php\">&lt;?php\nclass PageResponder\n{\n    // ...\n\n    public function respond(Request $request, Payload $payload)\n    {\n        if ($payload-&gt;getStatus() == 'NOT_FOUND') {\n            return new Response(404);\n        }\n\n        $output = $payload-&gt;getOutput();\n\n        $html = '';\n        $html .= $this-&gt;renderHeader($output['page_name'];\n        $html .= $this-&gt;renderNav();\n        $html .= $this-&gt;renderMain($output['main_data']);\n\n        foreach ($output['widgets'] as $widgetName =&gt; $widgetData) {\n            $method = \"render{$widgetName}Html\";\n            $html .= $this-&gt;$method($widgetData);\n        }\n\n        return new Response(200, $html);\n    }\n\n    protected function renderHeader($request, $pageName) { ... }\n\n    protected function renderNav($request) { ... }\n\n    protected function renderMain($request, $mainData) { ... }\n\n    protected function renderTodoHtml($request, $widgetData) { ... }\n\n    protected function renderRemindersHtml($request, $widgetData) { ... }\n\n    protected function renderUpcomingEventsHtml($request, $widgetData) { ... }\n\n    protected function renderCalendarHtml($request, $widgetData) { ... }\n?&gt;\n</code></pre>\n<p>One alternative here is for some client-side Javascript to make one additional call per widget or panel to retrieve widget-specific data, then render that data on the client. The server-side work becomes <em>less</em> complex (one action per widget, and transform the data to JSON instead of HTML) \u2013 but the client-side work becomes <em>more</em> complex, and you have more HTTP calls back-and-forth to build the page.</p>\n"
}
