I have a website where people can write articles and publish/unpublish them. This is done via single button on the page:
<a id="publish-button" href="#" class="btn btn-default <%= 'btn-success' if @article.published? %>">
<span class="glyphicon glyphicon-globe"></span>
</a>
As you can see, if an article is already published I'm adding additional btn-success
class.
Now, this is the JavaScript code that handles Ajax clicks on that button:
var $editor = $('#editor'); // Article container, it contains update action url as data-url
var $publishButton = $('#publish-button');
var togglePublished = function () {
var url = $editor.data('url');
var postData = {
article: {
stage: $publishButton.hasClass('btn-success') ? 'unpublished' : 'published'
},
_method: 'PUT'
};
$.ajax({
url: url,
data: postData,
method: 'POST',
success: function (article) {
$publishButton['published' == article.stage ? 'addClass' : 'removeClass']('btn-success');
}
});
};
$publishButton.click(function (e) {
e.preventDefault();
togglePublished();
});
My controller action just returns a JSON encoded article in this case:
def update
if @article.update(article_params)
render json: @article
else
render json: { errors: @article.errors.full_messages }, status: :unprocessable_entity
end
end
There are few things I see wrong with this:
- My post data depends on class, which can be changed and changing it would require me to change it on two places: in template and in JS.
- If I add
data-published
attribute to the button I would still have to add/remove class after Ajax has finished resulting in duplicated view logic (in HTML file and JS file).
I can see two ways to avoid this and be DRY:
Add conditional class only in JavaScript and run check upon page load
This would be fairly easy to do but would result in a brief delay because conditional class would be added in JavaScript which runs after all HTML has already loaded.
Extract button to partial view and return it from controller in Ajax
This seems like a more solid approach but since my update
action is used in other places too it has to return article as JSON. That's why I would have to create another controller action:
def toggle_published
@article.toggle_published # Something like this
render partial: '_publish_button'
end
This reduces "restfulness" of my controller and it seems kind of silly to extract only one HTML element into a partial view.
Is there any better way to do this? It seems like a minor and insignificant code duplication but it's not an isolated example and as website complexity grows these things can multiply a lot and cause a lot of headaches.