How can I update the topics list after making a change?

I have implemented a custom button on the Post action menu that “boots” the post out of the current topic thread. It is an action available to the topic author which essentially performs the move operation, but with less clicks. The backend operation is performed by a new custom API endpoint. My button is working, except that I have to manually refresh the page in order to see that the post was moved.

After refresh, the post looks like this:

My question is, what’s the best way to update the DOM with this change? Ideally I would just replace the post with the split placeholder pictured above, and avoid refreshing the whole page, or whole topics list.

I can imagine, if I have my API endpoint return the changed topic, then I just need to replace/inject the new post into the topic list?

So I guess this is a question of how state management works within Discourse, and how I can hook into that lifecycle.

2 Likes

Look in core (or the all-the-plugins repo) for things that update the MessageBus

1 Like

Solution

Putting this at the end of my API method solved the problem:

MessageBus.publish("/topic/#{@topic.id}", reload_topic: true, refresh_stream: true)

This should refresh not only for the current user, but anyone else viewing the topic.

Exploration that led me here

Read on if you want to track with me through the code base. I share this for anyone else it may be useful for.

What is MessageBus?

From Sam’s 2013 explanation of the MessageBus it seems like the Ruby code can publish and subscribe, whereas the Javascript code can only subscribe. Ok, so under this proposed architecture my server side code will be responsible for notifying the UI.

Is there a MessageBus example that is useful to me?

None of the plugin examples do anything with MessageBus.

GroupArchivedMessage.move_to_inbox! calls this code which is somewhat close to what I want, but the type code used here won’t work for me.

MessageBus.publish("/topic/#{topic_id}", { type: "move_to_inbox" }, group_ids: [group_id])

This code looks promising.

MessageBus.publish("/topic/#{@topic.id}", reload_topic: true, refresh_stream: true)

This was the final solution I chose.

What happens when the browser receives the update?

The Topics controller is subscribed to updates on the topic it is presently viewing. It triggers “post-stream:refresh”, which initiates the refresh. The only thing I can find listening on the other end is the ScrollingPostStream which binds a _refresh method to the event. This seems to be what’s actually performing the update.

By this I infer that if I wanted to just update the present client without pushing the update to anyone else, I could call the same code myself:

this.appEvents.trigger("post-stream:refresh", args)

And it looks like I could call this from any component.

What about the original implementation of move_posts? Does it publish to MessageBus?

Investigating core I see: The topic controller move_posts() method calls move_posts_to_destination() which calls topic.move_posts() which calls new PostMover() and then either to_topic() or to_new_topic(). These methods don’t call MessageBus, but they do call DiscourseEvent.trigger().

I call into topic.move_posts() from my custom API method. So my code calls directly into the model, and skips any of the existing controller logic:

new_topic = topic.move_posts(current_user, [post.id], {title: title, category_id: category_id})

In my case move_posts_to() gets called as the final step in the sequence, which calls the following:

DiscourseEvent.trigger(
  :posts_moved,
  destination_topic_id: destination_topic.id,
  original_topic_id: original_topic.id,
)

DiscourseEvent doesn’t seem to have anything to do with MessageBus, however. Maybe it’s only used for internal lifecycles within the server?

So I’m concluding that the MessageBus is not normally published to for a topic move.

4 Likes

Oh. Sorry. That’s a little surprising! I guess I should have sent you to core! I have a plugin that uses it, but it’s not public and it uses message buss for a custom model.

I’m not sure, but I think you probably don’t need reload topic (unless stuff changes that’s not displayed without the changed attributes?), but I’ve got a couple of places on my plugin where I think that might solve a problem!