I want to share with you my framework for PHP SPA creation. It is a client side framework that supports PHP server side rendered views. So, basically, it changes nothing about how PHP works, just facilitates the organization of your ajax calls to retrieve views from the server and update partial pages client side. If you were to build your own home grown SPA against PHP you would end up writing the same kind of code using async calls. The difference is that this framework already has all the plumbing, edge cases, error handling, and user interaction control ready for you. It can be found as open source on Github and is called WebRocketX. There is an example implementation of WebRocketX implemented with PHP in the templates folder along with the source code. Let me know if you have any questions or difficulties.
That sounds fairly similar to htmx. Have you ever compared the two?
Thanks for your feedback. Yes, I’ve looked at htmx. It is more broad with many tools and one of those tools includes being able to make async calls with “tags”. WebRocketX is focused on view management and mangement in a SPA with features not directly covered by htmx. For example:
-
It stores all views in a stack so that the web application behaves like a desktop application as far as holding on to any previous views in browser memory. If you navigate “n” pages deep and then use the browser back button it will restore each page from memory, without talking to the server and regardless of whether those views came from posts. This means that even user input will still be sitting there. Imagine your favorite airlines web application. Wouldn’t it be nice if all your search data was safely saved instead of being wiped or partially lost, when you return to the search page. This is all default behavior.
-
It allows you to control page behavior using capsules, with declarative programming. For example, marking a page as reloadPage = true will resubmit the same request, when you go back to the page, with all the same params that was used to retrieve the page previously and even execute the successful callback in the same context and closure it was originally. No more stale views if you don’t want them to be stale.
-
There are many other capsule attributes that allow declarative page control which means the page can be entered from many different use cases and flows and will behave the same in each one.
-
Structured error handling. Capsules have types and one of those types is an error. This allows errors to be standardized in your server side framework and delivered cleanly, without having to see error pages show up as a sub injection or break a callback.
-
Integrated user interaction control. During critical times of user interaction the user is blocked from double pushing buttons etc, and shown an hour glass. Silent mode is also available if this default behavior is not desired.
HTMX is a collection of tools whereas WebrocketX is a complete client side SPA solution for server rendered HTML.
Alright, that sounds good. Thanks for getting back to me
No I can’t imagine that I want a search result being cached. Next time I open the view there will be new data which I need to get from the server….
Also, there is no need to implement a caching as the browsers cache is already doing exactly what you explain without any needed additional framework. If there is a static file, it is cached, if there is a dynamic XHR request it is not cached. No way you can do it better with some self coded stuff…
I think I need to explain the use case better. Let’s say you are booking a flight on an airlines application.
Imagine page A is your search criteria. That is what you want to be cached. Starting airport, departure date, destination airport, return date, and number of passengers.
Page B is your search results. You might or might not want that cached. Page C is the detail page that you get when you click on one of your search results. Probably never want that cached.
More about page A:
You don’t want to return to your search page using the browser back button and have to reenter everything. You only want to change maybe one thing like your departure date. Many airline booking applications have this exact problem. The reason being that when you return to the search page they will have had to persist your search criteria to the server and then re-display them when you go back. You might say, well the browser already caches this by default. Actually it does not, especially if that page was rendered by a post rather than a get. Chrome tried years ago to refill out your fields for you but it generally does a bad job especially with fields that are more than just text. Furthermore, not only did the user enter data in this page, it also could have been transformed by script in a number of ways, like new sections could have been injected, certain parts of the page made visible that were hidden on original rendering. I wrote code in the full page refresh paradigm for 10 years in struts 1 and I can definitely tell you that the back button is one of the most difficult things to deal with. Caching a page in memory on the browser solves all these problems. You get the page exactly as it was when you left it. It’s been there all along. Keep in mind this also is super helpful when there is a validation error server side when submitting your search criteria. Since you are making an async call, when something goes wrong, you did not lose what they typed in either. A full page refresh paradigm, on the other hand, leaves your page. It’s stateless and gone unless you go through a bunch of gymnastics to persist this temporary page state to the server.
Page B. Your search results page.
Mark the capsule on this with reloadPage=true and you are good to go. The framework will retrieve a fresh copy of this page anytime you navigate back to it. As a developer you don’t have to write this code yourself. The framework stored your request and your callback and the closure context of the callback.
Page C. You details page.
Nothing new here. You can decide if you want to cache it or not.
Notice that application page flow works like a stack. If you have a flow like A,B,C,D and you jump from D to B then C will be popped. This is how application logic needs to work unlike static flows, because application pages have state.
By the way, most airlines have fixed this problem these days. You used to have to re-enter everything on the search screen even a couple years ago. However, they are doing this the “hard way”. Hit f12 and watch the network tab when you go back to the search page. They do a huge amount of communication with the server. If they were using WebRocketX they would not need to communicate with the server at all. What a money saver on your amazon ec2 bill that would be right?
The only criterion a browser uses to decide whether or not something should be cached is the response headers of the response. Whether the request was made via regular browing or XHR is completely irrelevant.
Yes correct, the headers are key for total control, and the browser does not differentiate between content brought in by an ajax call or by full page. There are many nuances to the default behavior. For example, post requests are not cacheable by default. In fact, using the back button on a post will usually get you the
“Confirm Form Resubmission. The page that you’re looking for used information that you entered. Returning to that page might cause any action you took to be repeated. Do you want to continue?”
Get requests will be cached by default as long as the request has the same param values as last time. The length of the caching is dependent on the page headers.
By using a single page application you are able to use the back button without triggering the browsers default behavior because you are not even trying to go to the server. In the case of WebRocketX it uses hash navigation which convinces the browser that you are only navigating between anchors in a single page rather than leaving a page. A nice feature of WebRocketX is that by default user entered input is left in place.
On the other hand, Angular does not do this for you, which was a big surprise to me the first time I developed with it. It instead renders the page empty again. In order to preserve user input in Angular you have to attach a service to the page that keeps the user entered state and restores it. Yes, the state is all kept client side so at least you aren’t having to get it from the server, but you have to do extra work to make this happen. Furthermore, DOM view states like say you changed the color of the page are lost, so any kind of manipulation of the page is gone and has to be saved and restored, which is even harder to persist than user input. By stashing the actual DOM object WebRocketX keeps the page in the same state no matter how many weird things you did to it. You could have pulled in 10 different innerHTML injections and they will all be there when you go back.