Reddit as you probably know is a huge social networking, news website. Check out the /r/emberjs subreddit It has a really great growing community of Ember developers.
Today I thought it would be fun to see if I could make an application that grabs picture data from any subreddit to display in a gallery. I was inspired by Yehuda Katz talk at EmberCamp London. Check it out if you haven't already.
I uploaded my finished project here (thanks PageFront!). If you like to follow along the source code is on Github here. It has a few bugs however for the most part you can click around on any of the subreddits and look at the galleries. To make things a little simpler I'm only getting information from imgur domains and I'm excluding albums.
What We Go Over In This Tutorial
In this tutorial we'll go over some basic routing, with dynamic segments. We'll look at returning data from a model using jQuery and afterModel. We'll use filterBy and forEach to do some filtering of our data. Lastly we'll be using a computed property and error states.
Setup
For this example I'm using Ember version 2.2.0 with the latest version of Ember CLI 1.13.13. We'll assume you already have node, Ember-CLI and bower installed. If not check out the official [Ember CLI guide](http://www.ember-cli.com/user-guide/#getting-started.
To begin enter these commands.
$ ember new RedditExample
$ cd RedditExample
$ ember g route r --path r/:subreddit
$ ember g controller r
$ ember g template index
$ ember g template r-error.hbs
$ ember install ember-cli-lightbox
The route command will create a new route called r, with a path to a dynamic segment called subreddit. The subreddit will passed in the URL as a parameter to the model. We'll look at that later.
The rest of the commands generate the basic boiler plate code we need to for our controller, index and our error state.
Setting up the router
The router should be already be setup. Nevertheless here is what it should look like.
// app/router.js
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('r', {path:'r/:subreddit'} );
});
export default Router;
The router is where the routes for the application are setup. Ember gives us the default application/index route for free.
In this example I want to simulate the URL structure of reddit so each subreddit will follow the /r/subredditname URL structure.
Keep in mind that a dynamic segment, in this case subreddit, is retrieved from the URL, and passed to the model as a parameter. We'll need that later when we setup the model in our routes.
Routes
The route is where we can pass data to our templates. To do this we'll create a model and an after model.
// app/routes/r.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params){
return Ember.$.getJSON(`https://www.reddit.com/r/${params.subreddit}.json`);
},
afterModel: function(r){
r.subreddit = r.data.children[0].data.subreddit || '';
}
});
In this example we're taking the model and returning, using jQuery, json data. Reddit has a nice ability to return back any type of json data by just attaching .json at the end of the URL. To understand this more type in your browser a URL to Reddit and see what the json data returns.
Notice that the model uses ${params.subreddit}. The parameter is the URL subreddit dynamic segment that we created earlier. It's sent as a hash to the model from the URL. For example if you type in http://localhost/r/pics then the model will retrieve pics in the params.subreddit. By the way we are using ES6 string interpolation. That's why the param.subreddit is surrounded by ${}.
The afterModel is triggered after the model is loaded. The model is returned in the parameter for the function. For fun I decided to pull the name of the subreddit out of the model data, and define it at r.subreddit.
Editing the data with a controller
We got all this json data from the model. Now we want to edit it in a way so that our template can display it. The goal is to create a gallery of picture images from each subreddit.
We could do this a couple of ways. We could edit the data in the model or edit it in a component or controller. For fun I decided to edit it in the controller instead of the model.
// app/controllers/r.js
import Ember from 'ember';
export default Ember.Controller.extend({
pictures: function() {
let model = this.get('model');
let c = model.data.children.filterBy('data.domain','i.imgur.com');
c.forEach(function(item){
let newURL = item.data.url.replace(/^http:\/\//i, 'https://');
let thumbURL = item.data.thumbnail.replace(/^http:\/\//i, 'https://');
Ember.set(item,'data.url',newURL);
Ember.set(item,'data.thumbnail',thumbURL);
if(item.data.thumbnail === 'nsfw'){
Ember.set(item,"data.thumbnail",'https://farm3.staticflickr.com/2571/3810679130_fbb7494d7b_t.jpg');
}
});
return c;
}.property('model.data.children.[]')
});
There is a lot going on here, so I'll try to explain. Pictures is a computed property that gets updated anytime the model.data.children.[] changes. If you look at the json data children is an array, hence the [] at the end.
The first step is to get the model and filter out the domain so only domains from i.imgur.com are left. I do this because this is just a simple example and I don't want to deal with a bunch of different domains.
c.forEach(function(item){
let newURL = item.data.url.replace(/^http:\/\//i, 'https://');
let thumbURL = item.data.thumbnail.replace(/^http:\/\//i, 'https://');
Ember.set(item,'data.url',newURL);
Ember.set(item,'data.thumbnail',thumbURL);
if(item.data.thumbnail === 'nsfw'){
Ember.set(item,"data.thumbnail",'https://farm3.staticflickr.com/2571/3810679130_fbb7494d7b_t.jpg');
}
});
This isn't the greatest example but I wanted to take each URL and replace it with it's https counterpart (My host required https). This is done with the replace and some basic regular expression.
We must use Ember.set to make changes to the Ember model. In this case we are changing the URL's to the https. Lastly if the item's thumbnail shows nsfw I changed it to a different thumbnail.
Now we can use pictures in our template with the correct picture data.
Adding the template information
We have the router, route and controller setup, let's take a look at some of our templates. This is what is shown to the user.
// app/templates/r.hbs
/r/{{model.subreddit}}<br>
{{#each pictures as |info|}}
{{#if info.data}}
{{#light-box href=info.data.url data-lightbox=info.data.title data-title=info.data.title inlineImage=false}}
<div class="img" >
<img src="{{info.data.thumbnail}}" width="110" height="90" >
</div>
{{/light-box}}
{{/if}}
{{/each}}
The template r has access to the controllers properties and the model in our route. In this code it iterates through the data using the each helper checking to make sure the data exists before displaying it. The light-box add-on installed earlier creates a modal for the picture to popup after the thumbnail is clicked.
Let's add an error route in case someone navigates to a route that doesn't exist.
// app/templates/r-error.hbs
Error! Subreddit Does Not Exist! <br> {{#link-to 'application'}}Back{{/link-to}}
Ember knows to navigate to this route because it has -error at the end.
Finally we'll update the index file with some sample subreddits to use.
{{#link-to 'r' 'pics'}}/r/pics{{/link-to}}<br>
{{#link-to 'r' 'wallpaper'}}/r/wallpaper{{/link-to}}<br>
{{#link-to 'r' 'reactiongifs'}}/r/reactiongifs{{/link-to}}<br>
{{#link-to 'r' 'wallpapers'}}/r/wallpapers{{/link-to}}<br>
{{#link-to 'r' 'aww'}}/r/aww{{/link-to}}<br>
{{#link-to 'r' 'cats'}}/r/cats{{/link-to}}<br>
{{#link-to 'r' 'foxes'}}/r/foxes{{/link-to}}<br>
{{#link-to 'r' 'images'}}/r/images{{/link-to}}<br>
{{#link-to 'r' 'memes'}}/r/memes{{/link-to}}<br>
<br>
Just type in URL /r/nameofsubreddit
The first argument of the link-to helper is the name of the route. The second is the segment that will passed into the route.
Finally we'll add some CSS to make it look a little bit nicer.
// app/styles/app.css
div.img {
margin: 5px;
padding: 5px;
border: 1px solid #0000ff;
height: auto;
width: auto;
float: left;
text-align: center;
}
div.img img {
display: inline;
margin: 5px;
border: 1px solid #ffffff;
}
div.img a:hover img {
border:1px solid #0000ff;
}
div.desc {
text-align: center;
font-weight: normal;
width: 120px;
margin: 5px;
}
div.img2 {
background-size: contain;
background-position: 50% 50%;
background-repeat: no-repeat;
}
I think I took this from W3Schools. -_-
Putting it all together
If you run the app it should look like this.
Future
There is a lot more we can do here. We can add in the imgur api so I can access albums or not limit the domain to just imgur. I can add in a way to look at comments. This is all pretty easy at this point. I'll be taking a look at some of these enhancements in the future.
What do you think? Have any questions? Leave a comment below.
Thanks to Reddit for the images