(Superseded) Plugin tutorial #3 - How to add a button after every post?

I’ve got a fun, little challenge here. Trying to add a button to the end of each post that allows the creator of a given topic to mark one post as being “correct.” This is similar to what you might find on a site like StackOverflow. I’ve got it working (kinda), but am running into a couple issues. Check out the Github Repo for all the code. And here’s a screenshot of what it looks like:

As you can see, there are “Mark as correct” buttons after the 1st and 3rd posts that the topic owner can click to choose the post that answered his/her question. When that button is clicked, it switches to looking like the highlighted post in the middle. Notice the button has changed to text that reads, “This post was marked as correct.”

Jumping into the code, assets/javascripts/correct_post.js is where I’m having some trouble. Here’s the code from that file:

Discourse.PostMenuView.reopen({
  shouldRerenderCorrectButton: Discourse.View.renderIfChanged("post.correctPostId"),

  renderCorrect: function(post, buffer) {
    var correctPostId = post.getWithDefault("correctPostId", this.get("post.topic.correct_post_id"));
    
    if (correctPostId && this.get("post.id") == correctPostId) {
      buffer.push("<div id='correct-text'>This post was marked as correct</div>");
      var postNumber = this.get("post.post_number");
      setTimeout(function() {
        $("#post_" + postNumber + " .topic-body").addClass("correct-post");
      }, 250);
    } else {
      if (this.get("post.topic.details.can_edit")) {
        buffer.push('<button title="Mark this post as solving your initial question" data-action="correct">Mark as correct</button>');
      }
    }
  },

  clickCorrect: function() {
    this.get('controller').markSolved(this.get("post"));
    $(".correct-post").removeClass("correct-post");
    $("#correct-text").replaceWith('<button title="Mark this post as solving your initial question" data-action="correct">Mark as correct</button>');
    this.set("post.correctPostId", this.get("post.id"));
  }
});

Off the bat, you’ll see a couple things that look extremely ugly:

  1. The use of setTimeout() in renderCorrect to add a class to the correct post.
  2. The use of removeClass() and replaceWith() in clickCorrect to unselect a previously correct post if the topic creator changes his/her mind and chooses a new correct post.

setTimout()

The reason I succumbed to using setTimeout() was due to page loading via HTML versus page rendering via JSON. At first, I figured I could just use the code within the setTimeout() function (but without the setTimeout()). That worked fine if the page was already loaded and all I did was select a correct post. If I then refreshed the page, the correct post did not appear highlighted in green because

$("#post_" + postNumber + " .topic-body")

did not exist when addClass() was called. Thus, I decided to use setTimeout.

removeClass() and replaceWith()

The use of removeClass() and replaceWith() I’m sure is easily resolved with better use of Ember, but I’m pretty new to Ember, so I need some help. Basically, I was hoping clickCorrect could just look like this:

clickCorrect: function() {
  this.get('controller').markSolved(this.get("post"));
  this.set("post.correctPostId", this.get("post.id"));
}

would mean that anytime the topic creator selected a new correct post, "post.correctPostId" would update, and all of the post menu buttons on the page would re-render. Turns out that clicking the “Mark as correct” button for a particular post only triggers the re-rendering of the post menu buttons for that post and not all of the posts on the page.

Again, while the solution above works, (1) it’s clunky and (2) it does have a failure point. If you click the “Mark as Correct” button for a few different posts, it stops working after a while. I believe this is a consequence of using too much jQuery to render new HTML on the page.


Feel free to drop this plugin into your app in local development and play around with it. Would appreciate any help (PRs to the Github Repo preferred!). Thanks!