Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: AJAX route option #2310

Closed
MGatner opened this issue Oct 8, 2019 · 12 comments
Closed

Feature: AJAX route option #2310

MGatner opened this issue Oct 8, 2019 · 12 comments
Labels
new feature PRs for new features
Milestone

Comments

@MGatner
Copy link
Member

MGatner commented Oct 8, 2019

I would like a way to differentiate route definitions for AJAX calls. It would work similar to the hostname/subdomain limits:

$routes->add('from', 'to', ['ajax' => true]);

I typically use forms that either AJAX submit or POST directly based on a few factors, and it would be helpful to have them point to one URL but have AJAX requests use a separate API controller, something like:

$routes->resource('photos', ['ajax' => true, 'controller' =>'App\Controllers\API\Photos']);
$routes->presenter('photos');
@jim-parry
Copy link
Contributor

AJAX isn't the only way that calls might come in to an "API endpoint". A server-side app that connects further upstream would likely use a CURLRequest to do so.

Aren't resources supposed to be an endpoint regardless of origin?

@MGatner
Copy link
Member Author

MGatner commented Oct 8, 2019

Sure, but in this scenario there would be a separate resource route explicitly for the API:

$routes->resource('photos', ['ajax' => true, 'controller' =>'App\Controllers\API\Photos']);
$routes->presenter('photos');
$routes->resource('api/photos', ' ['controller' =>'App\Controllers\API\Photos']);

The route option would be a way to redirect AJAX traffic to the existing API resource so that frontend tech (even just HTML) wouldn't need to care about differentiating endpoints as long as it knew the type of request it was making.

@MGatner
Copy link
Member Author

MGatner commented Oct 9, 2019

Thought about this more during evening dishes... here's what I think the issue is I am trying to get at. A table will help:

Web page AJAX CURL
index view view/code code
show view view/code code
edit view form ???
update redirect code code
new view form ???
create redirect code code
remove view form ???
delete redirect code code
--------- ---------- ----------- ------
auth browser browser token

I've taken some liberties, but here's a mapping of the eventual Controller methods to their response type for a typical browser page load, an AJAX request, and an API interface (e.g. CURL). Notice how the AJAX column responds sometimes like a page load and sometimes like an API request. This is the issue I'm trying to resolve as a developer structuring the backend controllers for these scenarios.

The dump solution is to put everything into a single controller and map routes by their methods and have a ton of extra code parsing it all out in the controller. Yuck.

The 1:1 solution is to have three controllers: Controller, ControllerAJAX, and API/Controller. This keeps things nicely split up but duplicates a lot of code, since likely many of the AJAX endpoints will do the same exact thing as the API endpoints. This solution still needs a way to route AJAX requests separately.

What I'm trying at is three resource routes to two controllers (see my comment just above) that would have AJAX requests share the API endpoints (for relevant methods) yet still use the presenter controller for HTML queries. This bundles together the responses that need ResponseTrait and allows for unified format control, but still lets the frontend tech be resource-agnostic. This solution relies on AJAX routing, the original request.

I hope that makes things more clear - probably the opposite though. :)

@jim-parry
Copy link
Contributor

This feels over-complicated.
It sounds like sometimes you want an AJAX call to deal with a REST API, and sometimes you want it to pull a view from the server. Wouldn't it be easier to have your JS call the appropriate endpoint, resource or presenter, rather than have a third endpoint which is sometimes one and sometimes the other? That doesn't sound like a general usecase to me.

@jim-parry
Copy link
Contributor

In your table, when you have "code", is that an indication that data only is being exchanged?

@MGatner
Copy link
Member Author

MGatner commented Oct 9, 2019

Wouldn't it be easier to have your JS call the appropriate endpoint, resource or presenter

That's my current workaround. It gets the job done, but it still feels like it leaves a "gap". My examples aside, the fact is that AJAX calls represent an additional layer of consideration because they are a "hybrid" of sorts, so not being able to route (or filter, with that broken too, #2314) on them leaves a Controller somewhere checking $this->request->isAJAX() on every relevant method.

when you have "code"

It's all code, excuse my shorthand. :) By "code" I mean it is unlikely to be formatted HTML, and that the headers are likely to carry crucial information. "form" indicates loading a partial view (e.g. might normally be included in a larger layout or template), such as an edit form pre-filled with an entity's values. "view" is preformatted, styled HTML ready for the browser.

@jim-parry
Copy link
Contributor

Makes sense, but I don't think your "workaround" is a workaround.
I see the presenter returning views, some of which might contain JS to request updates through AJAX calls to the API. I don't know that we can "dictate" which ones should or shouldn't, which is what your middle scenario feels like.
Your approach feels like "opinionated REST", where not everyone would share the same expectations. It would be useful, agreed, but generalizable and baked into the framework?
Maybe it is best left until the next level, eg. GraphQL or HATEOS. I am not sure those can be sensibly handled in the framework, and that or your AJAXController might be handled as addins.

@MGatner
Copy link
Member Author

MGatner commented Oct 9, 2019

You're definitely more knowledgable on REST than I am so I'll take your word on that. I spent some time reading Martin Fowler and I have some new ideas for my implementation - but I do still think making AJAX calls re-routable is a benefit. One of the framework's boundaries (weaknesses?) is that there is no way to change controllers by the time developer code hits except by issuing a RedirectResponse, so this puts pressure on routing to be able to get to precisely the right controller.

@jim-parry
Copy link
Contributor

https://martinfowler.com/articles/richardsonMaturityModel.html is the most sensible explanation I have seen. It feels like we are trying to make the best out of "level 2" REST.
GraphQL is getting a lot of traction elsewhere, and support for that could be a good thing. I don't know is it would address what you are trying to do with an AJAXController, or if it would be best handled by an addin ... those are questions for shortly down the road :-/

@lonnieezell lonnieezell added the new feature PRs for new features label Oct 17, 2019
@jim-parry
Copy link
Contributor

Please raise future feature requests in the forum, thanks :)

@lonnieezell lonnieezell added this to the 4.0.* milestone Oct 22, 2019
@benrogmans
Copy link
Contributor

I think the framework doesn't need to accommodate for this. Simply enough I'm using a global filter to only allow ajax calls to certain methods (those starting with 'ajax_').

class AjaxFilter implements FilterInterface
{
    /**
     * @param RequestInterface $request
     * @return mixed
     */
    public function before(RequestInterface $request)
    {
        if (strpos($request->detectPath(), '/ajax_') !== false && !$request->isAJAX()) {
            throw new PageNotFoundException();
        }
    }

@MGatner
Copy link
Member Author

MGatner commented Jan 31, 2020

@benrogmans That's definitely one way to work around it. See also this issue #2454 on the inconsistencies of isAJAX().

I think I agree with the concept behind your implementation: a developer will have to make these choices for the endpoints from within his or her own app, not rely on external factors. In light of this and the new troubles of isAJAX() I'm going to close this request.

@MGatner MGatner closed this as completed Jan 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature PRs for new features
Projects
None yet
Development

No branches or pull requests

4 participants