Tag: Built

How I Built my SaaS MVP With Fauna ($150 in revenue so far)

Are you a beginner coder trying to implement to launch your MVP? I’ve just finished my MVP of ReviewBolt.com, a competitor analysis tool. And it’s built using React + Fauna + Next JS. It’s my first paid SaaS tool so earning $ 150 is a big accomplishment for me.

In this post you’ll see why I chose Fauna for ReviewBolt and how you can implement a similar set up. I’ll show you why I chose Fauna as my primary database. It easily stores massive amounts of data and gets it to me fast.By the end of this article, you’ll be able to decide on whether you also want to create your own serverless website with Fauna as your back end.

What is ReviewBolt?

The website allows you to search any website and get a detailed review of a company’s ad strategies, tech stack, and user experiences.

Reviewbolt currently pulls data from seven different sources to give you an analysis of any website in the world. It will estimate Facebook spend, Google spend, yearly revenue, traffic growth metrics, user reviews, and more!

Why did I build it?

I’ve dabbled in entrepreneurship and I’m always scouting for new opportunities. I thought building ReviewBolt would help me (1) determine how big a company is… and (2) determine its primary distribution channel. This is super important because if you can’t get new users then your business is pretty much dead.

Some other cool tidbits about it:

  • You get a large overview of everything that’s going on with a website.
  • What’s more, every search you make on the website creates a page that gets saved and indexed. So ReviewBolt grows a tiny bit bigger with every user search.

So far, it’s made $ 150, 50 users, analysed over 3,000 websites and helped 5,000+ people with their research. So a good start for a solo dev indie-hacker like myself.

It was featured on Betalist and it’s quite popular in entrepreneur circles. You can see my real-time statistics here: reviewbolt.com/stats

I’m not a coder… all self-taught

Building it so far was no easy feat! Originally I graduated as an english major from McGill University in Canada with zero tech skills. I actually took one programming class in my last year and got a 50%… the lowest passing grade possible.

But between then and now a lot has changed. For the last two years I’ve been learning web and app development. This year my goal was to make a profitable SaaS company but to also to make something that I would find useful.

I built ReviewBolt in my little home office in London during this massive Lockdown. The project works and that’s one step for me on my journey. And luckily I chose Fauna because it was quite easy to get a fast, reliable database that actually works with very low costs.

Why did I pick Fauna?

Fauna provides a great free tier and as a solo dev project, I wanted to keep my costs lean to see first if this would actually work.

Warning: I’m no Fauna expert. I actually still have a long way to go to master it. However, this was my setup to create the MVP of ReviewBolt.com that you see today. I made some really dumb mistakes like storing my data objects as strings instead of objects… But you live and learn.

I didn’t start off with Fauna…

ReviewBolt first started as just one large google sheet. Every time someone made a wesbite search, it pulled the data from the various sources and saved it as a row in a google sheet.

Simple enough right? But there was a problem…

After about 1,000 searches Google Sheets started to break down like an old car on a road trip…. It was barely able to start when I loaded the page. So I quickly looked for something more stable.

Then I found Fauna 😇

I discovered that Fauna was really fast and quite reliable. I started out using their GraphQL feature but realized the native FQL language had much better documentation.

There’s a great dashboard that gives you immediate insight for your usage.

I primarily use Fauna in the following ways:

  1. Storage of 110,000 company bios that I scraped.
  2. Storage of Google Ads data
  3. Storage of Facebook Ad data
  4. Storage of Google Trends data
  5. Storage of tech stack
  6. Storage of user reviews

The 110k companies are stored in one collection and the live data about websites is stored in another. I could have probably created created relational databases within fauna but that was way beyond me at the time 😅 and it was easier to store everything as one very large object.

For testing, Fauna actually provides the built-in web shell. This is really useful, because I can follow the tutorials and try them in real-time on the website without load visual studio.

What frameworks does the website use?

The website works using React and NextJS. To load a review of a website you just type in the site.

Every search looks like this: reviewbolt.com/r/[website.com]

The first thing that happens on the back end is that it uses a Fauna Index to see if this search has already been done. Fauna is very efficient to search your database. Even with a 110k collection of documents it still works really well because of its use of indexing. So when a page loads — say reviewbolt.com/r/fauna — it first checks to see if there’s a match. If a match is found then it loads the saved data and renders that on the page.

If there’s no match then the page brings up a spinner and in the backend it queries all these public APIs about the requested website. As soon as it’s done it loads the data for the user.

And when that new website is analyzed it saves this data into my Fauna Collection. So then the next user won’t have to load everything but rather we can use Fauna to fetch it.

My use case is to index all of ReviewBolt’s website searches and then being able to retrieve those searches easily.

What else can Fauna do?

The next step is to create a charts section. So far I built a very basic version of this just for Shopify’s top 90 stores.

But ideally I have one that works by the category using Fauna’s index binding to create multiple indexes around: Top Facebook Spenders, Top Google Spenders, Top Traffic, Top Revenue, Top CRMs by traffic. And that will really be interesting to see who’s at the top for competitor research. Because in marketing, you always want to take inspiration from the winners.

But ideally I have one that works by the category using Fauna’s index binding to create multiple indexes around: Top Facebook Spenders, Top Google Spenders, Top Traffic, Top Revenue, Top CRMs by traffic. And that will really be interesting to see who’s at the top for competitor research. Because in marketing, you always want to take inspiration from the winners.

export async function findByName(name){    var data = await client.query(Map(     Paginate(       Match(Index("rbCompByName"), name)     ),     Lambda(       "person",       Get(Var("person"))     )   ))   return data.data//[0].data }

This queries Fauna to paginate the results and return the found object.

I run this function when searching for the website name. And then to create a company I use this code:

export async function createCompany(slug,linkinfo,trending,googleData,trustpilotReviews,facebookData,tech,date,trafficGrowth,growthLevels,trafficLevel,faunaData){    var Slug = slug   var Author = linkinfo   var Trends = trending   var Google = googleData   var Reviews = trustpilotReviews   var Facebook = facebookData   var TechData = tech   var myDate = date   var myTrafficGrowth = trafficGrowth   var myGrowthLevels = growthLevels   var myFaunaData = faunaData     client.query(     Create(Collection('RBcompanies'), {       data: {         "Slug": Slug,         "Author": Author,         "Trends": Trends,         "Google": Google,         "Reviews": Reviews,         "Facebook": Facebook,         "TechData": TechData,         "Date": myDate,         "TrafficGrowth":myTrafficGrowth,         "GrowthLevels":myGrowthLevels,         "TrafficLevels":trafficLevel,         "faunaData":JSON.parse(myFaunaData),       }     })   ).then(result=>console.log(result)).catch(error => console.error('Error mate: ', error.message));  }

Which is a bit longer because I’m pulling so much information on various aspects of the website and storing it as one large object.

The Fauna FQL language is quite simple once you get your head around. Especially since for what I’m doing at least I don’t need to many commands.

I followed this tutorial on building a twitter clone and that really helped.

This will change when I introduce charts and I’m sorting a variety of indexes but luckily it’s quite easy to do this in Fauna.

What’s the next step to learn more about Fauna?

I highly recommend watching the video above and also going through the tutorial on fireship.io. It’s great for going through the basic concepts. It really helped get to the grips with the fauna query language.


Fauna was quite easy to implement as a basic CRUD system where I didn’t have to worry about fees. The free tier is currently 100k reads and 50k writes and for the traffic level that ReviewBolt is getting that works. So I’m quite happy with it so far and I’d recommend it for future projects.

The post How I Built my SaaS MVP With Fauna ($ 150 in revenue so far) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

How The Web is Really Built

My 2020 was colored by the considerable amount of time I spent analyzing data about CSS usage in the wild, for the CSS chapter of the Web Almanac, by the HTTP Archive. The results were eye-opening to me. A wake-up call of sorts. We spend so much time in the bubble of bleeding-edge tech that we lose touch with how the web is really built. Most of the web favors old, stable tech instead of new bling.

CSS-in-JS? Only 2% of websites.

React? Only 4%.

Service Workers? Less than 1%.

Houdini? Practically 0%

Nobody uses jQuery anymore, right? Wrong. It appears on 83% of all websites! Everyone uses Jamstack instead of bloated CMSes, right, right? Wrong. Static site generators are used in less than 1% of websites, WordPress powers one-third of the Web.

A lot of the code we found could have been written a decade ago. When new tech ends up being used sufficiently to appear in these stats, it’s often because the primary driver is a popular library or framework. Effectively, we (standards folks, browser implementers, etc.) are building tech for tooling authors, who are the ones really building tech for the average web developer. Quite a perspective shift, isn’t it?

The post How The Web is Really Built appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



Halfmoon: A Bootstrap Alternative with Dark Mode Built In

I recently launched the first production version of Halfmoon, a front-end framework that I have been building for the last few months. This is a short introductory post about what the framework is, and why I decided to build it.

The elevator pitch

Halfmoon is a front-end framework with a few interesting things going for it:

  • Dark mode built right in: Creating a dark mode version of a site is baked in and a snap.
  • Modular components: A lot of consideration has gone into making modular components — such as forms, navbars, sidebars, dropdowns, toasts, shortcuts, etc. — that can be used anywhere to make layouts, even complex ones like dashboards.
  • JavaScript is optional: Many of the components found in Halfmoon are built to work without JavaScript. However, the framework still comes with a powerful JavaScript library with no extra dependencies.
  • All the CSS classes you need: The class names should be instantly familiar to anyone who has used Bootstrap because that was the inspiration.
  • Cross-browser compatibility: Halfmoon fully supports nearly every browser under the sun, including really old ones like Internet Explorer 11.
  • Easily customizable: Halfmoon uses custom CSS properties for things like colors and layouts, making it extremely easy to customize things to your liking, even without a CSS preprocessor.

In many ways, you can think of Halfmoon as Bootstrap with an integrated dark mode implementation. It uses a lot of Bootstrap’s components with slightly altered markup in many cases.

OK, great, but why this framework?

Whenever a new framework is introduced, the same question is inevitably pops up: Why did you actually build this? The answer is that I freaking love dark modes and themes. Tools that come with both a light and a dark mode (along with a toggle switch) are my favorite because I feel that being able to change a theme on a whim makes me less likely to get bored looking at it for hours. I sometimes read in dim lighting conditions (pray for my eyes), and dark modes are significantly more comfortable in that type of situation. 

Anyway, a few months ago, I wanted to build a simple tool for myself that makes dark mode implementation easy for a dashboard project I was working on. After doing some research, I concluded that I had only two viable options: either pickup a JavaScript-based component library for a front-end framework — like Vuetify for Vue — or shell out some cash for a premium dark theme for Bootstrap (and I did not like the look of the free ones). I did not want to use a component library because I like building simple server-rendered websites using Django. That’s just my cup of tea. Therefore, I built what I needed: a free, good-looking front-end framework that’s along the same lines as Bootstrap, but includes equally good-looking light and dark themes out of the box.

Future plans

I just wanted to share Halfmoon with you to let you know that it exists and is freely available if you happen to be looking for an extensible framework in the same vein as Bootstrap that prioritizes dark mode in the implementation.

And, as you might imagine, I’m still working on Halfmoon. In fact I have plenty of enhancements in mind:

  • More components
  • More customization options (using CSS variables)
  • More examples and templates
  • Better tooling
  • Improved accessibility examples in the docs
  • Vanilla JavaScript implementations of useful components, such as custom multi-select (think Select2, only without jQuery), data tables and form validators, among other things.

In short, the plan is to build a framework that is really useful when it comes to building complex dashboards, but is still great for building any website. The documentation for the framework can be found on the project’s website. The code is all open-source and licensed under MIT. You can also follow the project on GitHub. I’d love for you to check it out, leave feedback, open issues, or even contribute to it.

The post Halfmoon: A Bootstrap Alternative with Dark Mode Built In appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , ,

While solving for collaboration, we built a product that our own teams love and use everyday!

(This is a sponsored post.)

Flock is a messaging and collaboration tool built for both designers and developers. With close-to-zero setup, it brings together all your team’s conversations, appointments, and files in one place, helping you spend more time on what you are best at — building awesome stuff!

Building software is hard. Building software that is a delight to use every day is even harder, given the exacting standards most of us in the design and development community have for our tools. So, when we set out to change how people communicate in the modern workplace, we had but one goal – build something that we would objectively love!

Today, thousands of design and development teams use Flock every day, validating our UX-led approach to building a team collaboration tool for all kinds of teams. But how did we get here? Here’s our story.

At Flock, our designers frequently share creatives and design assets with the rest of the organization and using email to share links to files gets real old, real fast. So we started by looking at one of the biggest challenges to efficient collaboration at work — the “app-juggling” one had to master even for something as simple as sharing a file.

“Emails on one platform, files on another, real-time conversations on yet another one, and we would often need to shuffle between these apps to find and share relevant files with team members. That was an invisible time-sink!”
—Aaron Durham, Designer at Flock

We realized that bringing together our files and the conversations around them in one place would save us a lot of time and effort, and built integrations for Flock with Google Drive, OneDrive, Box, and Dropbox. Now, it is incredibly easy to find and share relevant files from the sidebar and discuss them with the team immediately, with dynamic previews and permission controls.

The next challenge we tackled was the time spent in getting feedback on designs and prototypes from colleagues in our geographically distributed design and development team. We knew that it was difficult to convey visual feedback on creatives through plain text/emails because our designers often struggled to understand what part of an illustration the feedback was aimed at.

And then, we thought, “Wouldn’t it be so much easier if we could hop on a call and show colleagues exactly what we see?” So, we built a seamless video and audio conferencing experience into Flock that allows us to start a video call with one or more team members and walk them through the feedback by sharing screens.

Like most startups, we have a few irons in the fire at any given time. So, one group of designers and developers might be working on a prototype of our newest product while another group works on landing pages for a marketing campaign. Conversations around these projects need to happen simultaneously and seamlessly. But with a team of over a hundred rock-stars, it’s difficult to keep track of conversations on various projects and keep those conversations on track. We had to create a system that accomplished both.

So, for every project, the Project Lead creates a Channel on Flock (a group conversation) where everyone involved can discuss the project. We create other channels for shared interests and water-cooler chats, so conversations in project channels are focused and more efficient.

Another reason for the dreaded “app-juggling” act? Our designers and developers use a lot of apps and services that they have to check for updates at various times of the day. So we built integrations for third-party services right into Flock. Now, team members receive notifications from all their favorite apps in one place — Flock — and can choose to take action when required.

Our App Store has over 60 integrations with popular third-party business apps and services, so we can work with all our favorite tools in one place. And we can connect hundreds of applications and web services to Flock using Zapier and IFTTT. From Dribble and Asana to Jira and GitHub, we connect almost every service we use to Flock. Last but not least, developers can build custom apps and integrations using our open API.

Many early adopters of Flock were teams with designers and developers who were happy to share feedback. We found that a lot of these teams worked with external consultants or clients, particularly at creative agencies. And these conversations were, again, on email, on the phone or, sometimes, verbal instructions with no record for later reference.

To ensure all these conversations could be brought into one window, we created Guests in Flock, an incredibly simple way of adding external collaborators to team workflows while maintaining a firewall of access between conversations within the team and conversations with guest users. This makes it easier to collaborate with clients and consultants, feedback can be shared and acted upon in real-time, and the built-in image annotation feature allows designers to share visual feedback on creatives.

Our thinking from the get-go has been that effective communication is a basic utility in every workplace, and it should add to productivity way more than it does to expenses. Which is why we priced Flock starting from free, with an option to unlock all functionality for $ 4.50 a user per month on the Pro plan — a third as much as our competitors.

Our designers and developers have found incredible success in building Flock and becoming its first power users, and the business case for adopting a team collaboration platform has never been clearer. Whether one wants to discuss ideas, share collateral’s, collect feedback from teammates and clients, or get code-push notifications from Gitlab, Flock just works.

Try Flock Now

Direct Link to ArticlePermalink

The post While solving for collaboration, we built a product that our own teams love and use everyday! appeared first on CSS-Tricks.


, , , , , ,

A Site for Front-End Development Conferences (Built with 11ty on Netlify)

I built a new little site! It’s a site for listing upcoming conferences in the world of front-end web design and development. In years past (like 2017), Sarah Drasner took up this daunting job. We used a form for new conference submissions, but it was still a rather manual task of basically manually editing a blog post. I wanted to keep doing this, as I think it’s valuable to have a simple reference page for conferences in our niche slice of the web, but I wanted the site to be able to live on year after year with lower maintenance-related technical debt.

So this is what I did!

I wanted to get it on GitHub.

So I put it there. Part of the beauty of GitHub is that it opens up the idea of collaboration through pull requests to really anyone in the world. You need to have a GitHub account, but that’s free, and you need to understand Git at least on some minor level (which is a barrier that I’d like to resolve in time), but it invites more collaboration than something like just asking people to email you content and ideas.

I wanted the content in Markdown in the Repo.

The Front Matter format, which is Markdown with some data the the top, is such a useful and approachable format. You need almost zero knowledge, not even HTML, to be able to create/edit a file like this:

Having the actual conference data in the repo means that pull requests aren’t just for design or features; more commonly, they will be for actual conference data. The work of making this site full of all the best conferences is the work of all of us, not just one of us.

At the time of this writing there have already been 30 closed pull requests.

I used 11ty to build the site.

11ty is almost fascinatingly simple. It looks in one directory for what it needs to process or move to another directory. It supports my favorite templating system out of the box: Nunjucks. Plus front matter Markdown like I mentioned above.

I was able to essentially design a card that displays the data we get from the Markdown files, and then build the homepage of the site by looping over those Markdown files and applying the templated card.

11ty is based on Node.js, so while I did have some learning-curve moments, it was comfortable for me to work in. There definitely is configuration for doing the things I wanted to be doing. For example, this is how I had to make a “collection” of conferences in order to loop over them:

config.addCollection("conferences", function(collection) {   let allConferences = collection.getFilteredByGlob("site/conferences/*.md");   let futureConferences = allConferences.filter(conf => {     return conf.data.date >= new Date();   });   return futureConferences; });

The site is hosted on Netlify.

One reason to use Netlify here is that it’s incredibly easy. I made a site site in Netlify by connecting it to the GitHub repo. I told it how to build the site (it’s a single command: eleventy) and where the built site files are (dist), and that’s it. In fact, that’s even part of the repo:

Now whenever I push to the master branch (or accept a pull request into master), the site automatically rebuilds and deploys. Just takes seconds. It’s really amazing.

Better, for each pull request, Netlify makes sure everything is in order first:

My favorite is the deploy preview. It gives you an (obscure) URL that will literally last forever (immutable) and that serves as a look at the built version of this site with that pull request.

So, not only is it extremely easy to use Netlify, but I get a bunch of stuff for free, like the fact that the site is smokin’ fast on their CDNs and such.

I’m also excited that I’ve barely tapped into Netlify’s features here, so there is a lot of stuff I can dig into over time. And I intend to!

I use Zapier to re-build the site every day.

There is a bit of a time-sensitive nature to this site. The point of this site is to reference it for upcoming conferences. It’s less interesting to see past conferences (although maybe we can have a browse-able archive in the future). I like the idea of ripping off past conferences for the homepage. If this was PHP (or whatever), we could do that at runtime, but this is a static site (on purpose). Doing something like this at build time is no big deal (see that code snippet above that only returns conferences past today’s date). But we can’t just waiting around for pull requests to re-build the site, nor do I want to make it a manual thing I need to do every day.

Fortunately, this is easy as pie with Zapier:

Phil Hawksworth took this to the extreme once and built a clock website that rebuilds every minute.

This site wasn’t just an experiment. I’d like to keep it going! If you’re part of running a conference, I’m quite sure it doesn’t hurt to add it to add yours, just so long as it has an enforcable and actionable Code of Conduct, and is within the world of front-end web design and development.

The post A Site for Front-End Development Conferences (Built with 11ty on Netlify) appeared first on CSS-Tricks.


, , , , , ,

How I Built a GPS-Powered Weather Clock With My Old iPhone 4

My first smartphone was an iPhone 4s. I remember the excitement of exploring its capabilities at a time when it was the coolest thing around. Eventually, of course, I replaced it with a newer model and the old iPhone, still in mint condition, gathered dust for two years. What a waste!

But was it? It occurred to me that I could repurpose the old iPhone to create a useful weather clock for our hallway.

Who needs Nest anyway?

In the process, I discovered that reusing an old device is not only fun and economical, it can also deepen your understanding of web standards. In this tutorial, I will show how I created a small web page to display the date, time, and current weather conditions based on the current GPS location. Together, we’ll retrieve weather data from a public API and hide an API key in a PHP file for security. Finally, we’ll look at adding a manifest file and meta tags so that users can save the page to a device’s home screen and then launch it as a standalone app, including a custom icon for it.

Here is a screen shot of what we’re aiming for:

Step 1: What time is it?

See the Pen Wall Clock by Steven Estrella (@sgestrella) on CodePen.

The HTML for the clock has some placeholder text that we will eventually replace. All we really need at this moment is a centered <main> container element and a couple of semantic <time> elements for the date and time. The <span> tag within the second <time> element will be styled specially to display the running seconds and the meridian. The datetime attribute within the <time> elements will be updated dynamically with JavaScript.

<main id="container" class="daymode">   <time id="date" datetime="" class="clocktext">Someday, Anymonth 15, 20XX</time>   <time id="time" datetime="" class="clocktext">12:00<span>:00 PM</span></time> </main>

A little responsive design is key here. We want this to fit nicely on an iPhone 4s screen or any other small-ish smartphone in both portrait and landscape modes. We also want it to work well on a desktop web browser, of course. We can’t use any bleeding-edge CSS or JavaScript, however, because older devices like my iPhone 4s won’t understand it.

I wound up taking things up a notch by creating styles specific for the time of day and we’ll definitely get to that as well. We could even leverage a media query to darken the daytime style for Mac users who have dark mode turned on in the latest MacOS.

The <span> tag with the running seconds and meridian will wrap nicely based on a width of 2em which is just large enough to accommodate two capital letters (i.e. AM and PM). The final CSS that’s needed is a media query to bump up font sizes when the clock is in landscape mode, or really any device with a viewport wider than 480px.

Here are the base styles we’re looking at, with the more decorative styles in the final app removed for brevity:

/* Base nighttime styles */ .nightmode {   background-color: #121212;   color: #fff; }  /* Base daytime styles */ .daymode {   background-color: #87ceeb;   color: #333; }  /* Target MacOS users who have Dark Mode enabled */ @media (prefers-color-scheme: dark) {   .daymode {     background-color: #003;     color: #ffc;   } }  /* Used to wrap any lines of text  */ .clocktext {   display: block;   margin: 0;   padding: 1px 0 0 0;   text-align: center;   white-space: nowrap;   width: 100%; }  #date {   font-size: 1.3rem;   padding-top: 15px; }  #time {   font-size: 5rem;   margin: 1px 0 0 0; }  #time span {   display: inline-block;   font-size: 1.5rem;   line-height: 1.5;   margin: 0 0 0 0.5em;   padding: 0;   text-align: left;   vertical-align: baseline;   white-space: normal;   width: 2em; }  @media (min-width: 480px){   #date {font-size: 2rem;}   #time {font-size: 8rem;}   #time span {     font-size: 2rem;     line-height: 2;   } }

For the JavaScript, I chose ES5 because many features of ES6 don’t work on the mobile Safari browser in iOS 9.35, which is the final iOS that runs on the iPhone 4s. Fortunately, ES5 is more than up to the task and the app runs properly on newer devices as well.

We need variables for the current date and time (now), the element that will display the date (dd), the element that will display the time (td) and the names of the months and days. Once the page is loaded, an init function is called to update the time every second (1000 milliseconds). The getClockStrings() function updates the value in the now Date object and returns an object containing HTML strings for the date and time. Then the updateTime() function updates the HTML to show the time. One lesser-used feature here is the inclusion of the toISOString() method of the Date object which adds a machine-readable date string to the datetime attribute of the two <time> elements.

Here’s what that boils dow to, using the JavaScript from the demo:

// NOTE: ES5 chosen instead of ES6 for compatibility with older mobile devices var now, dd, td; var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; var days = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];  document.addEventListener("DOMContentLoaded", init, false); function init() {   dd = document.getElementById("date");   td = document.getElementById("time");   updateTime();   setInterval(updateTime,1000); }  function updateTime() {   var clockdata = getClockStrings();   dd.innerHTML = clockdata.datehtml;   td.innerHTML = clockdata.timehtml;   dd.dateTime = now.toISOString();   td.dateTime = now.toISOString(); }  function getClockStrings() {   now = new Date();   var year = now.getFullYear();   var month = months[now.getMonth()];   var date = now.getDate();   var day = days[now.getDay()];   var hour = now.getHours();   var minutes = now.getMinutes();   var seconds = now.getSeconds();   var meridian = hour < 12 ? "AM" : "PM";   var clockhour = hour > 12 ? hour - 12 : hour;   if (hour === 0) {clockhour = 12;}   var clockminutes = minutes < 10 ? "0" + minutes : minutes;   var clockseconds = seconds < 10 ? "0" + seconds : seconds;   var datehtml = day + ", " + month + " " + date + ", " + year;   var timehtml = clockhour + ":" + clockminutes + "<span>:" + clockseconds + " " + meridian + "</span>";   return {"datehtml":datehtml,"timehtml":timehtml}; }

Step 2: Where are you?

See the Pen Clock with GPS by Steven Estrella (@sgestrella) on CodePen.

The Geolocation API is an easy way to get the user’s accurate location. We can do that when the page loads, but good manners and Chrome’s best practices audit dictate that we ask the user to initiate the location feature. So, we must add a button and a <div> to the HTML to display the GPS information we receive.

<button id="gpsbutton">Get GPS Location</button> <div id="gps" class="clocktext infotext"></div>

These new items require their own styles — mine can be seen in the embedded Pen above. The important thing is to add the class selector rule for the infotext class as well as the two ID selector rules for the gpsbutton.

Finally, we’ll need to add the modified infotext class selector rule within the media query.

Again, the basic styles minus decorative styling for brevity:

/* The geolocation coordinates upon clicking GPS button */ .infotext {   font-size: 1.3rem;   line-height: 1.4;   padding: 0 5px 0 5px;   width: auto; }  /* The button itself */ #gpsbutton {   -webkit-appearance: none;   -moz-appearance: none;   display: block;   margin: 0 auto;   width: auto;   cursor: pointer; }  #gpsbutton:hover {   /* Styles for the hover state */ }  @media (min-width: 480px){   /* Add the rule below to the end of the media query */   .infotext {font-size: 1.8rem;} }

The JavaScript requires a few new variables for the latitude, longitude, GPS <div>, and GPS <button>. When clicked, the GPS button calls the getLocation() function which tests for the availability of geolocation support in the browser. If it finds such support, it calls the getCurrentPosition method of the navigator.geolocation object and passes a reference to a success callback function named showPosition and an error callback function named geoError.

At this point, the browser will ask the user for permission to obtain their GPS location. If the visitor refuses, an appropriate message is displayed. If the user approves, the showPosition() function then displays the latitude and longitude and hides the GPS button.

Here’s the Javascript we get based on what we’ve covered in this section:

// ... var lat, lon, gd, gpsbutton; // ... function init(){   // ...   gd = document.getElementById("gps");   gpsbutton = document.getElementById("gpsbutton");   gpsbutton.addEventListener("click",getLocation,false);   // ... }  function updateTime(){   // ...   var sec = now.getSeconds();   var minutes = now.getMinutes();   if (sec === 0){     if (minutes % 5 === 0){       getLocation(); // Get location every 5 minutes while the app is running     }   } }  function getClockStrings(){   // ... }  function getLocation() {   if (navigator.geolocation) {     navigator.geolocation.getCurrentPosition(showPosition,geoError);   }else{     gd.innerHTML = "location unknown";   } }  function geoError(){   gd.innerHTML = "location detection disabled"; }  function showPosition(position) {   gpsbutton.style.display = "none";   lat = position.coords.latitude;   lon = position.coords.longitude;   gd.innerHTML = "GPS: " + lat.toFixed(2) + " | " + lon.toFixed(2); }

Step 3: How’s the weather?

Adding current weather conditions to this clock is a useful feature. Fortunately, the good folks at OpenWeatherMap.org allow access to basic weather information for free. Sign up for a free account there and you will be given an API key you can use in your web development. The free account provides current conditions and 5-day forecasts for any valid GPS location you throw at it. Just be sure to call the API no more than 60 times per minute or you will be prodded to upgrade to a paid account. Once you have an API key, substitute it for the words YOUR_API_KEY_HERE in this code sample and then paste the code into the browser’s location bar. You will receive a JSON object containing the weather for my location here in Pennsylvania. Experiment with different latitudes and longitudes. You can find coordinates for any major city at LatLong.net where the coordinates are given in the decimal format you need.


See the Pen Clock and Weather by Steven Estrella (@sgestrella) on CodePen.

Add the HTML

Just below the GPS <button>, add the following to the HTML:

<div id="weather" class="clocktext infotext"></div> <img id="icon" src="https://openweathermap.org/img/w/01n.png" alt="weather icon" />

Add the CSS

The CSS needs styles for the new weather <div> and the icon image. Note that the icon is set to 0 opacity initially. That will be changed in the JavaScript code once valid weather information is retrieved.

#weather {   display: block;   width: auto; }  #icon {   display: inline-block;   opacity: 0;   vertical-align: top;   width: 50px;   height: 50px; }  @media (min-width: 480px){   /* Add the rule below to the end of the media query */   #weather {display: inline-block;} }

Add the JavaScript

We need to add variables to reference the weather URL, the weather <div> (wd), and the weather icon. We also need to decide on Fahrenheit or Celsius. The Boolean value for usephp should be set to false for now. We will discuss hiding your API key in a PHP document a little later. The locationRequested Boolean value will help us avoid calling the weather and geolocation APIs before the user has requested them. The sunset and sunrise time variables will allow us to change the appearance of the clock based on the time of day. The iconurl value provides the stem of the URL we need to retrieve weather icons. We also need a random number between 0 and 14 to use in our updateTime function so that all our users don’t request weather at the same minute each quarter hour. If you have your own set of icons, you can change the URL value for iconurl . The file names for the PNG icons are available at OpenWeatherMap.org.

// ... var weatherurl, wd, icon, weatherminute; var temperaturescale = "F"; // Set to F or C (fahrenheit or celsius) var usephp = false; // Set to true to use a PHP document to hide your api key var locationRequested = false; var sunsettime = 0; var sunrisetime = 0; var iconurl = "https://openweathermap.org/img/w/";  // ... function init(){   //... Add these lines before the updateTime call at the end of the function   wd = document.getElementById("weather");   icon = document.getElementById("icon");   weatherminute = randRange(0,14);   // ... }  // Random number utility function function randRange(min, max) {   return Math.floor(Math.random()*(max-min+1))+min; }

Next we will modify the final if block of the updateTime() function. We wish to avoid unnecessary calls to the weather and geolocation APIs so it is important to test for sec === 0 to be sure we don’t call either API 60 times in a given minute. We also wish to call the APIs only if the user has approved the browser’s geolocation request.

function updateTime(){   //...   if (locationRequested && sec === 0){     checkForSunset(); // Checks for sunset once each minute     if (minutes % 15 === weatherminute){         getWeather(); // Get weather every 15 minutes while the app is running         // weatherminute is a random number between 0 and 14 to ensure         // that users don't all hit the API at the same minute     }     if (minutes % 5 === 0){       getLocation(); // Get location every 5 minutes while the app is running     }   } }

In the showPosition() function, the URL to request the weather data will be either a relative path to the PHP file or a full HTTPS URL pointing to the OpenWeatherMap.org service. In both cases, we will be passing the latitude and longitude in the query string of the URL. For the APPID, please substitute your own API Key for the words YOUR_API_KEY_HERE unless you are using the PHP solution discussed in Step 4 below.

function showPosition(position) {   //...   if (usephp){     weatherurl = "clock.php?lat=" + lat + "&lon=" + lon;   }else{     weatherurl = "https://api.openweathermap.org/data/2.5/weather?";     weatherurl += "lat=" + lat + "&lon=" + lon + "&APPID=";     weatherurl += "YOUR_API_KEY_HERE";   }   if (!locationRequested){     getWeather();     locationRequested = true;   } }

The showPosition() function then calls getWeather() which updates the weather <div> to let the user know something is happening while the weather data is being retrieved. I opted to use the older XMLHttpRequest standard because fetch is not supported on old devices like the iPhone 4s. If the weather data request is being channeled through a PHP document, the response type will be “document” rather than plain text so we have to test for that. If that is the case, the JSON object we need will be in the textContent property of the body of the response. Otherwise, we only need the plain text found in the responseText property. The data is then parsed as a JSON object and sent to the processWeather() function.

function getWeather() {   wd.innerHTML = "getting weather";   var xhttp = new XMLHttpRequest();   xhttp.responseType = usephp ? "document" : "text";    // The PHP file returns a document rather than plain text   xhttp.onreadystatechange = function() {     if (this.readyState === 4 && this.status === 200) {       // When using PHP as a data source we need the `textContent`       // of the body of the returned document       var data = usephp ? xhttp.response.body.textContent : xhttp.responseText;       processWeather(JSON.parse(data));     }   };   xhttp.open("GET", weatherurl, true);   xhttp.send(); }

The JSON object sent to processWeather() will look something like this:

{"coord":{"lon":-75.21,"lat":40.15}, "weather":[{"id":701,"main":"Mist","description":"mist","icon":"50n"}], "base":"stations", "main":{"temp":276.42,"pressure":1011,"humidity":100,"temp_min":275.15,"temp_max":277.15},"visibility":16093,"wind":{"speed":2.1,"deg":310},"clouds":{"all":90},"dt":1545021480, "sys":{"type":1,"id":4743,"message":0.1513,"country":"US","sunrise":1545049047,"sunset":1545082605},"id":5190089,"name":"Fort Washington","cod":200}

From this JSON data, we need to grab the weather property that contains the description of the current conditions and the filename for the weather icon. The <img> tag in the html that has the ID of “icon” is then updated with a new src property value to load the icon image. The temperature is part of the main property but it has to be converted to Fahrenheit or Celsius (most humans don’t think in Kelvin). When that is done, the current conditions can be assigned to the innerHTML property of the weather <div>. The times for sunrise and sunset are found in the sys property of the data. Once we have those, we can call the checkForSunset() function and modify the style to match the time of day.

function processWeather(data){   var weather = data["weather"][0];   icon.src = iconurl + weather.icon + ".png";   icon.style.opacity = 1;   var localtemperature = convertTemperature(data["main"].temp).toFixed(0);   var weatherstring = localtemperature + "°" + temperaturescale + "&nbsp;&nbsp;" + weather.description;   wd.innerHTML = weatherstring;   sunsettime = Number(data["sys"].sunset);   sunrisetime = Number(data["sys"].sunrise);   checkForSunset(); }  function checkForSunset(){   var nowtime = now.getTime()/1000;   // Changes the presentation style if the time of day is after sunset   // or before the next day's sunrise   var isDark = nowtime > sunsettime || nowtime < sunrisetime;   document.getElementById("container").className = isDark ? "nightmode":"daymode"; }  function convertTemperature(kelvin){   // Converts temps in kelvin to celsius or fahrenheit   var celsius = (kelvin - 273.15);   return temperaturescale === "F" ? celsius * 1.8 + 32 : celsius; }

Step 4: Do I really have to show you my API key?

I used a disposable API key to create a working demo. Generally, however, putting an API key in plain text within the code of a front-end web application seems like a bad idea. Others might copy it and use up your API access quota. If you have access to a typical web server (CodePen doesn’t do PHP), you can hide the API key in a PHP file. Here is some sample code. Substitute the API key and upload the file as clock.php into the same directory as the main HTML file. The PHP file serves as a sort of proxy to the OpenWeatherMap API that we’re using to fetch data in the demo. If it has any problem retrieving weather data, it simply returns an appropriately structured object with “Weather Unavailable” as a description and a temperature that converts to 0° Fahrenheit. The API key is never transferred from the server to the browser so there is nothing for prying eyes to see.

I would be interested to hear from readers if you know of a secure, serverless solution to hiding an API key (Netlify or Docker perhaps?) because it’d be nice not to have to spin up our own server to store and hit the data. Chime in if you have some thoughts.

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Clock Data</title></head> <body> <?php error_reporting(0); $ latitude = "80"; $ longitude = "-85";  if (isset($ _GET["lat"]) && isset($ _GET["lon"])) {   $ latitude = $ _GET["lat"];   $ longitude = $ _GET["lon"]; }  $ endpoint = "http://api.openweathermap.org/data/2.5/weather?"; $ apikey = "YOUR_API_KEY_HERE"; $ weatherurl = $ endpoint . "lat=" . $ latitude . "&lon=" . $ longitude . "&appid=" . $ apikey; $ jsonfile = file_get_contents($ weatherurl);  if ($ jsonfile !== false){   echo "$ jsonfile"; } else {   echo '{"weather":[{"description":"Weather Unavailable","icon":"01n"}],"main":{"temp":255.372278}}'; }  ?> </body> </html>

If anyone else tries to use this PHP file from another domain, the browser should throw an error like the one in the following example. I loaded a copy of the weather clock on my makepages.com domain and tried to access the PHP file on my shearspiremedia.com domain. These days, the same-origin policy is in place by default on typical commercial web server installations. You might need to confirm that is the case on the server you are using.

[Error] Origin https://makepages.com is not allowed by Access-Control-Allow-Origin. [Error] XMLHttpRequest cannot load https://shearspiremedia.com/demos/clock/clock.php?lat=40.14616446413611&lon=-75.20946717104738 due to access control checks.

Next, update the usephp variable in the JavaScript file and test:

var usephp = true; // Set to true to use a PHP document to hide your api key

Step 5: Can I please go full screen?

Once the app is up and working, it can load on any smartphone browser. The iPhone, however, is forced to endure the presence of the location bar and footer. Yuck!

It would be lovely to be able to view it full screen instead. To do that, we need a manifest file to tell the device that we wish to view the clock as a standalone app and to tell Android devices where the app icons are located. Here is my manifest file which I saved as manifest.json in the same directory as the HTML file. You can create your own manifest file using the Web App Manifest Generator. Be sure to adjust the icon file names in your own manifest file and in the link tags in the HTML as we see here:

{   "short_name": "Weather Clock",   "name": "Weather Clock by Shearspire Media",   "icons":      {       "src": "icons/launcher-icon-1x.png",       "type": "image/png",       "sizes": "48x48"     },     {       "src": "icons/launcher-icon-2x.png",       "type": "image/png",       "sizes": "96x96"     },     {       "src": "icons/launcher-icon-128.png",       "type": "image/png",       "sizes": "128x128"     },     {       "src": "icons/launcher-icon-152.png",       "type": "image/png",       "sizes": "152x152"     },     {       "src": "icons/launcher-icon-4x.png",       "type": "image/png",       "sizes": "192x192"     }   ],   "orientation": "landscape",   "display": "standalone",   "start_url": "index.html" }

We also need a set of square PNG images at 192px, 152px, 128px, 96px, and 48px for the home screen icon. Save these into an icons folder within the same folder as your HTML file. Use the file names found in the manifest. The Web App Manifest Generator will create icons in all the required sizes other than 48px by uploading a single 512 x 512 pixel image. Here is the simple icon I made:

Home screen icon for my Weather Clock.

Finally, we need to add a bunch of meta and link tags in the head of the HTML file to make this all work. Here is the completed index.html file code including all the head tags you can’t see on CodePen.

<!DOCTYPE html> <html lang="en"> <head>   <meta charset="utf-8" />   <meta name="viewport" content="width=device-width, initial-scale=1">   <meta name="mobile-web-app-capable" content="yes">   <meta name="apple-mobile-web-app-capable" content="yes">   <link rel="manifest" href="manifest.json">   <link rel="apple-touch-icon" sizes="48x48" href="icons/launcher-icon-1x.png">   <link rel="apple-touch-icon" sizes="96x96" href="icons/launcher-icon-2x.png">   <link rel="apple-touch-icon" sizes="128x128" href="icons/launcher-icon-128.png">   <link rel="apple-touch-icon" sizes="152x152" href="icons/launcher-icon-152.png">   <link rel="apple-touch-icon" sizes="192x192" href="icons/launcher-icon-4x.png">   <title>Weather Clock by ShearSpire Media</title>   <script src="clock.js"></script>   <link rel="stylesheet" type="text/css" href="clock.css"> </head>  <body>   /* Clock markup */ </body>  </html>

The visitor can now tap the share button on the iPhone and choose “Add to Home Screen.” An icon will appear that will launch the clock as a full-screen standalone app. Enjoy!

Another Option: IP Address Location

See the Pen Clock and Weather IP by Steven Estrella (@sgestrella) on CodePen.

If the user’s exact location isn’t a requirement, we could avoid the Geolocation API entirely and get the approximate location using any of several IP address services. In the demo above, a JSON object is received from extreme-ip-lookup.com to get the approximate GPS location of the user. That displays the city and region values found in the JSON instead of the GPS coordinates. It should be clear to the user in this case that the weather location is a neighboring town.

Since IP information is part of the normal request for a web page, an argument could be made that there is no ethical problem with displaying IP location information without user permission. That eliminates the need for the GPS button altogether. I actually switched the final app to use the IP address location feature with geolocation only as a fallback in the event the IP location service is down. I also added more weather information, custom background images, and custom weather icons to correspond to current weather conditions. The custom icons are available at this Pen. The final app is available here on my ShearSpireMedia.com site. I also created a Pen that generates a starry sky for night mode that can be used to make a night background.

That’s a wrap!

We covered a lot of ground in this article, but hopefully it gives you an idea that:

  • We can teach an old device new tricks.
  • The Geolocation API isn’t all that scary.
  • Fetching and using data from an external API is useful.
  • Using a small PHP file is one easy way to hide your API key from prying eyes.
  • Working with user permissions is a good (and often required) practice.

Like I mentioned earlier, a serverless solution for storing data would be ideal here, so if you have any thoughts on that — or really any ideas or questions at all — please let me know in the comments!

The post How I Built a GPS-Powered Weather Clock With My Old iPhone 4 appeared first on CSS-Tricks.


, , , ,

Get References from HTML Built with Template Literals

One thing JavaScript template literals are great at is little blocks of HTML. Like:

// Probably from some API or whatever const data = {   title: "Title",   content: "Content" };  const some_html = `   <div class="module">     <h2>$  {data.title}</h2>     <p>$  {data.content}</p>   </div> `;

But that’s still just a string. It’s not ready to append to the DOM just yet. And what if we need references to those elements inside somehow?

We’ve written about a couple of libraries that are in this vein: lit-html and hyperHTML. Those are pretty small libs, but are also sorta more about re-rendering of templates in an efficient way (like super mini React).

What if you just need the nodes? That’s almost a one-liner:

const getNodes = str => {    return new DOMParser().parseFromString(str, 'text/html').body.childNodes; }

Now we could drop that template literal of HTML right into the DOM:


Here’s that:

See the Pen pQyZOz by Chris Coyier (@chriscoyier) on CodePen.

But how do we get our hands on individual bits of that HTML? We don’t exactly have references to anything, even the whole chunk we put in.

I just saw this little lib called Facon that looks to do just this. It makes use of tagged template literals, which is super cool:

import f from 'facon';  const data = {   title: "Title",   content: "Content" };  let html = f`   <div class="module">     <h2>$  {data.title}</h2>     <p>$  {data.content}</p>   </div> `;  document.body.appendChild(html);

This skips the need for our little getNodes function, but more importantly, we can yank out those references!

let html = f`   <div class="module">     <h2 ref="title">$  {data.title}</h2>     <p ref="content">$  {data.content}</p>   </div> `;  let { title, content } = html.collect(); title.innerText = "Title Changed!";

Here’s that:

See the Pen Facon Template by Chris Coyier (@chriscoyier) on CodePen.

The post Get References from HTML Built with Template Literals appeared first on CSS-Tricks.


, , , , ,