PHP SPA tutorial part 1
Final product: A simple single-page application website (this will open in a new window).
This is for full-stack developers, back-end developers to get their head around, or hobbyists that are happy hard-coding their websites in only HTML.
In this two-part tutorial, you are going to build a simple single-page application website, which uses ajax to pre-get HTML content for display via PHP on the server. This will demonstrate the fundamentals of a single-page application, whether you intend to go fully static, or if need to serve pages dynamically. Ideally you want everything to be static, but that's not always practical.
Generally speaking, at the most advanced web development level, the end-goal of optimising a site is to retrieve only static files where we can, to minimise processing power on the server and/or to allow files to be cached on a CDN. This is the most scalable way to deliver a website, period. There is no need to take up processing power on a server generating a page dynamically if the page always looks the same, or only changes a few times a day. Otherwise the server is literally re-inventing the wheel every time the refresh button is hit. Ideally for a single-page application, both full-page HTML pages and inner HTML duplicates are generated using automatic HTML file generation. On this website offline, there is a fully adminable back-end using a database, but on the front-end live there are only static HTML files, all except the search results and the support form which are both PHP driven.
A good ACSS SPA setup will give you a rock-solid app, as it fixes all the things that are historically bad with SPAs. With the proper setup of an ACSS-driven SPA, the refresh button works, the browser history navigation works, and you get absolutely none of the "why SPAs are a bad idea" arguments. ACSS SPA websites using pre-rendered HTML content operate like apps - response time could be considered instant.
If you are just starting out how to code, you may want to park this to one side for the moment. It may be too advanced for you to grasp - not so much the ACSS side of it - but you may not have all the back-end information needed to get this to work for you.
If you are happy coding on the back-end, then you should find this method refreshingly simple.
What we need to do:
For this tutorial, on the first loading of a page the full HTML page is loaded up. On subsequent calls to the same page and other pages, we only need the inner HTML content to slot into the right place. This would usually be some sort of div with an ID of "innerContent" or something like that. All inner page content gets loaded up into there, so we don't have to reload the menu or graphics that never change around the content.
Whenever we hit refresh on any page, it will need to show the full page. This is so that people can bookmark or link to your page. So we need two files for each page of the website: a full HTML page, with all the headers and footers, and then a second HTML file with just the inner contents. (Generating these two files automatically for each page of your website from a database is beyond the scope of this tutorial, but not hard to build from scratch and an interesting exercise if you know how to code on the back-end already - it certainly isn't worth investing time in a framework if you are good enough to do it yourself.)
This documentation for the server-side is based on PHP, but other environment tutorials may possibly be added over time if asked for. Most websites are currently built with PHP, so there's a good chance that this article will apply to you, or if you switched from PHP to something else recently, you should at least understand the concepts and hopefully be able to reproduce the same behaviour in your new environment.
This is a tutorial on Active CSS but not PHP, apache, nginx or node. In order to get through this tutorial, you should already know how to set up a basic PHP website with apache or nginx, or whatever web server you use. We won't be going over server config, and server config doesn't need anything special for this tutorial.
The end result:
At the end of the tutorial, you should have created something like this example website. Move around a bit and click on the back and forward arrows and note that the page isn't reloading each time. It is a single-page application website. If you have the Active CSS DevTools extension, you can inspect the events and the config from DevTools, as this website here uses the dev version of the core. The tutorial code that you will write is designed to work off the root, not sub-directories like the example on this website.
Also, the PHP techniques you will see are really, really basic. This is a bare-bones setup. This is so that non-PHP people can read and at least understand the code too.
The basic concepts would (hopefully) apply to any back-end environment.
The Active CSS config code itself should also be the same in any environment.
You are expected to have understood the basics of Active CSS before continuing the tutorial.
Step 1 - Start typing your config
Active CSS needs to know what pages you have on your site. It needs to know, otherwise it won't know what to do with a URL when someone clicks on it.
The thing that makes an SPA an SPA is the method of getting and displaying data. SPAs never reload the page. The page only loads once. We will use ajax to get all our page content from the background, once the page has first loaded.
Create a text file, and for this tutorial place it on the root directory of your web server. Call it "activecss-config.txt".
In your config somewhere, create this. All the single-page stuff goes into a "@pages" declaration. This will contain a list of pages that are or will be on your website, and instructions for what happens when someone tries to go to those pages. You will notice that this is not a common way of doing routing. You are not setting fixed routes. Fixed routes are seriously limited and will lead to further complexities in trying to handle those limitations. It is actually better that fixed routes are not done. Why? Well, if you have fixed routes and then randomly switch pages in the browser history, the chances are your page will break. Any route is actually possible. A website is not an app. How about setting up all possible links in the router? Er... no. What if you have 100 pages and you can get to any page from any page. That's a petabyte file just for the routing. No. What you actually want is a dynamic way to handle pages, which you can set up and forget about. So you add conditions in your config to handle certain scenarios. All will become clear by the end of this tutorial (hopefully).
a:not(.outsideLink):not([target="_blank"]):click, form:not(.outsideLink):submit {
prevent-default: true;
}
@pages {
}
The first declaration is setting up your "a" tags so they don't just go to a brand new page when clicked on.
Note: If you want to later on add an external URL and it is not a 'target="_blank"' that is covered above, just add a class of "outsideLink" to the "a" tag in your HTML, and Active CSS will treat the "a" tag like a regular URL and go to the external page properly.
This tutorial assumes that the whole site is going to be an SPA, so therefore we need this kind of declaration.
Step 2 - Add some pages
We are going to add some pages now. We are going to add an index page (which also has an alias of "/", right?), a blog page, and an "about me" page.
(If you are doing this for real, and following along, you can use your own pages if you already have them named or set up.)
We're going to be a bit ambitious as well. Do three things at once. You know the page title that shows in the browser tab? We are going to add this in there too so that the page title in the browser says what page we are on - all without the page actually reloading.
So we are going to make up a "data-" attribute that we can use to set the title. Let's, together, creatively call this "data-title".
We are going to name some static HTML pages for getting the inner HTML content now as well, and we will create those pages later on.
Fill in some details:
@pages {
"/": data-title="My website" data-ajax="/html/index.html?v={$RAND}";
"/index.html": data-title="My website" data-ajax="/html/index.html?v={$RAND}";
"/blog.html": data-title="Blog" data-ajax="/html/blog.html?v={$RAND}";
"/about-me.html": data-title="About me" data-ajax="/html/about-me.html?v={$RAND}";
}
So to recap, we told Active CSS the url of each page, and we put in a reference to the title of each page, and we told Active CSS where to get static HTML for each page when needed.
It's all about the URL on the left. That URL is assigned a certain set of attributes that we are going to do stuff with.
See the {$RAND} next to the html file? You don't have to have this. It puts in a random number to force a fresh load. All the numbers will be identical, and the forced loading of the HTML file will only apply once per page refresh. Your pages will be in memory once they have loaded for that page session. This {$RAND} variable can be needed, at least in development, because even with a hard-refresh on a page, retrieved files sometimes remain cached thanks to amazing browser "AI", and the latest change to a file may otherwise require the user to clear the cache manually from settings. This can waste a lot of debug time, trying to work out what is going on. For now, you should leave this {$RAND} reference there in the @pages declaration, or if you are generating your page-list data into a config file from the server, you should generate a new number each time you regenerate your page-list to ensure your HTML files don't get cached during development at least. If you are not bothered about fresh data for your clients, or already have a handle on caching, then you could ignore this paragraph and remove the {$RAND} references.
Next...
Step 3 - Set up the ajax calls
In your config, let's set up some Ajax calls. This will control all your page calls when someone clicks on a menu item.
.myPage:click {
url-change: "{@href}" "{@data-title}";
ajax: "{@data-ajax}" get html;
}
Do you see the {@data-ajax} bit above? This refers to the data-ajax attribute in the page-list which we did in Step 2.
Whenever Active CSS receives a click, it looks in the @pages data for the href. If the href is found in @pages (on the far left), Active CSS first places all the attributes and classes found in the @pages declaration associated with that particular href into the link tag on the page (adding to what is there already, if anything), and then processes the click with what is there. Magically, the link then works with your config!
When you are writing links to pages, when you are writing content for your website, you don't want to be bothered setting up classes or IDs or anything else on the routing links on the page. And you don't expect your clients to be doing this if you have built them a CMS on the back-end. You want them just to type in the link and for everything to work automatically. Using this method, the routing classes and attributes get copied into the tag when they click on the link. Active CSS handles the @pages details for that page link before any config events are run. So you can build your page links without worrying about classes or conditions and you only ever need to type the URL into the "a" tag. It will "SPA" because you have done all your setup in the @pages declaration.
We have tried several methods of SPA-type routing, and this method we have ended up with has been by far the most sensible solution.
But what about ".myPage" above? We don't have a myPage class in the page-list yet. So we'll add that next.
Step 4 - Reference the click class in page-list
Let's now put the "myPage" class into your page setup.
@pages {
"/": data-title="My website" data-ajax="/html/index.html?v={$RAND}" class="myPage";
"/index.html": data-title="My website" data-ajax="/html/index.html?v={$RAND}" class="myPage";
"/blog.html": data-title="Blog" data-ajax="/html/blog.html?v={$RAND}" class="myPage";
"/about-me.html": data-title="About me" data-ajax="/html/about-me.html?v={$RAND}" class="myPage";
}
See the pattern we are building up? We just effectively tied in the single-page application href assignments to ajax calls.
The url that you will type in later into an "a" tag now has all the instructions it needs! You will only need to reference the link itself when you are writing actual HTML content for your website. You only have to worry about classes for styling. You don't have to worry about it working in the SPA, because you have done all you need to do for it to work.
There are more things that could be added to the @pages declaration, if you were doing a more complex SPA with conditional requirements, but for this tutorial here this is all you need to do. Yell if you need a more complex example and it isn't yet on the documentation website.
Next...
Step 5 - Put in pre-getting functionality
Copy your ajax command from the click into a new mouseover event, and change it to an ajax-pre-get command.
.myPage:mouseover {
ajax-pre-get: "{@data-ajax}" get html;
}
That is your "pre-getting" sorted out. This is an optional step. You may not think this is necessary on your website and you can leave it out. But if you are looking for user performance improvements, then this can make your site appear to be very fast.
Next...
Step 6 - Handle the ajax return data
What we are going to do, is when we get the html page from the server in string format, we are going to put the contents into a div tag, creatively named with the ID of "content". We only get the HTML string when the ajax call gets back from asking the server for the page, at which point we need to have an afterAjax event to handle the string:
.myPage:afterAjax {
#content {
render: {$STRING};
}
}
That is your config complete for this tutorial.
Here is the full config setup:
a:not(.outsideLink):not([target="_blank"]):click, form:not(.outsideLink):submit {
prevent-default: true;
}
@pages {
"/": data-title="My website" data-ajax="/html/index.html?v={$RAND}" class="myPage";
"/index.html": data-title="My website" data-ajax="/html/index.html?v={$RAND}" class="myPage";
"/blog.html": data-title="Blog" data-ajax="/html/blog.html?v={$RAND}" class="myPage";
"/about-me.html": data-title="About me" data-ajax="/html/about-me.html?v={$RAND}" class="myPage";
}
.myPage:click {
url-change: "{@href}" "{@data-title}";
ajax: "{@data-ajax}" get html;
}
.myPage:mouseover {
ajax-pre-get: "{@data-ajax}" get html;
}
.myPage:afterAjax {
#content {
render: {$STRING};
}
}
In the next tutorial step, you are going to actually make your HTML and PHP pages, set up a terrible-looking menu and get it all working!
You'll even be able to put random page links into pages. Wait and see! The excitement is just sweaty!