Skip to main content

29 Rails & Vue.js Trello Clone - Part 1

Episode 217 · November 29, 2017

Set up Rails models, scaffolds, and webpacker with Vue.js to start rendering our views

Javascript VueJS


Transcripts

What's up guys? This episode we're going to be building a clone of Trello, which we've been talking about in previous episodes, where we're going to use Vue.js to build the drag and drop support for the list themselves and the cards that are underneath them, and then we're going to be syncing this back to the rails app using AJAX, so that's what we're going to be doing in this episode and I hope you enjoy it. Let's dive in and take a look at Trello real quick if you aren't familiar with it.

They have the ability for you to add lists, where you can add multiple columns in here, and you can archive them and so on, but then you can underneath them add cards which you can give Titles and you can drag them around. You can also drag and drop those lists around, and so every time you make one of these changes that will be persisted server side, so that all of your changes are synced and saved in the database, so next time you open this up it will be visible. We're going to be replicating this where you can add lists and cards underneath these lists, and then have the same drag and drop functionality using Vue.js on the front end.

This is one of those things where you are required to use JavaScript for drag and drop so it makes sense for us to use something like Vue so that we can have some of that functionality taken care of for us when we're building out all of the JavaScript for the front end. What we've got right now is just a blank rails application that has devise and bootstrap installed, we're not even using devise at all, we don't care about user accounts in this example but we do want devise to give it some sort of layout and some helper classes in CSS that we can use to make this look a little prettier. What we're going to do is we're going to go and generate a scaffold for the list model, this is going to need a name for that title of the list, and then it's also going to need a position of integer which will denote which column it should be, so it will start at one, two, three, four, and if you drag and drop it will be able to remember the position, so next time you refresh the page it will be in the same spot.

rails g scaffold list new position:integer

So we're going to need that, so we're going to generate that scaffold, and then we're also going to need to generate a scaffold for cards that refer to the list

`

That's going to allow us to point back to the list, those are going to have names, they can have descriptions, assigned users, all kinds of other things you might want to add. We're not going to worry about any of those, but we're also going to have a position here, so we know if you drag it down in the list we can save it in that position, and keep it in the same correct list as well, so we're going to generate both of those, and then we're going to hop into the Gemfile, and we'll add the

gem 'acts_as_list'

This is going to be the gem that allows us to do that sorting for those positions and all of that fun stuff, then we're going to rails db:migrate to run those migrations, and our models folder is going to have a list, and we want to make sure that we have the has_many :cards association in here, but we also want to tell it by default we always want you to order these by position ascending, and so that's going to make sure that these cards are always in order based upon the acts_as_list position, then we're also going to say dependend: :destroy because if you remove the list we want to also remove those cards, and this should be acts_as_list so that the lists themselves are organized appropiately. Then, let's also add a validation here for validates :name, presence: true keep that validation in there just to make sure that our database is set well, we're going to have acts_as_list here as well, but this one is going to have a scope of the list but you're going to have as a belongs_to :list here. So the card belong to a list, and then they use that list as the scope and then this is going to valiate name, presence is true as well, and that's going to make sure that our names on our cards are always added as well. Before we start hopping in our views, we also want to

lists_controller.rb

def index
  @lists = list.order(position: :asc)
end

So it also reflects the same ordering that our lists are in the database, so acts_as_list we want to apply here and use that same sorting as well. We could also do sorted as a method here and we could go into lists and say let's add scope :sorted, ->[ order(position: :asc)] and that would be the same thing, we can do that as well just to give that a better naming scheme so it could be referenced in other places as well. With this said, we can go to our routes file, and let's move the cards and lists down here to the bottom, where this should be, and then we have our resources list that we want to make as our root route. So when you come to the website, we'll start up our server, when you come to the server you'll be presented with the list index page, so once that is loaded, we'll be able to create a couple lists here and we'll go and change up that UI so that we have the list in columns instead of where they will be in a table right now. Right now we'll have name, we'll have our backlog, we'll have a new list for in progress, we'll have a new list for completed, and we can go to that form if we wanted and say: Well, our list forms shouldn't actually add the position in there because that is automatically handled by the database acts_as_list plugin, so that's going to take care of those in order, but what we should do is in our index we should maybe get rid of this table, and handle this some other way, so what we might do is we might say: Well, let's give it a class of 'row' and this is just bootstrap stuff for us, but we might say that all of these columns or div

index.html.erb

<div class="row">
    <% @lists.each do |list| %>
    <div class="col-3">
        <h5><%= list.name %></h5>
    </div>
    <% end %>
</div>

This isn't going to be exactly the same as Trello, where you would actually want a div that overflows and scrolls left and right as opposed to this where it wwill actually wrap, so we would have to come back and change some of the CSS later on in order to make these the proper widths and so on, but for now we can prototype this out with bootstrap pretty easily and get well on our way, and then we can go back and change the CSS up to make it actually scrollable if we have multiple columns like we do in Trello. Really, we still have rails controlling our UI here, and it would be nice if we actually had this automatically handled for us by Vue.js. That's what we're going to do here, we're going to create

<%= tad.div id: :boards, data { lists: @lists.to_json(include: :cards)} %>

What this is going to do is take our database list the cards, convert them to JSON with the nested structure and everything, and then we're going to give that as preview data for Vue.js to load up, so when the page loads it knows all of the lists and cards that are available, and then we can render those out using Vue.js instead of using rails. What this will look like now is nothing, but we will be able to then use webpacker to set that up.

What I'm going to do is go to our console, we're going to run rails webpacker:install:vue This is going to give us all the defaults for Vue, that will go ahead and give us the configurations file stuff and a really simple example, but we're going to make some modifications to this, and we're also going to yarn add vuedraggable which we'll use as the library to make drag and drop and sorting of our lists, so we'll add that requirements right now, and then we'll also go up to our application, or our head tag, and then change javascript_include_tag to javascript_pack_tag and stylesheet_pack_tag for application on both of those, that's going to set it up so that it will use and include the stylesheets in JavaScript for our webpacker and application js file, and so here we'll go to packs/application.js and we're going to define our Vue.js application in here. Now, we're going to use Vue import:

import Vue from 'vue/dist/vue.esm'
import App from '../app.vue'

window.store = {}

document.addEventListener("turbolinks:load", function() {
  var element = document.querySelector("#boards")
  if (element != undefined) {
    window.store.lists = JSON.parse(element.dataset.lists)

    const app = new Vue({
      el: element,
      data: window.store,
      template: "<App :original_lists='lists' />",
      components: { App }
    })
  }
});

This is basically saying: Any app tag that you see in the template is going to reference the app variable, which is our app.vue file, and so that is going to get everything loaded up in our app.vue file, so from here we can just try this out and you should see we get hello vue, and everything is loaded correctly, but we can actually go and say: Well, we received some props called: original_lists and when we do that, that says that if we go back to application.js, so if we go to that in our text file, we are passing in an original_lists and that equals the data.list. This is actually giving tht original_lists in as a prop, and so we have the original list, here's a prop which we can accept, and then we can say

<div v-for="list in original_lists">
    {{ list.name }}
</div>

We can print all of those list names out, and here you will see backlog, in progress and completed, then if we wanted to make those columns once again, we can have our classes row at the top, and then for each of those lists we can have

<div v-for="list in original_lists" class='col-3'>

and we can go back and now we can see we have three columns. If we want to give those h6 headers, we can do that, and then we could go and give them, say a little breaker there, and we could have

<div v-for="(card, index) in list.cards" class="card card-body">
{{  card.name  }}
</div>

Because we haven't created any cards yet, we can go create one in the database, and if we run rails console here, we can have

List.first.cards.create name: "Test"

That will create a card in our database, and we can now see that we have a card here and if we did that, same thing with the last list, we could put one over the end as well, and so what we want to be able to do is have these as drag and droppable, but we also want to be able to create new cards in the UI as well as drag and drop them in the columns themselves, so there's quite a lot that we want to do here, but for now we have the visual UI laid out really easily, so I'm going to cut this off here, and we're going to come back next episode and start working on the drag and drop functionality as well as creating new cards using AJAX and Vue draggable to do all of that dragging stuff.

Discussion