Jan 21, 2020 4:27:54 PM
Consultant, Technology
In the last decade, cloud-based SaaS solutions have taken over a large part of the software market, including the market for our office tools: think of Slack, Salesforce and Google Docs. But sometimes these tools are missing a small feature that could speed up our work. At Columbia Road we're all about automation and trying out little experiments to improve a situation, so when we had a small issue with our time tracker Harvest, our developer Paul took a shot: what if we could hack in the solution ourselves?
SaaS software is often accessible through web apps, and with web apps it's possible to change things on the client-side. This series of articles will explain how to hack the web to your needs with bookmarklets and web extensions, and when or when not to consider these hacks.
In this post we'll take a look at bookmarklets, a way to add small behaviours to a web app activated on a click. We'll demonstrate step-by-step how to use it to solve a small hypothetical problem similar to one we encountered.
Caution: some web development experience is useful to follow along. Let's get started!
Take this scenario: you want to start tracking time in your business. You compare your options and eventually tool X looks the most promising. So you try it out and after evaluating it for a month, you start using it company wide.
All looks good, until you and some colleagues discover a very specific need that tool X is not addressing. Your tool has a view that summarises all the work your company tracked. It includes the specific work item that was tracked, which project team did the work, and the related number of tracked hours:
Item |
Team |
Hours |
Designing the shop front |
Tigers |
200 |
Developing the MVP |
Panthers |
500 |
Feature request #999 |
Panthers |
450 |
Creating sales pitches |
Lions |
1100 |
Designing the Christmas banner |
Tigers |
130 |
Here comes the problem. Every year you need to report to your manager how many hours were spent by each team. But unfortunately, your tool can't group the information that way.
What to do? Sure, you can calculate the sums manually, but this is frustrating and error-prone. What if the list is very long? Moreover, if many colleagues have the same problem frequently, this sums up to a lot of lost time. You could consider these options:
Option 1) is worth trying. Maybe tool X's maker has a dedicated page for feature requests. Unfortunately, it will likely take some time for them to consider, develop, implement and deploy the change you requested - if they do it at all.
Option 2) is also not ideal. You just invested big time in a tool that works well 95% of the time. It's only this one feature that is missing. The cost of switching to another tool is high enough to make it unjustified, plus you may be missing out on other features with other tools.
So, is this a hopeless situation? There might be a third option, if you can code it yourself. This particular problem could be solved with a nifty bit of browser magic called bookmarklets!
A bookmarklet simply is a bookmarked link. However, unlike the HTML usual <a> link that has a href that starts with https://, it instead starts with javascript:. With this kind of link we can store a small custom JavaScript snippet that executes on the page where it is clicked. It's a different protocol, just like the more common mailto: links.
Here's an example:
If you click the link, the alert('Hey!') code will run on your browser page.
If you drag that link to your bookmark toolbar, it becomes a bookmarklet. You can now execute this code on every page.
So what can we do with this? Bookmarklets can for example be used to send selected text to a web service. But in our case, we can use one to solve our grouping problem.
The javascript: link above reports the grouped hour count for each team. Let's break down how it works, and how we make a bookmarklet from it.
On a high level, we want our code to do this:
The following code is what we'll end up with.
alert(
JSON.stringify(
Array.from(document.querySelectorAll("tr:nth-of-type(n+2)")).reduce(
(acc, row) => {
const team = row.children[1].textContent;
const hours = +row.children[2].textContent;
return { ...acc, [team]: (acc[team] || 0) + hours };
},
{}
)
)
);
We start by extracting all the rows from the HTML <table> in the DOM of this blog page. In this case there is only one <table> in this article, so we can keep this example code simple & generic.
Array.from(document.querySelectorAll("tr:nth-of-type(n+2)"))
We now have an array with all table rows!
Next, we need to group the tracked items to their respective teams. The aim is to group them in an object of the form {"Tigers": a,"Panthers": b,"Lions": c} with the final tracked hour totals.
We're using reduce here to process each row, adding every hour to the object that is passed as an accumulator, keeping track of the sum for every row up to the current one. This expression finally resolves with the desired totals.
.reduce(
(acc, row) => {
const team = row.children[1].textContent;
const hours = +row.children[2].textContent;
return { ...acc, [team]: (acc[team] || 0) + hours };
},
{}
)
This will only pr esent the stringified object, but you could adjust it to a nicer table layout as well.
alert(JSON.stringify(reducedObject))
Now to the interesting part: let's put this code into a javascript: link. We can’t just copy-paste the code because we need to make sure the link is valid as a href attribute. Therefore, we first need to URL encode it.
URL encoding is hard to do manually, so we can use a web tool for that. This one is tailor-made for bookmarklets: https://mrcoles.com/bookmarklet/. One nice feature it adds is that it wraps your code in an Immediately Invoked Function Expression, which avoids interference with the existing variable scope of the JavaScript on the web page.
Copy-pasting the code into that tool gives us the final link we can put into an <a> link on a website and share that way, or add manually as a new bookmark in you bookmark toolbar.
<a title="Bookmarklet to group items by team" href="javascript:(function()%7Balert(JSON.stringify(Array.from(document.querySelectorAll(%22tr%3Anth-of-type(n%2B2)%22)).reduce((acc%2C%20row)%20%3D%3E%20%7Bconst%20team%20%3D%20row.children%5B1%5D.textContent%3Bconst%20hours%20%3D%20%2Brow.children%5B2%5D.textContent%3Breturn%20%7B%20...acc%2C%20%5Bteam%5D%3A%20(acc%5Bteam%5D%20%7C%7C%200)%20%2B%20hours%20%7D%3B%7D%2C%7B%7D)))%7D)()">Bookmarklet link</a>
That was it! Check out this CodePen for the full code & more details, including two more ways to present content instead of alert(): one with a bookmarklet document, and one where you inject the answer back into a page.
This post demonstrated a simple bookmarklet with information extraction, processing and presentation using a single JS expression. Here some ideas of what else you could do:
Bookmarklets can provide small solutions to small client-side problems in web apps. They can save a lot of time. But of course there are also limitations:
For more complex issues and to circumvent some of the limitations above, you could jump to the big brother of bookmarklets: Web Extensions. You're probably using some already, so why not make one yourself? Web extensions have access to a rich set of browser APIs that give them much more power. Moreover, they can be developed and distributed in a better way. We'll dive into the world of extensions in the next post.
Enjoyed the blog? Perhaps you would be interested in working at Columbia Road! We're constantly looking for nice and skilful people to work with – and to have some good banter with! If you are interested in reading more about the work we do at Columbia Road, have a look at our client cases.
Sep 27, 2021
Principal Consultant, Technology
Aug 2, 2021
General Manager, Sweden
Jul 26, 2021
General Manager, Finland
Stay up-to-date what’s going on in digital commerce and Columbia Road. We’ll email you once a month with news, interesting articles and studies from the industry and the crème de la crème of our blog.