Native Neocities Hit-Counter

Since the dawn of the web, hit counters have been a handy way to determine how many people have actually visited your site. Everyone wants an audience, and this provides some concrete validation that there is one.

Basic hit counters simply add one number to the existing value upon every access. Rather than count the site visits, the embedded counter would track its own requests and use that metric to display visitor volume. This could be exploited, however. Back in 1995, a high school frenemy had a website with a counter. I would create a page that embedded the same counter, and proceeded to refresh it endlessly. I'm not sure why I thought making him think that his site was more popular than it truly was, would be a prank, but nevertheless it showed the downside to this basic approach.

As counters improved, they would often filter out duplicate IP address, or factor sessions into to the ultimate count. This would reflect truer unique visits rather than simple page access. Neocities helpfully provides both visitor by session as well as raw page counts. The public profile only shows the user session count.

So with that knowledge, it would seem more beneficial to tap into those numbers rather than a 3rd party counter. Luckily due to the Neocities API, we can do just that. Most APIs require authentication or tokens to use properly, but there is one that will provide a few basic stats without such requirements.

Hope on over to https://neocities.org/api/info?sitename=SITENAME (replace your own site/user name) and see your own info. Or type your sitename below and press the button:

You should see something like this:

Even if the format doesn't look familiar, the data should be easily seen. Each pair has a reference name and a value. Once we capture this block, we will break it down into those pairs and then use the numbers to create the counter. In order to do that, we will will need to use a piece of technology called AJAX. This stands for Asynchronous JavaScript And XML, but you really don't need to know any of that. All that we care about, is its ability to load some other content even after your page has finished loading.

Let's take a look at the code. If you want to follow along, either create a test page or add this on you current site right above your final </body> tag.

It looks like a bunch of cluttered commands, and if you saw the earlier implementations of AJAX, you'd be relieved that it appears this nice. So we start and end with <script> tags so we can run some JavaScript at that point. If you already have some JavaScript on your site, you can omit those and include the inner portion inside an existing script block. Right in the middle, I've left an area for our eventual code (the [// DO SOMETHING] part). As for the rest, we basically create an object (xhttp) that will facilitate the fetching of the Neocities API stats. The bottom two command define which site to retrieve, and then finally issue the send() command. The center portion does some error checking to make sure it actually worked and then just gives you an area to execute whatever you want to do with the result.

Once thing you might have caught, was the URL that we used. Rather than https://neocities.org/api/info?sitename=...., I appended https://cors-anywhere.herokuapp.com/. Due to some technical and security measures that prevent grabbing data from sites that aren't your own, this middle-man sort of makes it work. If they ever stop providing this service, we'll have to figure something else out, but for now it gets the job done.

At this point, the browser has grabbed that block of text, but any attempt to use it will only result in pasting the entire thing. We only want to grab the relevant data, so we must first parse it. The format that we saw above was known as JSON encoding. It's a way to represent paired key/value combinations in a pure text string. In the center of our original code, where the // DO SOMETHING part was, enter this:

So what did that do? Well, we've create a new variable called site_data that will house all of the stats in a way that JavaScript can easily pull the values if we know the reference name. The second half of the line: JSON.parse is the command to achieve this conversion. this.responseText is part of the AJAX block that forwards whatever was requested by the given URL. In this case, it is the raw JSON string that you saw when visiting the API page in your browser earlier.

At this point, we could start printing some values to the screen and call it a day, but let's first look at how JavaScript will work with the values now that they can be read. The JSON.parse command broke apart the text and turned it into an object. If you are familiar with arrays, it will work in a similar fashion, except we won't reference the values using brackets (array_name["key"]), but rather using a period (.) (array_name.key).

Here's the raw string again just so we can look at the data:

The values in our block are actually nested, so the only values we can access at the top level are: "result" and "info". If you want to test the script before going further, add this below the JSON line we added a moment ago:

If your entire page disappeared and was replaced with the word success, then you have connected to the API, parsed the data, and retrieved a value. However we don't want to flush our entire page, so we ultimately won't be using the document.write() approach. By its nature, that command will only write in-line text as your page is loading. Since AJAX works after your page is finished, it will overtake any other page content. We'll implement something else shortly, but for now we can test this way.

At least we now know that we can access the data. If we try to replace "result" with "info", we will get an output of: [object Object]. Since the data is nested, we have objects within other objects and will need to drill down to get the information we want. Luckily, we only need to append an additional period to get there. Try using:

You should now see your sitename listed. On your own, try to pull up either you view count or hit count. If you simply replaced "sitename" with either "views" or "hits", then you were correct. All of the other data is also available for us to use, but we will only be using one of the hit counter values for now.

Let's work on getting something to appear on the page without destroying all of the content. Right above the <script> tag (or wherever you want your counter to display), add this:

Then remove the document.write(....) code that we just added, and replace it with:

You could also use "hits" instead of "views" if you want to use that. So we've created a <span> element that has an id of "hitcount". We then use the results of the AJAX to push the extracted value into it. It should look something like this:

Now if you are happy with this, we can end there. But let's see what else we can do. The lack of separating commas sort of annoys me. Granted I shouldn't be bellyaching about having over a million hits, but I do want it to look nicer. Remove the previous document.getElementById... line and replace it with this:

If you don't care how that worked, then you can also just end here. But I'll explain. First we created a new variable called num_arr, which will be an array, basically a collection of things. The second half takes our hit count (site_data.info.views) converts it to a text string using toString(), since JavaScript can get picky about such things. Finally we append the split("") command to split the string into individual characters. Normally you would insert something into the quotes, like a comma or space depending on context, but a blank value will just give you each letter or number on its own.

Then we created a new variable called num_str which will eventually house the number with the commas. We set it to be blank initially. Below that is a for {} loop. We feed it a counter called i, which starts at 0, and will loop until it reaches the size of the array (in our case the number of digits in the hit-count string). Keep in mind all arrays start at a value of 0 rather than 1.

As we progress through the digits, we append one character at a time and add a comma if, and only if, the remaining length of the number is a multiple of 3. (num_arr.length-1 - i) % 3 == 0 achieves this by first finding the difference between the length of the number (in digits) and which position we currently are at. % 3 means divide by 3, but only return the remainder, not the actual mathematical result. If a remainder is 0, then it is a multiple. We also make sure that we are not at the last digit, since that technically is a multiple of 3, and we don't want a comma at the end.

The last line inserts the final num_str into the our HTML placeholder rather than the original raw value.

You should see:

BUT WAIT! We want to go further. Let's say the text output is boring and we want it to look like an odometer or digital counter. First we will need some images. You may grab these for the time being, but feel free to find your own. Just right-click and save them in the same folder as your .html page for now.

Revise the line starting with num_str += ... to be:

It's basically the same thing, however we're wrapping the number with an image tag. Since the images are named c0.gif ... c9.gif, we just output the image and swap the relevant digit in the filename. If you want to keep your images in a subfolder for organization's sake, just change the "src" parameter to src="images/c... or similar.

We should have the following now:

Just to recap the code in case you want to simply copy&paste. Remember to put your sitename in! Here's the text-only counter:

And if you want to use the image-based counter.

Bonus Code

So I mentioned that we could also use the other values in the API, but only a few were worth targetting. Obviously sitename, created date, domain (if applicable), and tags are all rather static data. You might as well just hard code those, not that you would have much use on your own site. Perhaps if you were running some live site comparisons for Neocities friends, they may come into play.

That said, the 'last_updated' field would probably have some use. There's many times where you want to let visitors know the last time the site was touched. We can use this field, much in the same way as the hit count. Let's start simply:

So basically, create another variable called date_str, assign it site_data.info.last_updated, then insert it into a new block of HTML with the appropriate label. It will look like this:

Last Updated: Fri, 07 Feb 2020 11:25:43 -0000

...which from a technical standpoint is correct, but perhaps a little too much information. We don't really need the timestamp down to the second, and perhaps the date format could use a little rearranging. We could use a method similar to the hit counter, where we chop up the string and piece it together, but it's probably best to let the scripting language handle the messy work. The process will basically be: Turn the String into a Date object, then use a method within the Date object to spit out a formated string to our liking. Let's start by converting the string:

So we created date_obj and assigned it a new Date object while feeding it the API string. If we omitted the date date, it would default to the current time. Then we also moved the final HTML inserts to reference the new object variable. (You could have also simplified the first two lines as: var date_obj = new Date(site_data.info.last_updated);) Refreshing our code would now show:

Last Updated:

...which is even worse. But we have the date in a format that Javascript understands. Days, Months, Years, all make sense to a human, but machine code only wants a numerical value. A computerized date is measured in milliseconds since the start of the epoch (whatever Year 0 is in the system of interest). For old mainframes, this was Jan 1, 1900, which caused concern over the Y2K issue. Browser dates are generally Jan 1, 1970. You can of course have dates prior to that, as the scripting language will simply have negative millisecond values.
The current Milliseconds right now are:

But history aside, what do we then need to do in order to spit out a cleaner date string? Well, there's a few methods. You can choose which you like the best. We'll start with some included String Methods:

.toDateString()
.toLocaleDateString()
.toLocaleString()
.toUTCString()

The Locale methods will format the date based on your region, which may swap around the month/day. Choosing any of the above would look like this:

If you still want to make it more personalized, you can reassemble the date in any way you want using the .get methods:

.getDate()
.getDay()
.getFullYear()
.getMonth()

Some of those returned values make since, while others don't. Date and FullYear match the day of the month, and the current calendar year. Month is a numberical value starting at 0 (like arrays), as is Day, which specifies the day of the week. You will need to add '1' to both of those to make them meaningful. Here's a basic output using these:

Last Updated:

If you want to conver the numbers to text labels, first create an array with the list of options, and use the numberical value to display the correct one:

Last Updated:

I converted some of the dashes into spaces and commas. You can play around however you see fit. If you want to use short labels for the Days or Months (Sun, Mon; Jan, Feb, etc...) just change them out in the array.

As always, if you have any questions please drop me a line on my profile page.

Back to Tutorial List