DEV Community

Cover image for Smarter list sorting with DataDrivenJS
Anna Su**
Anna Su**

Posted on

Smarter list sorting with DataDrivenJS

If you work on a blog, or an online shop you have lots of lists that you need to sort. Post, products, product filters... You probably sort them from new to old, from cheap to expensive, or alphabetically. Or, your boss tells you "the right" order and you arrange items manually.

However, if you would take a look at the website's stats, you would find out that whatever order you use, it doesn't match the order of your most-popular or best-converting items. This means that a user has to see/click lots of less relevant items until she or he stumbles upon your blockbuster.

What if, instead of arbitrary sorting, we could crowd-source the order of lists on our websites?

You probably think this is another tutorial about writing a recommendation engine using R or Python, but it's not. This isn't a tutorial about databases either, or any other backend tech for that matter. No sir, we'll talk front-end only aka serverless JavaScript aka DataDrivenJS.

What is serverless JavaScript?

It's a hosted backend that you can interact with using a JavaScript API.

What is DataDrivenJS?

It's almost like Google Analytics. It tracks what people do on your website. But it doesn't show you any pie charts. Instead, DataDrivenJS makes it very easy for front-end devs, to read the tracked data back on the site and add - as the name suggests - data-driven features.

Here's how easy it is.

Sign up for free, add the tracking code to your site, and then run a code like this to send data to your account every time a user views a post (or: orders a product, clicks a navigation item etc.) :

DD.tracker.trackMetaEvent('viewed post','My Post About DataDrivenJS');

Then, with a couple more lines of JavaScript, you can read that data back (see the docs):


// prepare public data feed

var feed = DD.data.feed('My Blog Posts');
feed.select(
  DD.data.datapoints.metaevent('viewed post'),
  DD.data.datapoints.metaevent('viewed post').count().as('views')
).orderBy(
  DD.data.feedColumn('views').desc()
);

// read the entire data feed
// curious what '{}' is for? I'll tell your later!

DD.reader.read(feed, {}, function(response){
  console.log(response.results);
});

You will get a list of all your blog posts sorted by views - from the most to the least viewed:

[
  {"viewed post":"My Post About Cats", "views":1000},
  {"viewed post":"My Post About DataDrivenJS", "views":50},
  {"viewed post":"My Post About Python", "views":10}
]

The data above is enough to sort posts that are already displayed on your page.

Easy? But what if you have hundreds of blog posts and not all of them are loaded at once?

Instead of tracking just the titles, you could track a JSON with all the details:

DD.tracker.trackMetaEvent('viewed post',JSON.stringify({
  "Title":"My Post About DataDrivenJS",
  "URL":"https://dev.to/my-post-about-datadrivenjs/",
  "Thumbnail":"https://dev.to/images/my-thumbnail.png",
  "Category":"JavaScript"
}));

and then you will have enough data to create a list of your blog posts with thumbnail images.

You could even filter the results:


// prepare public data feed

var feed = DD.data.feed('My Blog Posts');
feed.select(
  DD.data.datapoints.metaevent('viewed post'),
  DD.data.datapoints.metaevent('viewed post').count().as('views')
).orderBy(
  DD.data.feedColumn('views').desc()
);

// read only 5 most-viewed posts related to JavaScript

var query = DD.data.feedQuery().select(
  DD.data.feedColumn('viewed post')
).where(
  DD.data.feedColumn('viewed post').contains('javascript') // case insensitive
).limit(5);

DD.reader.read(feed, query, function(response){
  console.log(response.results);
});

You may say - "oh, I could do that without DataDrivenJS - I just need some PHP and MySQL and I'm good". You're right. With some effort, you can add a column in your database to store views for each item. But what if, after a month, you would like to change how the sorting works, and sort items differently for mobile and desktop users?

With DataDrivenJS, the data you need is already there, all you need to do, is to create a separate feed with data from mobile visits:


// prepare public data feed

var feed = DD.data.feed('My Blog Posts for Mobile');
feed.select(
  DD.data.datapoints.metaevent('viewed post'),
  DD.data.datapoints.metaevent('viewed post').count().as('views')
).orderBy(
  DD.data.feedColumn('views').desc()
).from(
  DD.data.segment('Mobile Visits').where(
    DD.data.datapoints.property('screenWidth').isLessThanOrEqualTo(480),
  )
);

// read the entire feed

DD.reader.read(feed, {}, function(response){
  console.log(response.results);
});

At this point, you must think - "this girl wrote a tutorial about sorting lists, but she hasn't actually sorted a single list!". True, but I'm sure you'll figure out that part by yourself*!

* in case you won't, here's a codepen with a working example - note, that the script added to 'resources' contains project specific params.

Top comments (2)

Collapse
 
joshcheek profile image
Josh Cheek

Nice :) I'd be interested in decisions it makes while talking to the backend. Eg it seems fairly smart about batching, events together, which is one of my frequent issues with tracking tools (b/c when done poorly, it wastes users' data plan and battery) I guess, for me, efficient use of resources is an important feature, so if you're intending to make more of these, that'd be a cool topic :)


SS where I modified the codepen to emit the event 5 times, but in different ticks of the event loop, and it batched them all into a single request:

batched requests

Collapse
 
anndd profile image
Anna Su**

Thanks Josh! The events are stored in a small, session-persistent buffer and sent in short intervals and/or when the buffer is full - both, time and size, are configurable. Of course, there's always a chance that if a user clicks an external link some events might never be sent, but that could also be the case if you'd set the buffer size to 0 to force an immediate request.

Sending events in packages, besides the benefits you mentioned, helps to avoid reaching the limit of parallel connections both, in a browser and in the backend. Considering how many different tracking services are installed on every website, managing resources is critical.

One of the hardest decisions building the lib was to drop support for promises, simply because the polyfill would significantly increase the size.