We built a Twitter/YouTube-style embed system, and this is how.

We were able to make a neat embeddable content system for our ReactJS/Rails App in a couple days, but couldn’t come up with a better title for this article.

Brian Hogg
Blundit
10 min readFeb 26, 2018

--

In case you’re unaware, Blundit is an expert-accuracy tracking website/system that’s currently in development. It aims to provide a rating to experts and pundits of all stripes, to help people make better decisions about who they should listen to. You can sign up to our email list at blundit.com, or go to dev.blundit.com to check out our current build (we’re at v0.2 as of Feb 25, 2018)

We’re still very much in heads-down development mode for a lot of primary features here at Blundit, but we’re always very cognizant of how we hope and expect to get people to use it. There are basically two modes of interaction we imagine happening: the first is on the site, obviously, with people arriving via a link they come across or are sent, or coming to the site specifically to search for something a pundit’s said, or to find out what expert opinion on a given subject is. The second mode is via our info embedded on other sites.

When you come to the site, on the home page, claims/predictions/experts indices and in the search page, you see info cards. They look like this:

Expert, Claim and Prediction cards on Blundit.

Our thinking has long been that if we let people embed these cards on other sites — very much in the vein of YouTube and Twitter embeds — it would be a really great way to provide additional context for the people highlighted in a given article, and that it would likely be the strongest way for Blundit to gain users and popularity.

For example, if a site supported embeds, then a writer could include the Blundit cards for the people in their article, providing an extra level of context for the reader, and a way to learn more about the experts opinions. Like this:

Medium is used in this mockup, even though Medium doesn’t support iframes.

Looks pretty neat, right? Well, this week I built it!

Decisions, Decisions

There were several different ways I considered adding embed functionality, each with their own pros and cons. The first question was how the embed would function: is it scrutable? With a YouTube embed, you can scan the iframe code that they provide and understand which video you’re watching. If the URL for a video is:

then you can know that the URL for the embed is:

which gets wrapped in an iframe block, like this:

Twitter’s embed is also scrutable, and basically involves loading an external Javascript file to do an inline update of styling and data replacement of a blockquote wrapping an href to aTwitter status. It looks like this

Having a predictable URL structure for the embeds, such as:

would be fine, but in the future I’d like to allow users to create lists of embed content, such as selecting their 5 favourite tech pundits, 10 craziest scientific predictions of the 1900's, or creating a list of everyone in the 112th US Congress. Scrutable embed urls would require to users to create these lists manually, and that’s no fun. So keeping an eye to the future, Blundit’s v1 embed functionality utilizes lists, albeit a list with just only a single item.

Making an embed that references predictable naming would allow us to always display the embed information on the page if we wanted, but if we’re creating embed lists, we won’t be able to do that, unless we want to create a new embed list for every time a user actually views a page for a Claim, Prediction or Expert. This would be ludicrous, of course, so the decision to use lists necessitates the some UI choices: a user will have to actively click a button to create a new embed list. This adds a step to the process, but going with lists also allows us increased user/behavioural tracking, and will, in the future, allow users to update any lists they’ve created without updating any posts that the lists appear in. This seems pretty cool, and worth the trade-off.

The Structure

Broadly speaking, the structure for the Embed feature is made up of the following components:

  • Embed model
  • API Endpoint (to create instance of Embed model)
  • Frontend Interface (to access API endpoint; visible in detail pages)
  • Embed Display

Let’s dive into each one, to see how they work.

Embed model

Each embed will be an instance of an Embed object, which is a model in the Rails backend. The rough structure is as follows:

You can see the full schema, as well as the model files for Embed, EmbedItem and EmbedView, in the project Github repo, if you’re interested.

API Endpoint

To create embeds, we’ll add an API endpoint each for adding a claim, prediction or expert embed. The routes API endpoints are:

I wrote a do_add_embed methodin application_controller.rb, which the controllers each refer to it in their own add_embed methods that are invoked with the above endpoints.

I realize this approach violates DRY somewhat, but I’m making each controller call the method in the embed helper from their own method in case I need to add any claim/prediction/expert-specific logic later on.

This is the method, excerpted from application_controller.rb:

And this is what the add_embed method looks like the prediction controller:

Pretty straightforward. When the method is called, it calls the method defined in application_controller, passing the type of the item being added to the embed. The script sets the title and description of the new embed, and if it successfully saves, attempts to add the singular item (using the type that was passed to determine the variable value to set). It returns errors as appropriate, and if everything saves correctly, it returns a json object with an embed_key, which is a 32-character alphanumeric string that’s going to be used in the embed display to determine which embed gets shown, which is in the Embed model:

The embed key is added as a precaution to prevent people from being able to guess at other embeds to show based on using the embed ID or something. It doesn’t particularly matter with this version of the embed, as there’s no mission-sensitive data being set or returned, but if I add anything in the future — such as allowing private embed lists — then I won’t have to change anything that impacts existing embed. Plus, an embed that you reference using ef65gqh3idy4knvowxm8ztb7ap9jc2lr just looks cooler than one you reference using 1257, right?

I realized while writing this that I don’t actually have a validator to ensure that the embed_key is unique. I will add that soon.

Frontend Interface

Next up, I wired it into the frontend. The interface is Material Design-esque, with a series of cards showing content of varying kinds (like Categories, Evidence for a Claim and Comments). One of them is a Share card, which was only showing share links for various services, like Facebook, Twitter and Pinterest. This is where the embed functionality goes. A “Click to Embed” link calls the API endpoint that creates the Embed, and when that goes through, a text field will be drawn with the appropriate iframe code. And to be extra fancy I’ve added a functioning copy to clipboard link, so you don’t even have to manually select the code and hit CTRL+C like an animal.

This is what the React component, ShareItem.js, looks like:

And this is what it looks like In the browser:

When you first come to a detail page.
After you’ve clicked “Click to Embed.”

There’s a bunch of code there, but it’s more verbose than complicated: There’s an initial state pertaining to the embeds (embed, embed_host, loadedEmbed, embedError, embedStatusText) and in the render method, either the “Click to Embed” link or the embed code is shown, based on what the state is. The user clicking the link fires the createEmbed method, which makes the API call on line 49. The API class wraps a fetch() call, which resolves a Promise when it hears back from the server, setting new state variables based on the outcome.

Assuming the call is a success, the embedText method will display the iframe code. On line 163, an onClick handler is set that will select all the text in the input field for easy copying, and on line 166, an onClick handler is defined for the clipboard icon (using the excellent Font Awesome 5), which selects the text, then uses document.execCommand(“Copy”) to copy the text to the user’s clipboard. This is ridiculously simple and hardly worth mentioning, but I’m pretty happy by how easy it was to implement, and how much more polished it makes the embed feature look.

Embed Display

So now we’ve got the embed object being created, and the iframe code is generated and displayed to the user, and it has a nice bit of shine to it. But how to display the actual embed content?

The simplest way to embed, in terms of how long it would take me, would be to simply repurpose the ClaimCard, PredictionCard and ExpertCard components in views that only contain them. I’d be able to utilize all existing logic, and use the already-created API to pull in the appropriate data. All I’d need to do is add some routes to the main App.js file, and create 3 new views. But the down-side is that a user would have to load a lot more of Blundit then they’d be needing to, including the entire site’s CSS file. Plus they’d need to be making multiple back-and-forth calls to the server, since the site has automated checks for token validity. This would be a lot of extra baggage for someone viewing one of our embeds on some other blog, especially if they were to have two or three on a page — they’d essentially be loading multiple instances of Blundit!

Plus, the cards are just display elements; there’s no real interactivity beyond an href wrapping the whole component. A simpler approach, and the one I went with, is to create a flat version of the cards, with plain HTML and the CSS data pertaining only to the card that the user is looking at. The only external assets that are loaded are Font Awesome 5 and Google’s Open Sans font.

The embeds_controller.rb file retrieves the relevant data to display the appropriate card (and sets some placeholder data that will get replaced as features intended for the in-card display are added), and renders the appropriate html template file, depending on the type of the Embed being shown.

This is the entirety of the Prediction template file, complete with my own haphazard CSS indentation:

Right away you can probably guess a short-coming of this approach: if I modify the cards on the site in any way, these immediately become out-of-date, which will require updates: I’m giving myself another version of the codebase to maintain. That’s not ideal, obviously, but I think the benefits of this approach outweigh this relatively minor issue.

Can you see the embed?

This is the part where I’d like to embed an expert card into this article, so that you can see it. Unfortunately, Medium doesn’t allow embeds like that (though they do support embed.ly, which is something I’ll have to check out), so I can’t. But I can show you a link to an embed in the Dev environment, so you can see what it would look like if you were seeing it below this paragraph:

(The component will stretch to the width of the screen, but the iframe it gets wrapped in is set to 302px wide)

Next Steps

I’ve got a long list of improvements I’d like to make to the embed system, not the least of which involving some tidying up of the Ruby code I’ve written. Lists are the primary item, along with setting an optional title and description as a wrapper around the embedded items, along with some styling options and probably the ability to define some amount of inline style overrides so that the background and title/description match the visuals of the site they’re appearing on.

Additionally, while this embed iframe solution is cool, it only works for folks who use blogs that support code embeds. I’m very cognizant of the fact that it wouldn’t show up visually on FB, Twitter, Instagram, Tumblr, or any place that shares images. So I’ll look into rendering the card as a graphic that can be posted elsewhere (including to Medium blogs). A static graphic would lose the whole “always showing the latest data” benefit that an iframe has, but that’ll probably be worth the extra locations the content will be shareable to.

And that’s it! I’m still building a sense of how much to explain in detail versus at a higher level. So if this seems to cursory a run-through (or too needlessly in-depth), I’d love to know! At some point I’ll make a video detailing the code, and that seems like a better venue to pore over every line, but if you’ve questions about my approach or the logic of it, please post them. Thanks!

If you want to follow along with the development of Blundit, please follow this Publication on Blundit. Alternatively you can head over to blundit.com and add yourself to our mailing list, and you can load up dev.blundit.com to see the current version of the site, and find links to our public Github repos, public Trello board, and other things.

--

--